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