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}