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; 023 024import java.io.Serializable; 025import java.security.Principal; 026import java.util.*; 027 028/** 029 * Abstract superclass that provides a complete implementation of most Step methods; subclasses need only implement {@link #execute(Context)} and 030 * {@link #getActor()}. 031 * 032 * @since 2.5 033 */ 034public abstract class AbstractStep implements Step { 035 036 private static final long serialVersionUID = 8635678679349653768L; 037 038 /** Timestamp of when the step started. */ 039 private Date m_start; 040 041 /** Timestamp of when the step ended. */ 042 private Date m_end; 043 044 private final String m_key; 045 046 private boolean m_completed; 047 048 private final Map< Outcome, Step > m_successors; 049 050 private int workflowId; 051 052 /** attribute map. */ 053 private Map< String, Serializable > workflowContext; 054 055 private Outcome m_outcome; 056 057 private final List<String> m_errors; 058 059 private boolean m_started; 060 061 /** 062 * Protected constructor that creates a new Step with a specified message key. After construction, the method 063 * {@link #setWorkflow(int, Map)} should be called. 064 * 065 * @param messageKey the Step's message key, such as {@code decision.editPageApproval}. By convention, the message prefix should 066 * be a lower-case version of the Step's type, plus a period (<em>e.g.</em>, {@code task.} and {@code decision.}). 067 */ 068 protected AbstractStep( final String messageKey ) { 069 m_started = false; 070 m_start = Step.TIME_NOT_SET; 071 m_completed = false; 072 m_end = Step.TIME_NOT_SET; 073 m_errors = new ArrayList<>(); 074 m_outcome = Outcome.STEP_CONTINUE; 075 m_key = messageKey; 076 m_successors = new LinkedHashMap<>(); 077 } 078 079 /** 080 * Constructs a new Step belonging to a specified Workflow and having a specified message key. 081 * 082 * @param workflowId the parent workflow id to set 083 * @param workflowContext the parent workflow context to set 084 * @param messageKey the Step's message key, such as {@code decision.editPageApproval}. By convention, the message prefix should 085 * be a lower-case version of the Step's type, plus a period (<em>e.g.</em>, {@code task.} and {@code decision.}). 086 */ 087 public AbstractStep( final int workflowId, final Map< String, Serializable > workflowContext, final String messageKey ) { 088 this( messageKey ); 089 setWorkflow( workflowId, workflowContext ); 090 } 091 092 /** 093 * {@inheritDoc} 094 */ 095 @Override 096 public final void addSuccessor(final Outcome outcome, final Step step ) { 097 m_successors.put( outcome, step ); 098 } 099 100 /** 101 * {@inheritDoc} 102 */ 103 @Override 104 public final Collection< Outcome > getAvailableOutcomes() { 105 final Set< Outcome > outcomes = m_successors.keySet(); 106 return Collections.unmodifiableCollection( outcomes ); 107 } 108 109 /** 110 * {@inheritDoc} 111 */ 112 @Override 113 public final List< String > getErrors() { 114 return Collections.unmodifiableList( m_errors ); 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 @Override 121 public abstract Outcome execute(Context ctx ) throws WikiException; 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public abstract Principal getActor(); 128 129 /** 130 * {@inheritDoc} 131 */ 132 @Override 133 public final Date getEndTime() { 134 return m_end; 135 } 136 137 /** 138 * {@inheritDoc} 139 */ 140 @Override 141 public final String getMessageKey() { 142 return m_key; 143 } 144 145 /** 146 * {@inheritDoc} 147 */ 148 @Override 149 public final synchronized Outcome getOutcome() { 150 return m_outcome; 151 } 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public final Date getStartTime() { 158 return m_start; 159 } 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 public final boolean isCompleted() { 166 return m_completed; 167 } 168 169 /** 170 * {@inheritDoc} 171 */ 172 @Override 173 public final boolean isStarted() { 174 return m_started; 175 } 176 177 /** 178 * {@inheritDoc} 179 */ 180 @Override 181 public final synchronized void setOutcome(final Outcome outcome ) { 182 // Is this an allowed Outcome? 183 if( !m_successors.containsKey( outcome ) ) { 184 if( !Outcome.STEP_CONTINUE.equals( outcome ) && !Outcome.STEP_ABORT.equals( outcome ) ) { 185 throw new IllegalArgumentException( "Outcome " + outcome.getMessageKey() + " is not supported for this Step." ); 186 } 187 } 188 189 // Is this a "completion" outcome? 190 if( outcome.isCompletion() ) { 191 if( m_completed ) { 192 throw new IllegalStateException( "Step has already been marked complete; cannot set again." ); 193 } 194 m_completed = true; 195 m_end = new Date( System.currentTimeMillis() ); 196 } 197 m_outcome = outcome; 198 } 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override 204 public final synchronized void start() throws WikiException { 205 if( m_started ) { 206 throw new IllegalStateException( "Step already started." ); 207 } 208 m_started = true; 209 m_start = new Date( System.currentTimeMillis() ); 210 } 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override 216 public final Step getSuccessor(final Outcome outcome ) { 217 return m_successors.get( outcome ); 218 } 219 220 // --------------------------Helper methods-------------------------- 221 222 /** 223 * method that sets the parent Workflow id and context post-construction. 224 * 225 * @param workflowId the parent workflow id to set 226 * @param workflowContext the parent workflow context to set 227 */ 228 @Override 229 public final synchronized void setWorkflow(final int workflowId, final Map< String, Serializable > workflowContext ) { 230 this.workflowId = workflowId; 231 this.workflowContext = workflowContext; 232 } 233 234 public int getWorkflowId() { 235 return workflowId; 236 } 237 238 public Map< String, Serializable > getWorkflowContext() { 239 return workflowContext; 240 } 241 242 /** 243 * Protected helper method that adds a String representing an error message to the Step's cached errors list. 244 * 245 * @param message the error message 246 */ 247 protected final synchronized void addError( final String message ) { 248 m_errors.add( message ); 249 } 250 251}