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