001    /* 
002        Licensed to the Apache Software Foundation (ASF) under one
003        or more contributor license agreements.  See the NOTICE file
004        distributed with this work for additional information
005        regarding copyright ownership.  The ASF licenses this file
006        to you under the Apache License, Version 2.0 (the
007        "License"); you may not use this file except in compliance
008        with the License.  You may obtain a copy of the License at
009    
010           http://www.apache.org/licenses/LICENSE-2.0
011    
012        Unless required by applicable law or agreed to in writing,
013        software distributed under the License is distributed on an
014        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015        KIND, either express or implied.  See the License for the
016        specific language governing permissions and limitations
017        under the License.  
018     */
019    package org.apache.wiki.workflow;
020    
021    import java.security.Principal;
022    import java.util.HashMap;
023    import java.util.Map;
024    
025    import org.apache.wiki.WikiEngine;
026    import org.apache.wiki.api.exceptions.WikiException;
027    
028    /**
029     * Factory class that creates common Workflow instances such as a standard approval workflow.
030     */
031    public final class WorkflowBuilder
032    {
033        private static final Map<WikiEngine, WorkflowBuilder> BUILDERS = new HashMap<WikiEngine, WorkflowBuilder>();
034        private final WikiEngine m_engine;
035    
036        /**
037         * Private constructor that creates a new WorkflowBuilder for the supplied WikiEngine.
038         * @param engine the wiki engine
039         */
040        private WorkflowBuilder( WikiEngine engine )
041        {
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        {
053            WorkflowBuilder builder = BUILDERS.get( engine );
054            if ( builder == null )
055            {
056                builder = new WorkflowBuilder( engine );
057                BUILDERS.put( engine, builder );
058            }
059            return builder;
060        }
061    
062        /**
063         * <p>Builds an approval workflow that requests approval from a named
064         * user, {@link org.apache.wiki.auth.authorize.Group} or
065         * {@link org.apache.wiki.auth.authorize.Role} before running a Task.</p>
066         * <p>The Principal who approves the activity is determined by looking up
067         * the property <code>jspwiki.approver.<var>workflowApproverKey</var></code>
068         * in <code>jspwiki.properties</code>. If that Principal resolves to a known user, Group
069         * Role, a Decision will be placed in the respective workflow queue (or multiple queues,
070         * if necessary). Only one approver needs to make the Decision, and if the request is
071         * approved, the completion task will be executed. If the request is denied, a
072         * {@link SimpleNotification} with a message corresponding to the <code>rejectedMessage</code>
073         * message key will be placed in the submitter's workflow queue.</p>
074         * <p>To help approvers determine how to make the Decision, callers can supply an
075         * array of Fact objects to this method, which will be added to the Decision in the order
076         * they appear in the array. These items will be displayed in the web UI.
077         * In addition, the value of the first Fact will also be added as the third message
078         * argument for the workflow (the first two are always the submitter and the approver).
079         * For example, the PageManager code that creates the "save page approval" workflow
080         * adds the name of the page as its first Fact; this results in the page name being
081         * substituted correctly into the resulting message:
082         * "Save wiki page &lt;strong&gt;{2}&lt;/strong&gt;".</p>
083         * @param submitter the user submitting the request
084         * @param workflowApproverKey the key that names the user, Group or Role who must approve
085         * the request. The key is looked up in <code>jspwiki.properties</code>, and is derived
086         * by prepending <code>jspwiki.approver</code> to the value of <code>workflowApproverKey</code>
087         * @param prepTask the initial task that should run before the Decision step is processed.
088         * If this parameter is <code>null</code>, the Decision will run as the first Step instead
089         * @param decisionKey the message key in <code>default.properties</code> that contains
090         * the text that will appear in approvers' workflow queues indicating they need to make
091         * a Decision; for example, <code>decision.saveWikiPage</code>. In the i18n message bundle
092         * file, this key might return text that reads "Approve page &lt;strong&gt;{2}&lt;/strong&gt;"
093         * @param facts an array of {@link Fact} objects that will be shown to the approver
094         * to aid decision-making. The facts will be displayed in the order supplied in the array
095         * @param completionTask the Task that will run if the Decision is approved
096         * @param rejectedMessageKey the message key in <code>default.properties</code> that contains
097         * the text that will appear in the submitter's workflow queue if request was
098         * not approved; for example, <code>notification.saveWikiPage.reject</code>. In the
099         * i18n message bundle file, this key might might return
100         * text that reads "Your request to save page &lt;strong&gt;{2}&lt;/strong&gt; was rejected."
101         * If this parameter is <code>null</code>, no message will be sent
102         * @return the created workflow
103         * @throws WikiException if the name of the approving user, Role or Group cannot be determined
104         */
105        public Workflow buildApprovalWorkflow( Principal submitter,
106                                               String workflowApproverKey,
107                                               Task prepTask,
108                                               String decisionKey,
109                                               Fact[] facts,
110                                               Task completionTask,
111                                               String rejectedMessageKey ) throws WikiException
112        {
113            WorkflowManager mgr = m_engine.getWorkflowManager();
114            Workflow workflow = new Workflow( workflowApproverKey, submitter );
115    
116            // Is a Decision required to run the approve task?
117            boolean decisionRequired = mgr.requiresApproval( workflowApproverKey );
118    
119            // If Decision required, create a simple approval workflow
120            if ( decisionRequired )
121            {
122                // Look up the name of the approver (user or group) listed in jspwiki.properties;
123                // approvals go to the approver's decision cue
124                Principal approverPrincipal = mgr.getApprover( workflowApproverKey );
125                Decision decision = new SimpleDecision( workflow, decisionKey, approverPrincipal );
126    
127                // Add facts to the Decision, if any were supplied
128                if ( facts != null )
129                {
130                    for ( Fact fact: facts )
131                    {
132                        decision.addFact( fact );
133                    }
134                    // Add the first one as a message key
135                    if ( facts.length > 0 )
136                    {
137                        workflow.addMessageArgument( facts[0].getValue() );
138                    }
139                }
140    
141                // If rejected, sent a notification
142                if ( rejectedMessageKey != null )
143                {
144                    SimpleNotification rejectNotification = new SimpleNotification( workflow, rejectedMessageKey, submitter );
145                    decision.addSuccessor( Outcome.DECISION_DENY, rejectNotification );
146                }
147    
148                // If approved, run the 'approved' task
149                decision.addSuccessor( Outcome.DECISION_APPROVE, completionTask );
150    
151                // Set the first step
152                if ( prepTask == null )
153                {
154                    workflow.setFirstStep( decision );
155                }
156                else
157                {
158                    workflow.setFirstStep( prepTask );
159                    prepTask.addSuccessor( Outcome.STEP_COMPLETE, decision );
160                }
161            }
162    
163            // If Decision not required, just run the prep + approved tasks in succession
164            else
165            {
166                // Set the first step
167                if ( prepTask == null )
168                {
169                    workflow.setFirstStep( completionTask );
170                }
171                else
172                {
173                    workflow.setFirstStep( prepTask );
174                    prepTask.addSuccessor( Outcome.STEP_COMPLETE, completionTask );
175                }
176            }
177    
178            // Make sure our tasks have this workflow as the parent, then return
179            if ( prepTask != null )
180            {
181                prepTask.setWorkflow( workflow );
182            }
183            completionTask.setWorkflow( workflow );
184            return workflow;
185        }
186    
187    }