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     */
019    package org.apache.wiki.workflow;
020    
021    import java.io.Serializable;
022    import java.security.Principal;
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.LinkedList;
026    
027    import org.apache.wiki.api.exceptions.WikiException;
028    import 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     */
036    public 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    }