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