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.core.Session;
023import org.apache.wiki.api.exceptions.WikiException;
024import org.apache.wiki.event.WikiEventEmitter;
025import org.apache.wiki.event.WorkflowEvent;
026
027import java.io.Serializable;
028import java.security.Principal;
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.LinkedList;
032import java.util.concurrent.atomic.AtomicInteger;
033
034
035/**
036 * Keeps a queue of pending Decisions that need to be acted on by named Principals.
037 *
038 * @since 2.5
039 */
040public class DecisionQueue implements Serializable {
041
042    private static final long serialVersionUID = -7172912793410302533L;
043
044    private final LinkedList< Decision > m_queue = new LinkedList<>();
045
046    private final AtomicInteger next = new AtomicInteger( 1_000 );
047
048    /** Constructs a new DecisionQueue. */
049    public DecisionQueue() {
050    }
051
052    /**
053     * Adds a Decision to the DecisionQueue; also sets the Decision's unique identifier.
054     *
055     * @param decision the Decision to add
056     */
057    protected synchronized void add( final Decision decision ) {
058        m_queue.addLast( decision );
059        decision.setId( next.getAndIncrement() );
060    }
061
062    /**
063     * Protected method that returns all pending Decisions in the queue, in  order of submission. If no Decisions are pending, this
064     * method returns a zero-length array.
065     *
066     * @return the pending decisions 
067     */
068    protected Decision[] decisions() {
069        return m_queue.toArray( new Decision[0] );
070    }
071
072    /**
073     * Protected method that removes a Decision from the queue.
074     *
075     * @param decision the decision to remove
076     */
077    protected synchronized void remove( final Decision decision ) {
078        m_queue.remove( decision );
079    }
080
081    /**
082     * Returns a Collection representing the current Decisions that pertain to a users's Session. The Decisions are obtained by iterating
083     * through the Session's Principals and selecting those Decisions whose {@link Decision#getActor()} value match. If the session
084     * is not authenticated, this method returns an empty Collection.
085     *
086     * @param session the wiki session
087     * @return the collection of Decisions, which may be empty
088     */
089    public Collection<Decision> getActorDecisions( final Session session ) {
090        final ArrayList< Decision > decisions = new ArrayList<>();
091        if( session.isAuthenticated() ) {
092            final Principal[] principals = session.getPrincipals();
093            final Principal[] rolePrincipals = session.getRoles();
094            for( final Decision decision : m_queue ) {
095                // Iterate through the Principal set
096                for( final Principal principal : principals ) {
097                    if( principal.equals( decision.getActor() ) ) {
098                        decisions.add( decision );
099                    }
100                }
101                // Iterate through the Role set
102                for( final Principal principal : rolePrincipals ) {
103                    if( principal.equals( decision.getActor() ) ) {
104                        decisions.add( decision );
105                    }
106                }
107            }
108        }
109        return decisions;
110    }
111
112    /**
113     * Attempts to complete a Decision by calling {@link Decision#decide(Outcome, Context)}. This will cause the Step immediately following the
114     * Decision (if any) to start. If the decision completes successfully, this method also removes the completed decision from the queue.
115     *
116     * @param decision the Decision for which the Outcome will be supplied
117     * @param outcome the Outcome of the Decision
118     * @throws WikiException if the succeeding Step cannot start for any reason
119     */
120    public void decide( final Decision decision, final Outcome outcome, final Context context ) throws WikiException {
121        decision.decide( outcome, context );
122        if ( decision.isCompleted() ) {
123            remove( decision );
124        }
125
126        WikiEventEmitter.fireWorkflowEvent( decision, WorkflowEvent.DQ_DECIDE );
127    }
128
129    /**
130     * Reassigns the owner of the Decision to a new owner. Under the covers, this method calls {@link Decision#reassign(Principal)}.
131     *
132     * @param decision the Decision to reassign
133     * @param owner the new owner
134     * @throws WikiException never
135     */
136    public synchronized void reassign( final Decision decision, final Principal owner ) throws WikiException {
137        if( decision.isReassignable() ) {
138            decision.reassign( owner );
139
140            WikiEventEmitter.fireWorkflowEvent( decision, WorkflowEvent.DQ_REASSIGN );
141            return;
142        }
143        throw new IllegalStateException( "Reassignments not allowed for this decision." );
144    }
145
146}