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