    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}