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    public final void addSuccessor( final Outcome outcome, final Step step ) {
096        m_successors.put( outcome, step );
097    }
098
099    /**
100     * {@inheritDoc}
101     */
102    public final Collection< Outcome > getAvailableOutcomes() {
103        final Set< Outcome > outcomes = m_successors.keySet();
104        return Collections.unmodifiableCollection( outcomes );
105    }
106
107    /**
108     * {@inheritDoc}
109     */
110    public final List< String > getErrors() {
111        return Collections.unmodifiableList( m_errors );
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    public abstract Outcome execute( Context ctx ) throws WikiException;
118
119    /**
120     * {@inheritDoc}
121     */
122    public abstract Principal getActor();
123
124    /**
125     * {@inheritDoc}
126     */
127    public final Date getEndTime() {
128        return m_end;
129    }
130
131    /**
132     * {@inheritDoc}
133     */
134    public final String getMessageKey() {
135        return m_key;
136    }
137
138    /**
139     * {@inheritDoc}
140     */
141    public final synchronized Outcome getOutcome() {
142        return m_outcome;
143    }
144
145    /**
146     * {@inheritDoc}
147     */
148    public final Date getStartTime() {
149        return m_start;
150    }
151
152    /**
153     * {@inheritDoc}
154     */
155    public final boolean isCompleted() {
156        return m_completed;
157    }
158
159    /**
160     * {@inheritDoc}
161     */
162    public final boolean isStarted() {
163        return m_started;
164    }
165
166    /**
167     * {@inheritDoc}
168     */
169    public final synchronized void setOutcome( final Outcome outcome ) {
170        // Is this an allowed Outcome?
171        if( !m_successors.containsKey( outcome ) ) {
172            if( !Outcome.STEP_CONTINUE.equals( outcome ) && !Outcome.STEP_ABORT.equals( outcome ) ) {
173                throw new IllegalArgumentException( "Outcome " + outcome.getMessageKey() + " is not supported for this Step." );
174            }
175        }
176
177        // Is this a "completion" outcome?
178        if( outcome.isCompletion() ) {
179            if( m_completed ) {
180                throw new IllegalStateException( "Step has already been marked complete; cannot set again." );
181            }
182            m_completed = true;
183            m_end = new Date( System.currentTimeMillis() );
184        }
185        m_outcome = outcome;
186    }
187
188    /**
189     * {@inheritDoc}
190     */
191    public final synchronized void start() throws WikiException {
192        if( m_started ) {
193            throw new IllegalStateException( "Step already started." );
194        }
195        m_started = true;
196        m_start = new Date( System.currentTimeMillis() );
197    }
198
199    /**
200     * {@inheritDoc}
201     */
202    public final Step getSuccessor( final Outcome outcome ) {
203        return m_successors.get( outcome );
204    }
205
206    // --------------------------Helper methods--------------------------
207
208    /**
209     * method that sets the parent Workflow id and context post-construction.
210     *
211     * @param workflowId the parent workflow id to set
212     * @param workflowContext the parent workflow context to set
213     */
214    public final synchronized void setWorkflow( final int workflowId, final Map< String, Serializable > workflowContext ) {
215        this.workflowId = workflowId;
216        this.workflowContext = workflowContext;
217    }
218
219    public int getWorkflowId() {
220        return workflowId;
221    }
222
223    public Map< String, Serializable > getWorkflowContext() {
224        return workflowContext;
225    }
226
227    /**
228     * Protected helper method that adds a String representing an error message to the Step's cached errors list.
229     *
230     * @param message the error message
231     */
232    protected final synchronized void addError( final String message ) {
233        m_errors.add( message );
234    }
235
236}