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