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 TODO: explore whether this method could be
071     *         made protected
072     */
073    protected Decision[] decisions()
074    {
075        return m_queue.toArray( new Decision[m_queue.size()] );
076    }
077
078    /**
079     * Protected method that removes a Decision from the queue.
080     * @param decision the decision to remove
081     */
082    protected synchronized void remove(Decision decision)
083    {
084        m_queue.remove( decision );
085    }
086
087    /**
088     * Returns a Collection representing the current Decisions that pertain to a
089     * users's WikiSession. The Decisions are obtained by iterating through the
090     * WikiSession's Principals and selecting those Decisions whose
091     * {@link Decision#getActor()} value match. If the wiki session is not
092     * authenticated, this method returns an empty Collection.
093     *
094     * @param session
095     *            the wiki session
096     * @return the collection of Decisions, which may be empty
097     */
098    public Collection getActorDecisions(WikiSession session)
099    {
100        ArrayList<Decision> decisions = new ArrayList<Decision>();
101        if ( session.isAuthenticated() )
102        {
103            Principal[] principals = session.getPrincipals();
104            Principal[] rolePrincipals = session.getRoles();
105            for ( Decision decision : m_queue )
106            {
107                // Iterate through the Principal set
108                for ( Principal principal : principals )
109                {
110                    if ( principal.equals( decision.getActor() ) )
111                    {
112                        decisions.add( decision );
113                    }
114                }
115                // Iterate through the Role set
116                for ( Principal principal : rolePrincipals )
117                {
118                    if ( principal.equals( decision.getActor() ) )
119                    {
120                        decisions.add( decision );
121                    }
122                }
123            }
124        }
125        return decisions;
126    }
127
128    /**
129     * Attempts to complete a Decision by calling
130     * {@link Decision#decide(Outcome)}. This will cause the Step immediately
131     * following the Decision (if any) to start. If the decision completes
132     * successfully, this method also removes the completed decision from the
133     * queue.
134     *
135     * @param decision the Decision for which the Outcome will be supplied
136     * @param outcome the Outcome of the Decision
137     * @throws WikiException if the succeeding Step cannot start
138     * for any reason
139     */
140    public void decide( Decision decision, Outcome outcome ) throws WikiException
141    {
142        decision.decide( outcome );
143        if ( decision.isCompleted() )
144        {
145            remove( decision );
146        }
147
148        // TODO: We should fire an event indicating the Outcome, and whether the
149        // Decision completed successfully
150    }
151
152    /**
153     * Reassigns the owner of the Decision to a new owner. Under the covers,
154     * this method calls {@link Decision#reassign(Principal)}.
155     *
156     * @param decision the Decision to reassign
157     * @param owner the new owner
158     * @throws WikiException never
159     */
160    public synchronized void reassign(Decision decision, Principal owner) throws WikiException
161    {
162        if (decision.isReassignable())
163        {
164            decision.reassign( owner );
165
166            // TODO: We should fire an event indicating the reassignment
167            return;
168        }
169        throw new IllegalStateException( "Reassignments not allowed for this decision." );
170    }
171
172    /**
173     * Returns the next available unique identifier, which is subsequently
174     * incremented.
175     *
176     * @return the id
177     */
178    private synchronized int nextId()
179    {
180        int current = m_next;
181        m_next++;
182        return current;
183    }
184
185}