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