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