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 @Override 125 public Outcome execute(final Context context ) throws WikiException { 126 if( getOutcome().isCompletion() ) { 127 return getOutcome(); 128 } 129 130 // Put decision in the DecisionQueue 131 WikiEventEmitter.fireWorkflowEvent( this, WorkflowEvent.DQ_ADDITION ); 132 133 // Indicate we are waiting for user input 134 return Outcome.STEP_CONTINUE; 135 } 136 137 /** 138 * {@inheritDoc} 139 */ 140 @Override 141 public final Principal getActor() { 142 return m_actor; 143 } 144 145 /** 146 * Returns the default or suggested outcome, which must be one of those returned by {@link #getAvailableOutcomes()}. This method is 147 * guaranteed to return a non-<code>null</code> Outcome. 148 * 149 * @return the default outcome. 150 */ 151 public Outcome getDefaultOutcome() { 152 return m_defaultOutcome; 153 } 154 155 /** 156 * Returns the Facts associated with this Decision, in the order in which they were added. 157 * 158 * @return the list of Facts 159 */ 160 public final List< Fact > getFacts() { 161 return Collections.unmodifiableList( m_facts ); 162 } 163 164 /** 165 * Returns the unique identifier for this Decision. Normally, this ID is programmatically assigned when the Decision is added to the 166 * DecisionQueue. 167 * 168 * @return the identifier 169 */ 170 public final int getId() { 171 return m_id; 172 } 173 174 /** 175 * Returns <code>true</code> if the Decision can be reassigned to another actor. This implementation always returns <code>true</code>. 176 * 177 * @return the result 178 */ 179 public boolean isReassignable() { 180 return true; 181 } 182 183 /** 184 * Reassigns the Decision to a new actor (that is, provide an outcome). If the Decision is not reassignable, this method throws an 185 * IllegalArgumentException. 186 * 187 * @param actor the actor to reassign the Decision to 188 */ 189 public final synchronized void reassign( final Principal actor ) { 190 if( isReassignable() ) { 191 m_actor = actor; 192 } else { 193 throw new IllegalArgumentException( "Decision cannot be reassigned." ); 194 } 195 } 196 197 /** 198 * Sets the unique identfier for this Decision. 199 * 200 * @param id the identifier 201 */ 202 public final void setId( final int id ) { 203 m_id = id; 204 } 205 206}