    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 &lt;strong&gt;{2}&lt;/strong&gt;".</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 &lt;strong&gt;{2}&lt;/strong&gt;"
    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 &lt;strong&gt;{2}&lt;/strong&gt; 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}