001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2002 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.HashMap;
024: import java.util.Map;
025:
026: import com.ecyrd.jspwiki.WikiEngine;
027: import com.ecyrd.jspwiki.WikiException;
028:
029: /**
030: * Factory class that creates common Workflow instances such as a standard approval workflow.
031: * @author Andrew Jaquith
032: */
033: public final class WorkflowBuilder {
034: private static final Map BUILDERS = new HashMap();
035: private final WikiEngine m_engine;
036:
037: /**
038: * Private constructor that creates a new WorkflowBuilder for the supplied WikiEngine.
039: * @param engine the wiki engine
040: */
041: private WorkflowBuilder(WikiEngine engine) {
042: m_engine = engine;
043: }
044:
045: /**
046: * Returns the WorkflowBuilder instance for a WikiEngine. Only one WorkflowBuilder
047: * exists for a given engine.
048: * @param engine the wiki engine
049: * @return the workflow builder
050: */
051: public static WorkflowBuilder getBuilder(WikiEngine engine) {
052: WorkflowBuilder builder = (WorkflowBuilder) BUILDERS
053: .get(engine);
054: if (builder == null) {
055: builder = new WorkflowBuilder(engine);
056: BUILDERS.put(engine, builder);
057: }
058: return builder;
059: }
060:
061: /**
062: * <p>Builds an approval workflow that requests approval from a named
063: * user, {@link com.ecyrd.jspwiki.auth.authorize.Group} or
064: * {@link com.ecyrd.jspwiki.auth.authorize.Role} before running a Task.</p>
065: * <p>The Principal who approves the activity is determined by looking up
066: * the property <code>jspwiki.approver.<var>workflowApproverKey</var></code>
067: * in <code>jspwiki.properties</code>. If that Principal resolves to a known user, Group
068: * Role, a Decision will be placed in the respective workflow queue (or multiple queues,
069: * if necessary). Only one approver needs to make the Decision, and if the request is
070: * approved, the completion task will be executed. If the request is denied, a
071: * {@link SimpleNotification} with a message corresponding to the <code>rejectedMessage</code>
072: * message key will be placed in the submitter's workflow queue.</p>
073: * <p>To help approvers determine how to make the Decision, callers can supply an
074: * array of Fact objects to this method, which will be added to the Decision in the order
075: * they appear in the array. These items will be displayed in the web UI.
076: * In addition, the value of the first Fact will also be added as the third message
077: * argument for the workflow (the first two are always the submitter and the approver).
078: * For example, the PageManager code that creates the "save page approval" workflow
079: * adds the name of the page as its first Fact; this results in the page name being
080: * substituted correctly into the resulting message:
081: * "Save wiki page <strong>{2}</strong>".</p>
082: * @param submitter the user submitting the request
083: * @param workflowApproverKey the key that names the user, Group or Role who must approve
084: * the request. The key is looked up in <code>jspwiki.properties</code>, and is derived
085: * by prepending <code>jspwiki.approver</code> to the value of <code>workflowApproverKey</code>
086: * @param prepTask the initial task that should run before the Decision step is processed.
087: * If this parameter is <code>null</code>, the Decision will run as the first Step instead
088: * @param decisionKey the message key in <code>default.properties</code> that contains
089: * the text that will appear in approvers' workflow queues indicating they need to make
090: * a Decision; for example, <code>decision.saveWikiPage</code>. In the i18n message bundle
091: * file, this key might return text that reads "Approve page <strong>{2}</strong>"
092: * @param facts an array of {@link Fact} objects that will be shown to the approver
093: * to aid decision-making. The facts will be displayed in the order supplied in the array
094: * @param completionTask the Task that will run if the Decision is approved
095: * @param rejectedMessageKey the message key in <code>default.properties</code> that contains
096: * the text that will appear in the submitter's workflow queue if request was
097: * not approved; for example, <code>notification.saveWikiPage.reject</code>. In the
098: * i18n message bundle file, this key might might return
099: * text that reads "Your request to save page <strong>{2}</strong> was rejected."
100: * If this parameter is <code>null</code>, no message will be sent
101: * @return the created workflow
102: * @throws WikiException if the name of the approving user, Role or Group cannot be determined
103: */
104: public Workflow buildApprovalWorkflow(Principal submitter,
105: String workflowApproverKey, Task prepTask,
106: String decisionKey, Fact[] facts, Task completionTask,
107: String rejectedMessageKey) throws WikiException {
108: WorkflowManager mgr = m_engine.getWorkflowManager();
109: Workflow workflow = new Workflow(workflowApproverKey, submitter);
110:
111: // Is a Decision required to run the approve task?
112: boolean decisionRequired = mgr
113: .requiresApproval(workflowApproverKey);
114:
115: // If Decision required, create a simple approval workflow
116: if (decisionRequired) {
117: // Look up the name of the approver (user or group) listed in jspwiki.properties;
118: // approvals go to the approver's decision cue
119: Principal approverPrincipal = mgr
120: .getApprover(workflowApproverKey);
121: Decision decision = new SimpleDecision(workflow,
122: decisionKey, approverPrincipal);
123:
124: // Add facts to the Decision, if any were supplied
125: if (facts != null) {
126: for (int i = 0; i < facts.length; i++) {
127: decision.addFact(facts[i]);
128: }
129: // Add the first one as a message key
130: if (facts.length > 0) {
131: workflow.addMessageArgument(facts[0].getValue());
132: }
133: }
134:
135: // If rejected, sent a notification
136: if (rejectedMessageKey != null) {
137: SimpleNotification rejectNotification = new SimpleNotification(
138: workflow, rejectedMessageKey, submitter);
139: decision.addSuccessor(Outcome.DECISION_DENY,
140: rejectNotification);
141: }
142:
143: // If approved, run the 'approved' task
144: decision.addSuccessor(Outcome.DECISION_APPROVE,
145: completionTask);
146:
147: // Set the first step
148: if (prepTask == null) {
149: workflow.setFirstStep(decision);
150: } else {
151: workflow.setFirstStep(prepTask);
152: prepTask.addSuccessor(Outcome.STEP_COMPLETE, decision);
153: }
154: }
155:
156: // If Decision not required, just run the prep + approved tasks in succession
157: else {
158: // Set the first step
159: if (prepTask == null) {
160: workflow.setFirstStep(completionTask);
161: } else {
162: workflow.setFirstStep(prepTask);
163: prepTask.addSuccessor(Outcome.STEP_COMPLETE,
164: completionTask);
165: }
166: }
167:
168: // Make sure our tasks have this workflow as the parent, then return
169: if (prepTask != null) {
170: prepTask.setWorkflow(workflow);
171: }
172: completionTask.setWorkflow(workflow);
173: return workflow;
174: }
175:
176: }
|