001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.workflow;
021:
022: import java.security.Principal;
023: import java.util.ArrayList;
024: import java.util.Collections;
025: import java.util.List;
026:
027: import com.ecyrd.jspwiki.WikiException;
028:
029: /**
030: * <p>
031: * AbstractStep subclass that asks an actor Principal to choose an Outcome on
032: * behalf of an owner (also a Principal). The actor "makes the decision" by
033: * calling the {@link #decide(Outcome)} method. When this method is called,
034: * it will set the Decision's Outcome to the one supplied. If the parent
035: * Workflow is in the {@link Workflow#WAITING} state, it will be re-started.
036: * Any checked WikiExceptions thrown by the workflow after re-start will be
037: * re-thrown to callers.
038: * </p>
039: * <p>
040: * When a Decision completes, its
041: * {@link #isCompleted()} method returns <code>true</code>. It also tells its
042: * parent WorkflowManager to remove it from the list of pending tasks by calling
043: * {@link DecisionQueue#remove(Decision)}.
044: * </p>
045: * <p>
046: * To enable actors to choose an appropriate Outcome, Decisions can store
047: * arbitrary key-value pairs called "facts." These facts can be presented by the
048: * user interface to show details the actor needs to know about. Facts are added
049: * by calling classes to the Decision, in order of expected presentation, by the
050: * {@link #addFact(Fact)} method. They can be retrieved, in order, via {@link #getFacts()}.
051: * </p>
052: *
053: * @author Andrew Jaquith
054: * @since 2.5
055: */
056: public abstract class Decision extends AbstractStep {
057: private Principal m_actor;
058:
059: private int m_id;
060:
061: private final Outcome m_defaultOutcome;
062:
063: private final List m_facts;
064:
065: /**
066: * Constructs a new Decision for a required "actor" Principal, having a default Outcome.
067: * @param workflow the parent Workflow object
068: * @param messageKey the i18n message key that represents the message the actor will see
069: * @param actor the Principal (<em>e.g.</em>, a WikiPrincipal, Role, GroupPrincipal) who is
070: * required to select an appropriate Outcome
071: * @param defaultOutcome the Outcome that the user interface will recommend as the
072: * default choice
073: */
074: public Decision(Workflow workflow, String messageKey,
075: Principal actor, Outcome defaultOutcome) {
076: super (workflow, messageKey);
077: m_actor = actor;
078: m_defaultOutcome = defaultOutcome;
079: m_facts = new ArrayList();
080: addSuccessor(defaultOutcome, null);
081: }
082:
083: /**
084: * Appends a Fact to the list of Facts associated with this Decision.
085: *
086: * @param fact
087: * the new fact to add
088: */
089: public final void addFact(Fact fact) {
090: m_facts.add(fact);
091: }
092:
093: /**
094: * <p>Sets this Decision's outcome, and restarts the parent Workflow if
095: * it is in the {@link Workflow#WAITING} state and this Decision is
096: * its currently active Step. Any checked WikiExceptions thrown by
097: * the workflow after re-start will be re-thrown to callers.</p>
098: * <p>This method cannot be invoked if the Decision is not the
099: * current Workflow step; all other invocations will throw
100: * an IllegalStateException. If the Outcome supplied to this method
101: * is one one of the Outcomes returned by {@link #getAvailableOutcomes()},
102: * an IllegalArgumentException will be thrown.</p>
103: *
104: * @param outcome
105: * the Outcome of the Decision
106: * @throws WikiException
107: * if the act of restarting the Workflow throws an exception
108: */
109: public void decide(Outcome outcome) throws WikiException {
110: super .setOutcome(outcome);
111:
112: // If current workflow is waiting for input, restart it and remove
113: // Decision from DecisionQueue
114: Workflow w = getWorkflow();
115: if (w.getCurrentState() == Workflow.WAITING
116: && this .equals(w.getCurrentStep())) {
117: WorkflowManager wm = w.getWorkflowManager();
118: if (wm != null) {
119: wm.getDecisionQueue().remove(this );
120: }
121: // Restart workflow
122: w.restart();
123: }
124: }
125:
126: /**
127: * Default implementation that always returns {@link Outcome#STEP_CONTINUE}
128: * if the current Outcome isn't a completion (which will be true if the
129: * {@link #decide(Outcome)} method hasn't been executed yet. This method
130: * will also add the Decision to the associated DecisionQueue.
131: * @return the Outcome of the execution
132: * @throws WikiException never
133: */
134: public Outcome execute() throws WikiException {
135: if (getOutcome().isCompletion()) {
136: return getOutcome();
137: }
138:
139: // Put decision in the DecisionQueue
140: WorkflowManager wm = getWorkflow().getWorkflowManager();
141: if (wm != null) {
142: wm.getDecisionQueue().add(this );
143: }
144:
145: // Indicate we are waiting for user input
146: return Outcome.STEP_CONTINUE;
147: }
148:
149: /**
150: * {@inheritDoc}
151: */
152: public final Principal getActor() {
153: return m_actor;
154: }
155:
156: /**
157: * Returns the default or suggested outcome, which must be one of those
158: * returned by {@link #getAvailableOutcomes()}. This method is guaranteed
159: * to return a non-<code>null</code> Outcome.
160: *
161: * @return the default outcome.
162: */
163: public Outcome getDefaultOutcome() {
164: return m_defaultOutcome;
165: }
166:
167: /**
168: * Returns the Facts associated with this Decision, in the order in which
169: * they were added.
170: *
171: * @return the list of Facts
172: */
173: public final List getFacts() {
174: return Collections.unmodifiableList(m_facts);
175: }
176:
177: /**
178: * Returns the unique identifier for this Decision. Normally, this ID is
179: * programmatically assigned when the Decision is added to the
180: * DecisionQueue.
181: *
182: * @return the identifier
183: */
184: public final int getId() {
185: return m_id;
186: }
187:
188: /**
189: * Returns <code>true</code> if the Decision can be reassigned to another
190: * actor. This implementation always returns <code>true</code>.
191: *
192: * @return the result
193: */
194: public boolean isReassignable() {
195: return true;
196: }
197:
198: /**
199: * Reassigns the Decision to a new actor (that is, provide an outcome).
200: * If the Decision is not reassignable, this method throws
201: * an IllegalArgumentException.
202: *
203: * @param actor the actor to reassign the Decision to
204: */
205: public final synchronized void reassign(Principal actor) {
206: if (isReassignable()) {
207: m_actor = actor;
208: } else {
209: throw new IllegalArgumentException(
210: "Decision cannot be reassigned.");
211: }
212: }
213:
214: /**
215: * Sets the unique identfier for this Decision.
216: *
217: * @param id
218: * the identifier
219: */
220: public final void setId(int id) {
221: m_id = id;
222: }
223: }
|