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.Context; 022import org.apache.wiki.api.exceptions.WikiException; 023import org.apache.wiki.event.WikiEventEmitter; 024import org.apache.wiki.event.WorkflowEvent; 025 026import java.io.Serializable; 027import java.security.Principal; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.List; 031import java.util.Map; 032 033 034/** 035 * <p> 036 * AbstractStep subclass that asks an actor Principal to choose an Outcome on behalf of an owner (also a Principal). The actor 037 * "makes the decision" by calling the {@link #decide(Outcome, Context)} method. When this method is called, it will set the Decision's Outcome to 038 * the one supplied. If the parent Workflow is in the {@link Workflow#WAITING} state, it will be re-started. Any checked WikiExceptions 039 * thrown by the workflow after re-start will be re-thrown to callers. 040 * </p> 041 * <p> 042 * When a Decision completes, its {@link #isCompleted()} method returns <code>true</code>. It also tells its parent WorkflowManager to 043 * remove it from the list of pending tasks by calling {@link DecisionQueue#remove(Decision)}. 044 * </p> 045 * <p> 046 * To enable actors to choose an appropriate Outcome, Decisions can store arbitrary key-value pairs called "facts." These facts can be 047 * presented by the user interface to show details the actor needs to know about. Facts are added by calling classes to the Decision, 048 * in order of expected presentation, by the {@link #addFact(Fact)} method. They can be retrieved, in order, via {@link #getFacts()}. 049 * </p> 050 * 051 * @since 2.5 052 */ 053public abstract class Decision extends AbstractStep { 054 055 private static final long serialVersionUID = -6835601038263238062L; 056 057 private Principal m_actor; 058 059 private int m_id; 060 061 private final Outcome m_defaultOutcome; 062 063 private final List<Fact> m_facts; 064 065 /** 066 * Constructs a new Decision for a required "actor" Principal, having a default Outcome. 067 * 068 * @param workflowId the parent workflow id to set 069 * @param workflowContext the parent workflow context to set 070 * @param messageKey the i18n message key that represents the message the actor will see 071 * @param actor the Principal (<em>e.g.</em>, a WikiPrincipal, Role, GroupPrincipal) who is required to select an appropriate Outcome 072 * @param defaultOutcome the Outcome that the user interface will recommend as the default choice 073 */ 074 public Decision( final int workflowId, final Map< String, Serializable > workflowContext, final String messageKey, final Principal actor, final Outcome defaultOutcome ) { 075 super( workflowId, workflowContext, messageKey ); 076 m_actor = actor; 077 m_defaultOutcome = defaultOutcome; 078 m_facts = new ArrayList<>(); 079 addSuccessor( defaultOutcome, null ); 080 } 081 082 /** 083 * Appends a Fact to the list of Facts associated with this Decision. 084 * 085 * @param fact the new fact to add 086 */ 087 public final void addFact( final Fact fact ) 088 { 089 m_facts.add( fact ); 090 } 091 092 /** 093 * <p> 094 * Sets this Decision's outcome, and restarts the parent Workflow if it is 095 * in the {@link Workflow#WAITING} state and this Decision is its currently 096 * active Step. Any checked WikiExceptions thrown by the workflow after 097 * re-start will be re-thrown to callers. 098 * </p> 099 * <p> 100 * This method cannot be invoked if the Decision is not the current Workflow 101 * step; all other invocations will throw an IllegalStateException. If the 102 * Outcome supplied to this method is one one of the Outcomes returned by 103 * {@link #getAvailableOutcomes()}, an IllegalArgumentException will be 104 * thrown. 105 * </p> 106 * 107 * @param outcome the Outcome of the Decision 108 * @param context wiki context of the Decision 109 * @throws WikiException if the act of restarting the Workflow throws an exception 110 */ 111 public void decide( final Outcome outcome, final Context context ) throws WikiException { 112 super.setOutcome( outcome ); 113 WikiEventEmitter.fireWorkflowEvent( this, WorkflowEvent.DQ_REMOVAL, context ); 114 } 115 116 /** 117 * Default implementation that always returns {@link Outcome#STEP_CONTINUE} if the current Outcome isn't a completion (which will be 118 * true if the {@link #decide(Outcome, Context)} method hasn't been executed yet. This method will also add the Decision to the associated 119 * DecisionQueue. 120 * 121 * @return the Outcome of the execution 122 * @throws WikiException never 123 */ 124 public Outcome execute( final Context context ) throws WikiException { 125 if( getOutcome().isCompletion() ) { 126 return getOutcome(); 127 } 128 129 // Put decision in the DecisionQueue 130 WikiEventEmitter.fireWorkflowEvent( this, WorkflowEvent.DQ_ADDITION ); 131 132 // Indicate we are waiting for user input 133 return Outcome.STEP_CONTINUE; 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 public final Principal getActor() { 140 return m_actor; 141 } 142 143 /** 144 * Returns the default or suggested outcome, which must be one of those returned by {@link #getAvailableOutcomes()}. This method is 145 * guaranteed to return a non-<code>null</code> Outcome. 146 * 147 * @return the default outcome. 148 */ 149 public Outcome getDefaultOutcome() { 150 return m_defaultOutcome; 151 } 152 153 /** 154 * Returns the Facts associated with this Decision, in the order in which they were added. 155 * 156 * @return the list of Facts 157 */ 158 public final List< Fact > getFacts() { 159 return Collections.unmodifiableList( m_facts ); 160 } 161 162 /** 163 * Returns the unique identifier for this Decision. Normally, this ID is programmatically assigned when the Decision is added to the 164 * DecisionQueue. 165 * 166 * @return the identifier 167 */ 168 public final int getId() { 169 return m_id; 170 } 171 172 /** 173 * Returns <code>true</code> if the Decision can be reassigned to another actor. This implementation always returns <code>true</code>. 174 * 175 * @return the result 176 */ 177 public boolean isReassignable() { 178 return true; 179 } 180 181 /** 182 * Reassigns the Decision to a new actor (that is, provide an outcome). If the Decision is not reassignable, this method throws an 183 * IllegalArgumentException. 184 * 185 * @param actor the actor to reassign the Decision to 186 */ 187 public final synchronized void reassign( final Principal actor ) { 188 if( isReassignable() ) { 189 m_actor = actor; 190 } else { 191 throw new IllegalArgumentException( "Decision cannot be reassigned." ); 192 } 193 } 194 195 /** 196 * Sets the unique identfier for this Decision. 197 * 198 * @param id the identifier 199 */ 200 public final void setId( final int id ) { 201 m_id = id; 202 } 203 204}