View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.actionrequest.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.config.CoreConfigHelper;
21  import org.kuali.rice.core.api.config.property.ConfigContext;
22  import org.kuali.rice.core.api.criteria.CountFlag;
23  import org.kuali.rice.core.api.criteria.Predicate;
24  import org.kuali.rice.core.api.criteria.QueryByCriteria;
25  import org.kuali.rice.core.api.exception.RiceRuntimeException;
26  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
27  import org.kuali.rice.kew.actionitem.ActionItem;
28  import org.kuali.rice.kew.actionlist.service.ActionListService;
29  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
30  import org.kuali.rice.kew.actionrequest.Recipient;
31  import org.kuali.rice.kew.actionrequest.dao.ActionRequestDAO;
32  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
33  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
34  import org.kuali.rice.kew.actiontaken.service.ActionTakenService;
35  import org.kuali.rice.kew.api.KewApiConstants;
36  import org.kuali.rice.kew.api.KewApiServiceLocator;
37  import org.kuali.rice.kew.api.action.ActionRequestPolicy;
38  import org.kuali.rice.kew.api.action.ActionRequestStatus;
39  import org.kuali.rice.kew.api.action.RecipientType;
40  import org.kuali.rice.kew.api.document.DocumentRefreshQueue;
41  import org.kuali.rice.kew.doctype.bo.DocumentType;
42  import org.kuali.rice.kew.engine.ActivationContext;
43  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
44  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
45  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
46  import org.kuali.rice.kew.routemodule.RouteModule;
47  import org.kuali.rice.kew.service.KEWServiceLocator;
48  import org.kuali.rice.kew.util.FutureRequestDocumentStateManager;
49  import org.kuali.rice.kew.util.PerformanceLogger;
50  import org.kuali.rice.kew.util.ResponsibleParty;
51  import org.kuali.rice.kim.api.group.Group;
52  import org.kuali.rice.kim.api.identity.principal.Principal;
53  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
54  import org.kuali.rice.krad.data.DataObjectService;
55  import org.kuali.rice.krad.data.PersistenceOption;
56  import org.kuali.rice.krad.util.KRADConstants;
57  
58  import java.sql.Timestamp;
59  import java.util.ArrayList;
60  import java.util.Collection;
61  import java.util.Collections;
62  import java.util.HashMap;
63  import java.util.HashSet;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Set;
67  
68  import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
69  
70  /**
71   * Default implementation of the {@link ActionRequestService}.
72   *
73   * @author Kuali Rice Team (rice.collab@kuali.org)
74   */
75  public class ActionRequestServiceImpl implements ActionRequestService {
76  
77      private static final Logger LOG = Logger.getLogger(ActionRequestServiceImpl.class);
78  
79      private static final String STATUS = "status";
80      private static final String DOCUMENT_ID = "documentId";
81      private static final String CURRENT_INDICATOR = "currentIndicator";
82      private static final String PARENT_ACTION_REQUEST = "parentActionRequest";
83      private static final String ACTION_REQUESTED = "actionRequested";
84      private static final String ROUTE_NODE_INSTANCE_ID = "nodeInstance.routeNodeInstanceId";
85      private static final String GROUP_ID = "groupId";
86      private static final String ACTION_TAKEN_ID = "actionTaken.actionTakenId";
87      private static final String RECIPIENT_TYPE_CD = "recipientTypeCd";
88      private static final String PRINCIPAL_ID = "principalId";
89  
90      private DataObjectService dataObjectService;
91      private ActionRequestDAO actionRequestDAO;
92  
93      @Override
94      public ActionRequestValue findByActionRequestId(String actionRequestId) {
95          return getDataObjectService().find(ActionRequestValue.class, actionRequestId);
96      }
97  
98      @Override
99      public Map<String, String> getActionsRequested(DocumentRouteHeaderValue routeHeader, String principalId, boolean completeAndApproveTheSame) {
100     	return getActionsRequested(principalId, routeHeader.getActionRequests(), completeAndApproveTheSame);
101     }
102 
103     /**
104      * Returns a Map of actions that are requested for the given principalId in the given list of action requests.
105      */
106     protected Map<String, String> getActionsRequested(String principalId, List<ActionRequestValue> actionRequests, boolean completeAndApproveTheSame) {
107     	Map<String, String> actionsRequested = new HashMap<String, String>();
108         actionsRequested.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, "false");
109         actionsRequested.put(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, "false");
110         actionsRequested.put(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, "false");
111         actionsRequested.put(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, "false");
112     	String topActionRequested = KewApiConstants.ACTION_REQUEST_FYI_REQ;
113         for (ActionRequestValue actionRequest : actionRequests) {
114             // we are getting the full list of requests here, so no need to look at role requests, if we did this then
115             // we could get a "false positive" for "all approve" roles where only part of the request graph is marked
116             // as "done"
117             if (!RecipientType.ROLE.getCode().equals(actionRequest.getRecipientTypeCd()) &&
118                     actionRequest.isRecipientRoutedRequest(principalId) && actionRequest.isActive()) {
119                 int actionRequestComparison = ActionRequestValue.compareActionCode(actionRequest.getActionRequested(), topActionRequested, completeAndApproveTheSame);
120                 if (actionRequest.isFYIRequest() && actionRequestComparison >= 0) {
121                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, "true");
122                 } else if (actionRequest.isAcknowledgeRequest() && actionRequestComparison >= 0) {
123                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, "true");
124                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, "false");
125                     topActionRequested = actionRequest.getActionRequested();
126                 } else if (actionRequest.isApproveRequest() && actionRequestComparison >= 0) {
127                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, "true");
128                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, "false");
129                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, "false");
130                     topActionRequested = actionRequest.getActionRequested();
131                 } else if (actionRequest.isCompleteRequst() && actionRequestComparison >= 0) {
132                 	actionsRequested.put(KewApiConstants.ACTION_REQUEST_COMPLETE_REQ, "true");
133                 	actionsRequested.put(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, "false");
134                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, "false");
135                     actionsRequested.put(KewApiConstants.ACTION_REQUEST_FYI_REQ, "false");
136                 	if (completeAndApproveTheSame) {
137                 		actionsRequested.put(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, "true");
138                 	}
139                     topActionRequested = actionRequest.getActionRequested();
140                 }
141             }
142         }
143         return actionsRequested;
144     }
145 
146     @Override
147     public ActionRequestValue initializeActionRequestGraph(ActionRequestValue actionRequest,
148             DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
149         if (actionRequest.getParentActionRequest() != null) {
150             LOG.warn("-->A non parent action request from doc " + document.getDocumentId());
151             actionRequest = KEWServiceLocator.getActionRequestService().getRoot(actionRequest);
152         }
153         propagatePropertiesToRequestGraph(actionRequest, document, nodeInstance);
154         return actionRequest;
155     }
156 
157     private void propagatePropertiesToRequestGraph(ActionRequestValue actionRequest, DocumentRouteHeaderValue document,
158             RouteNodeInstance nodeInstance) {
159         setPropertiesToRequest(actionRequest, document, nodeInstance);
160         for (ActionRequestValue actionRequestValue : actionRequest.getChildrenRequests())
161         {
162             propagatePropertiesToRequestGraph(actionRequestValue, document, nodeInstance);
163         }
164     }
165 
166     private void setPropertiesToRequest(ActionRequestValue actionRequest, DocumentRouteHeaderValue document,
167             RouteNodeInstance nodeInstance) {
168         actionRequest.setDocumentId(document.getDocumentId());
169         actionRequest.setDocVersion(document.getDocVersion());
170         actionRequest.setRouteLevel(document.getDocRouteLevel());
171         actionRequest.setNodeInstance(nodeInstance);
172         actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
173     }
174 
175 
176 
177     @Override
178     public List<ActionRequestValue> activateRequests(List<ActionRequestValue> actionRequests) {
179         return activateRequests(actionRequests, new ActivationContext(!ActivationContext.CONTEXT_IS_SIMULATION));
180     }
181 
182     @Override
183     public List<ActionRequestValue> activateRequests(List<ActionRequestValue> actionRequests, boolean simulate) {
184         return activateRequests(actionRequests, new ActivationContext(simulate));
185     }
186 
187     @Override
188     public List<ActionRequestValue> activateRequests(List<ActionRequestValue> actionRequests, ActivationContext activationContext) {
189         if (actionRequests == null) {
190             return new ArrayList<ActionRequestValue>();
191         }
192         PerformanceLogger performanceLogger = null;
193         if ( LOG.isInfoEnabled() ) {
194         	performanceLogger = new PerformanceLogger();
195         }
196         activationContext.setGeneratedActionItems(new ArrayList<ActionItem>());
197         // first step, we are going to save the action requests, since we are using JPA on the backend doing a save will
198         // either persist or merge this action request and all of it's children into the current persistence context so
199         // that subsequent saves won't be required and we won't have to return and reset action requests from every
200         // internal method because we will *know* it has already been merged into the persistence contest
201         if (!activationContext.isSimulation()) {
202             actionRequests = saveActionRequests(actionRequests);
203         }
204 
205         activateRequestsInternal(actionRequests, activationContext);
206         if (!activationContext.isSimulation()) {
207             KEWServiceLocator.getNotificationService().notify(ActionItem.to(activationContext.getGeneratedActionItems()));
208         }
209         if ( LOG.isInfoEnabled() ) {
210         	performanceLogger.log("Time to " + (activationContext.isSimulation() ? "simulate activation of " : "activate ")
211         			+ actionRequests.size() + " action requests.");
212         }
213         if ( LOG.isDebugEnabled() ) {
214         	LOG.debug("Generated " + activationContext.getGeneratedActionItems().size() + " action items.");
215         }
216         return actionRequests;
217     }
218 
219     @Override
220     public ActionRequestValue activateRequest(ActionRequestValue actionRequest) {
221         return activateRequests(Collections.singletonList(actionRequest), new ActivationContext(!ActivationContext.CONTEXT_IS_SIMULATION)).get(0);
222     }
223 
224     @Override
225     public ActionRequestValue activateRequest(ActionRequestValue actionRequest, boolean simulate) {
226         return activateRequests(Collections.singletonList(actionRequest), new ActivationContext(simulate)).get(0);
227     }
228 
229     @Override
230     public ActionRequestValue activateRequest(ActionRequestValue actionRequest, ActivationContext activationContext) {
231         return activateRequests(Collections.singletonList(actionRequest), activationContext).get(0);
232     }
233 
234     @Override
235     public ActionRequestValue activateRequestNoNotification(ActionRequestValue actionRequest, ActivationContext activationContext) {
236         activationContext.setGeneratedActionItems(new ArrayList<ActionItem>());
237         actionRequest = saveActionRequest(actionRequest, activationContext.isSimulation());
238         activateRequestInternal(actionRequest, activationContext);
239         return actionRequest;
240     }
241 
242     /**
243      * Internal helper method for activating a Collection of action requests and their children. Maintains an accumulator
244      * for generated action items.
245      *
246      * <p>IMPORTANT! This method assumes that the action requests given have already been "merged" into the
247      * JPA persistence context.</p>
248      */
249     private void activateRequestsInternal(List<ActionRequestValue> actionRequests, ActivationContext activationContext) {
250         if (actionRequests != null) {
251             for (ActionRequestValue actionRequest : actionRequests) {
252                 activateRequestInternal(actionRequest, activationContext);
253             }
254         }
255     }
256 
257     /**
258      * Internal helper method for activating a single action requests and it's children. Maintains an accumulator for
259      * generated action items.
260      *
261      * <p>IMPORTANT! This method assumes that the action request given has already been "merged" into the
262      * JPA persistence context.</p>
263      */
264     private void activateRequestInternal(ActionRequestValue actionRequest, ActivationContext activationContext) {
265         PerformanceLogger performanceLogger = null;
266         if ( LOG.isInfoEnabled() ) {
267         	performanceLogger = new PerformanceLogger();
268         }
269         if (actionRequest == null || actionRequest.isActive() || actionRequest.isDeactivated()) {
270             return;
271         }
272         processResponsibilityId(actionRequest);
273         if (deactivateOnActionAlreadyTaken(actionRequest, activationContext)) {
274             return;
275         }
276         if (deactivateOnInactiveGroup(actionRequest, activationContext)) {
277             return;
278         }
279         if (deactivateOnEmptyGroup(actionRequest, activationContext)) {
280         	return;
281         }
282         actionRequest.setStatus(ActionRequestStatus.ACTIVATED.getCode());
283         if (!activationContext.isSimulation()) {
284             activationContext.getGeneratedActionItems().addAll(generateActionItems(actionRequest, activationContext));
285         }
286         activateRequestsInternal(actionRequest.getChildrenRequests(), activationContext);
287         activateRequestInternal(actionRequest.getParentActionRequest(), activationContext);
288         if ( LOG.isInfoEnabled() ) {
289         	if (activationContext.isSimulation()) {
290                 performanceLogger.log("Time to simulate activation of request.");
291 	        } else {
292 	            performanceLogger.log("Time to activate action request with id " + actionRequest.getActionRequestId());
293 	        }
294         }
295     }
296 
297     /**
298      * Generates ActionItems for the given ActionRequest and returns the List of generated Action Items.
299      *
300      * @return the List of generated ActionItems
301      */
302     private List<ActionItem> generateActionItems(ActionRequestValue actionRequest, ActivationContext activationContext) {
303     	if ( LOG.isDebugEnabled() ) {
304     		LOG.debug("generating the action items for request " + actionRequest.getActionRequestId());
305     	}
306         List<ActionItem> actionItems = new ArrayList<ActionItem>();
307         if (!actionRequest.isPrimaryDelegator()) {
308             if (actionRequest.isGroupRequest()) {
309                 List<String> principalIds =  KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
310                 actionItems.addAll(createActionItemsForPrincipals(actionRequest, principalIds));
311             } else if (actionRequest.isUserRequest()) {
312                 ActionItem actionItem = getActionListService().createActionItemForActionRequest(actionRequest);
313                 actionItems.add(actionItem);
314             }
315         }
316         List<ActionItem> actionItemsToReturn = new ArrayList<ActionItem>(actionItems.size());
317         if (!activationContext.isSimulation()) {
318             for (ActionItem actionItem: actionItems) {
319             	if ( LOG.isDebugEnabled() ) {
320             		LOG.debug("Saving action item: " + actionItems);
321             	}
322                 actionItem = getActionListService().saveActionItem(actionItem);
323                 actionItemsToReturn.add(actionItem);
324             }
325         } else {
326         	actionRequest.getSimulatedActionItems().addAll(actionItems);
327         	actionItemsToReturn.addAll(actionItems);
328         }
329         return actionItemsToReturn;
330     }
331 
332     private List<ActionItem> createActionItemsForPrincipals(ActionRequestValue actionRequest, List<String> principalIds) {
333         List<ActionItem> actionItems = new ArrayList<ActionItem>();
334         for (String principalId: principalIds) {
335 
336             ActionItem actionItem = getActionListService().createActionItemForActionRequest(actionRequest);
337             actionItem.setPrincipalId(principalId);
338             actionItem.setRoleName(actionRequest.getQualifiedRoleName());
339 
340             //KULRICE-3307 Prevent workflow from attempting to activate requests for null principals
341             String ignoreUnknownPrincipalIdsValue = ConfigContext.getCurrentContextConfig().getProperty(KewApiConstants.WORKFLOW_ACTION_IGNORE_UNKOWN_PRINCIPAL_IDS);
342             boolean ignoreUnknownPrincipalIds = Boolean.parseBoolean(ignoreUnknownPrincipalIdsValue);
343 
344             if(principalId==null && ignoreUnknownPrincipalIds)
345             {
346                 LOG.warn("Ignoring action item with actionRequestID of " + actionRequest.getActionRequestId()  + " due to null principalId.");
347             }
348             else
349             {
350                 if(principalId==null)
351                 {
352                     IllegalArgumentException e = new IllegalArgumentException("Exception thrown when trying to add action item with null principalId");
353                     LOG.error(e);
354                     throw e;
355                 }
356                 else
357                 {
358                     actionItems.add(actionItem);
359                 }
360             }
361         }
362         return actionItems;
363     }
364 
365     private void processResponsibilityId(ActionRequestValue actionRequest) {
366     	if (actionRequest.getResolveResponsibility()) {
367 	        String responsibilityId = actionRequest.getResponsibilityId();
368 	        try {
369 	            RouteModule routeModule = KEWServiceLocator.getRouteModuleService().findRouteModule(actionRequest);
370 	            if (responsibilityId != null && actionRequest.isRouteModuleRequest()) {
371 	            	if ( LOG.isDebugEnabled() ) {
372 	            		LOG.debug("Resolving responsibility id for action request id=" + actionRequest.getActionRequestId()
373 	                        + " and responsibility id=" + actionRequest.getResponsibilityId());
374 	            	}
375 	                ResponsibleParty responsibleParty = routeModule.resolveResponsibilityId(actionRequest.getResponsibilityId());
376 	                if (responsibleParty == null) {
377 	                    return;
378 	                }
379 	                if (responsibleParty.getPrincipalId() != null) {
380 	                    Principal user = KimApiServiceLocator.getIdentityService()
381 	                            .getPrincipal(responsibleParty.getPrincipalId());
382 	                    actionRequest.setPrincipalId(user.getPrincipalId());
383 	                } else if (responsibleParty.getGroupId() != null) {
384 	                	actionRequest.setGroupId(responsibleParty.getGroupId());
385 	                } else if (responsibleParty.getRoleName() != null) {
386 	                    actionRequest.setRoleName(responsibleParty.getRoleName());
387 	                }
388 	            }
389 	        } catch (Exception e) {
390 	            LOG.error("Exception thrown when trying to resolve responsibility id " + responsibilityId, e);
391 	            throw new RuntimeException(e);
392 	        }
393     	}
394     }
395 
396     protected boolean deactivateOnActionAlreadyTaken(ActionRequestValue actionRequestToActivate,
397             ActivationContext activationContext) {
398 
399         FutureRequestDocumentStateManager futureRequestStateMngr = null;
400 
401         if (actionRequestToActivate.isGroupRequest()) {
402             futureRequestStateMngr = new FutureRequestDocumentStateManager(actionRequestToActivate.getRouteHeader(), actionRequestToActivate.getGroup());
403         } else if (actionRequestToActivate.isUserRequest()) {
404             futureRequestStateMngr = new FutureRequestDocumentStateManager(actionRequestToActivate.getRouteHeader(), actionRequestToActivate.getPrincipalId());
405         } else {
406             return false;
407         }
408 
409         if (futureRequestStateMngr.isReceiveFutureRequests()) {
410             return false;
411         }
412         if (!actionRequestToActivate.getForceAction() || futureRequestStateMngr.isDoNotReceiveFutureRequests()) {
413             ActionTakenValue previousActionTaken = null;
414             if (!activationContext.isSimulation()) {
415                 previousActionTaken = getActionTakenService().getPreviousAction(actionRequestToActivate);
416             } else {
417                 previousActionTaken = getActionTakenService().getPreviousAction(actionRequestToActivate,
418                         activationContext.getSimulatedActionsTaken());
419             }
420             if (previousActionTaken != null) {
421                 if ( LOG.isDebugEnabled() ) {
422                 	LOG.debug("found a satisfying action taken so setting this request done.  Action Request Id "
423                             + actionRequestToActivate.getActionRequestId());
424                 }
425                 // set up the delegation for an action taken if this is a delegate request and the delegate has
426                 // already taken action.
427                 if (!previousActionTaken.isForDelegator() && actionRequestToActivate.getParentActionRequest() != null) {
428                     previousActionTaken.setDelegator(actionRequestToActivate.getParentActionRequest().getRecipient());
429                     if (!activationContext.isSimulation()) {
430                         previousActionTaken = getActionTakenService().saveActionTaken(previousActionTaken);
431                     }
432                 }
433                 deactivateRequest(previousActionTaken, actionRequestToActivate, null, activationContext);
434                 return true;
435             }
436         }
437         if ( LOG.isDebugEnabled() ) {
438         	LOG.debug("Forcing action for action request " + actionRequestToActivate.getActionRequestId());
439         }
440         return false;
441     }
442 
443     /**
444      * Checks if the action request which is being activated has a group with no members.  If this is the case then it will immediately
445      * initiate de-activation on the request since a group with no members will result in no action items being generated so should be
446      * effectively skipped.
447      */
448     protected boolean deactivateOnEmptyGroup(ActionRequestValue actionRequestToActivate, ActivationContext activationContext) {
449     	if (actionRequestToActivate.isGroupRequest()) {
450     		 if (KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequestToActivate.getGroup().getId()).isEmpty()) {
451     			 deactivateRequest(null, actionRequestToActivate, null, activationContext);
452     			 return true;
453          	}
454     	}
455     	return false;
456     }
457 
458     /**
459      * Checks if the action request which is being activated is being assigned to an inactive group.  If this is the case and if the FailOnInactiveGroup
460      * policy is set to false then it will immediately initiate de-activation on the request
461      */
462     protected boolean deactivateOnInactiveGroup(ActionRequestValue actionRequestToActivate, ActivationContext activationContext) {
463         if (actionRequestToActivate.isGroupRequest()) {
464             if (!actionRequestToActivate.getGroup().isActive() && !actionRequestToActivate.getRouteHeader().getDocumentType().getFailOnInactiveGroup().getPolicyValue()) {
465                 deactivateRequest(null, actionRequestToActivate, null, activationContext);
466                 return true;
467             }
468         }
469         return false;
470     }
471 
472     @Override
473     public ActionRequestValue deactivateRequest(ActionTakenValue actionTaken, ActionRequestValue actionRequest) {
474         return deactivateRequest(actionTaken, actionRequest, null, new ActivationContext(!ActivationContext.CONTEXT_IS_SIMULATION));
475     }
476 
477     @Override
478     public ActionRequestValue deactivateRequest(ActionTakenValue actionTaken, ActionRequestValue actionRequest,
479             ActivationContext activationContext) {
480         return deactivateRequest(actionTaken, actionRequest, null, activationContext);
481     }
482 
483     @Override
484     public List<ActionRequestValue> deactivateRequests(ActionTakenValue actionTaken, List<ActionRequestValue> actionRequests) {
485         return deactivateRequests(actionTaken, actionRequests, null,
486                 new ActivationContext(!ActivationContext.CONTEXT_IS_SIMULATION));
487     }
488 
489     @Override
490     public List<ActionRequestValue> deactivateRequests(ActionTakenValue actionTaken, List<ActionRequestValue> actionRequests, boolean simulate) {
491         return deactivateRequests(actionTaken, actionRequests, null, new ActivationContext(simulate));
492     }
493 
494     @Override
495     public List<ActionRequestValue> deactivateRequests(ActionTakenValue actionTaken, List<ActionRequestValue> actionRequests, ActivationContext activationContext) {
496         return deactivateRequests(actionTaken, actionRequests, null, activationContext);
497     }
498 
499     private List<ActionRequestValue> deactivateRequests(ActionTakenValue actionTaken, List<ActionRequestValue> actionRequests,
500             ActionRequestValue deactivationRequester, ActivationContext activationContext) {
501         List<ActionRequestValue> deactivatedRequests = new ArrayList<ActionRequestValue>();
502         if (actionRequests != null) {
503             for (ActionRequestValue actionRequest : actionRequests) {
504                 deactivatedRequests.add(deactivateRequest(actionTaken, actionRequest, deactivationRequester, activationContext));
505             }
506         }
507         return deactivatedRequests;
508     }
509 
510     private ActionRequestValue deactivateRequest(ActionTakenValue actionTaken, ActionRequestValue actionRequest,
511             ActionRequestValue deactivationRequester, ActivationContext activationContext) {
512         if (actionRequest == null || actionRequest.isDeactivated()
513                 || haltForAllApprove(actionRequest, deactivationRequester)) {
514             return actionRequest;
515         }
516         actionRequest.setStatus(ActionRequestStatus.DONE.getCode());
517         actionRequest.setActionTaken(actionTaken);
518 
519         if (!activationContext.isSimulation()) {
520             if (actionTaken != null) {
521                 // only add it if it's not null and we aren't in a simulation context, if we are in simulation mode, we
522                 // don't want to modify any action requests, lest they get saved by a JPA flush later!
523                 actionTaken.getActionRequests().add(actionRequest);
524             }
525             actionRequest = getDataObjectService().save(actionRequest);
526             deleteActionItems(actionRequest, true);
527         }
528         actionRequest.setChildrenRequests(deactivateRequests(actionTaken, actionRequest.getChildrenRequests(), actionRequest, activationContext));
529         actionRequest.setParentActionRequest(deactivateRequest(actionTaken, actionRequest.getParentActionRequest(), actionRequest, activationContext));
530         return actionRequest;
531     }
532 
533     /**
534      * Returns true if we are dealing with an 'All Approve' request, the requester of the deactivation is a child of the
535      * 'All Approve' request, and all of the children have not been deactivated. If all of the children are already
536      * deactivated or a non-child request initiated deactivation, then this method returns false. false otherwise.
537      */
538     private boolean haltForAllApprove(ActionRequestValue actionRequest, ActionRequestValue deactivationRequester) {
539         if (ActionRequestPolicy.ALL.getCode().equals(actionRequest.getApprovePolicy())
540                 && actionRequest.hasChild(deactivationRequester)) {
541             for (ActionRequestValue childRequest : actionRequest.getChildrenRequests()) {
542                 if (!childRequest.isDeactivated()) {
543                     return true;
544                 }
545             }
546         }
547         return false;
548     }
549 
550     @Override
551     public List<ActionRequestValue> getRootRequests(Collection<ActionRequestValue> actionRequests) {
552     	Set<ActionRequestValue> unsavedRequests = new HashSet<ActionRequestValue>();
553     	Map<String, ActionRequestValue> requestMap = new HashMap<String, ActionRequestValue>();
554     	for (ActionRequestValue actionRequest1 : actionRequests) {
555     		ActionRequestValue actionRequest = actionRequest1;
556     		ActionRequestValue rootRequest = getRoot(actionRequest);
557     		if (rootRequest.getActionRequestId() != null) {
558     			requestMap.put(rootRequest.getActionRequestId(), rootRequest);
559     		} else {
560     			unsavedRequests.add(rootRequest);
561     		}
562     	}
563     	List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
564     	requests.addAll(requestMap.values());
565     	requests.addAll(unsavedRequests);
566     	return requests;
567     }
568 
569     @Override
570     public ActionRequestValue getRoot(ActionRequestValue actionRequest) {
571         if (actionRequest == null) {
572             return null;
573         }
574         if (actionRequest.getParentActionRequest() != null) {
575             return getRoot(actionRequest.getParentActionRequest());
576         }
577         return actionRequest;
578     }
579 
580     /**
581      * Returns all pending requests for a given routing identity
582      * @param documentId the id of the document header being routed
583      * @return a List of all pending ActionRequestValues for the document
584      */
585     @Override
586     public List<ActionRequestValue> findAllPendingRequests(String documentId) {
587         return findByStatusAndDocId(ActionRequestStatus.ACTIVATED.getCode(), documentId);
588     }
589 
590     @Override
591     public List<ActionRequestValue> findAllValidRequests(String principalId, String documentId, String requestCode) {
592         List<ActionRequestValue> pendingArs =
593                 findByStatusAndDocumentId(ActionRequestStatus.ACTIVATED.getCode(), documentId);
594         return findAllValidRequests(principalId, pendingArs, requestCode);
595     }
596 
597     protected List<ActionRequestValue> findByStatusAndDocumentId(String statusCode, String documentId) {
598         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
599                 equal(STATUS, statusCode),
600                 equal(DOCUMENT_ID, documentId),
601                 equal(CURRENT_INDICATOR, Boolean.TRUE)
602         );
603         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
604     }
605 
606     @Override
607     public List<ActionRequestValue> findAllValidRequests(String principalId, List<ActionRequestValue> actionRequests, String requestCode) {
608         List<String> arGroups = KimApiServiceLocator.getGroupService().getGroupIdsByPrincipalId(principalId);
609         return filterActionRequestsByCode(actionRequests, principalId, arGroups, requestCode);
610     }
611 
612     /**
613 	 * Filters action requests based on if they occur after the given requestCode, and if they relate to
614 	 * the given principal
615 	 * @param actionRequests the List of ActionRequestValues to filter
616 	 * @param principalId the id of the principal to find active requests for
617 	 * @param principalGroupIds List of group ids that the principal belongs to
618 	 * @param requestCode the request code for all ActionRequestValues to be after
619 	 * @return the filtered List of ActionRequestValues
620 	 */
621 	@Override
622     public List<ActionRequestValue> filterActionRequestsByCode(List<ActionRequestValue> actionRequests, String principalId, List<String> principalGroupIds, String requestCode) {
623 		List<ActionRequestValue> filteredActionRequests = new ArrayList<ActionRequestValue>();
624 
625         for (ActionRequestValue ar : actionRequests) {
626             if (ActionRequestValue.compareActionCode(ar.getActionRequested(), requestCode, true) > 0) {
627                 continue;
628             }
629             if (ar.isUserRequest() && principalId.equals(ar.getPrincipalId())) {
630             	filteredActionRequests.add(ar);
631             } else if (ar.isGroupRequest() && principalGroupIds != null && !principalGroupIds.isEmpty()) {
632             	for (String groupId : principalGroupIds) {
633             		if (groupId.equals(ar.getGroupId())) {
634             			filteredActionRequests.add(ar);
635             		}
636             	}
637             }
638         }
639 
640 		return filteredActionRequests;
641 	}
642 
643     @Override
644     public void updateActionRequestsForResponsibilityChange(Set<String> responsibilityIds) {
645     	PerformanceLogger performanceLogger = null;
646     	if ( LOG.isInfoEnabled() ) {
647     		performanceLogger = new PerformanceLogger();
648     	}
649         Collection<String> documentsAffected = getRouteHeaderService().findPendingByResponsibilityIds(responsibilityIds);
650         String cacheWaitValue = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_CACHE_REQUEUE_DELAY);
651         Long cacheWait = KewApiConstants.DEFAULT_CACHE_REQUEUE_WAIT_TIME;
652         if (!org.apache.commons.lang.StringUtils.isEmpty(cacheWaitValue)) {
653             try {
654                 cacheWait = Long.valueOf(cacheWaitValue);
655             } catch (NumberFormatException e) {
656                 LOG.warn("Cache wait time is not a valid number: " + cacheWaitValue);
657             }
658         }
659         if ( LOG.isInfoEnabled() ) {
660         	LOG.info("Scheduling requeue of " + documentsAffected.size() + " documents, affected by " + responsibilityIds.size()
661                     + " responsibility changes.  Installing a processing wait time of " + cacheWait
662                     + " milliseconds to avoid stale rule cache.");
663         }
664         for (String documentId : documentsAffected) {
665 
666              String applicationId = null;
667              DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByDocumentId(documentId);
668 
669              if (documentType != null) {
670                 applicationId = documentType.getApplicationId();
671              }
672 
673             if (applicationId == null)
674             {
675                 applicationId = CoreConfigHelper.getApplicationId();
676             }
677             if(documentType.getRegenerateActionRequestsOnChange().getPolicyValue()) {
678                 DocumentRefreshQueue documentRequeuer = KewApiServiceLocator.getDocumentRequeuerService(applicationId,
679                         documentId, cacheWait);
680                 documentRequeuer.refreshDocument(documentId);
681             }
682         }
683         if ( LOG.isInfoEnabled() ) {
684         	performanceLogger.log("Time to updateActionRequestsForResponsibilityChange");
685         }
686     }
687 
688     @Override
689     public void deleteActionRequestGraphNoOutbox(ActionRequestValue actionRequest) {
690         deleteActionRequestGraph(actionRequest, false);
691     }
692 
693     /**
694      * Deletes an action request and all of its action items following the graph down through the action request's
695      * children. This method should be invoked on a top-level action request.
696      */
697     @Override
698     public void deleteActionRequestGraph(ActionRequestValue actionRequest) {
699         deleteActionRequestGraph(actionRequest, true);
700     }
701 
702     protected void deleteActionRequestGraph(ActionRequestValue actionRequest, boolean populateOutbox) {
703         if (actionRequest.getParentActionRequest() != null) {
704             throw new IllegalArgumentException("Must delete action request graph from the root, encountered a request with a parent: " + actionRequest);
705         }
706         deleteActionItemsFromGraph(actionRequest, populateOutbox);
707         if (actionRequest.getActionTakenId() != null) {
708             ActionTakenValue actionTaken = getActionTakenService().findByActionTakenId(actionRequest.getActionTakenId());
709             if (actionTaken != null) {
710                 getActionTakenService().delete(actionTaken);
711             }
712         }
713         // delete from the root, it should cascade down to the children
714         getDataObjectService().delete(actionRequest);
715         // go ahead and flush to ensure that the deletion happens before we return control to the calling code
716         getDataObjectService().flush(ActionRequestValue.class);
717     }
718 
719     /**
720      * Deletes the action items for the action request
721      * @param actionRequest the action request whose action items to delete
722      */
723     private void deleteActionItems(ActionRequestValue actionRequest, boolean populateOutbox) {
724     	List<ActionItem> actionItems = actionRequest.getActionItems();
725     	if ( LOG.isDebugEnabled() ) {
726     		LOG.debug("deleting " + actionItems.size() + " action items for action request: " + actionRequest);
727     	}
728         for (ActionItem actionItem: actionItems) {
729         	if ( LOG.isDebugEnabled() ) {
730         		LOG.debug("deleting action item: " + actionItem);
731         	}
732             if (populateOutbox) {
733                 getActionListService().deleteActionItem(actionItem);
734             } else {
735                 getActionListService().deleteActionItemNoOutbox(actionItem);
736             }
737         }
738     }
739 
740     /**
741      * Deletes the action items for the *root* action request.
742      *
743      * @param actionRequest the action request whose action items to delete
744      */
745     private void deleteActionItemsFromGraph(ActionRequestValue actionRequest, boolean populateOutbox) {
746         if (actionRequest.getParentActionRequest() != null) {
747             throw new IllegalArgumentException("Must delete action item from root of action request graph!");
748         }
749         List<ActionItem> actionItemsToDelete = new ArrayList<ActionItem>();
750         accumulateActionItemsFromGraph(actionRequest, actionItemsToDelete);
751         if ( LOG.isDebugEnabled() ) {
752             LOG.debug("deleting " + actionItemsToDelete.size() + " action items for action request graph: " + actionRequest);
753         }
754         for (ActionItem actionItem : actionItemsToDelete) {
755             if ( LOG.isDebugEnabled() ) {
756                 LOG.debug("deleting action item: " + actionItem);
757             }
758             if (populateOutbox) {
759                 getActionListService().deleteActionItem(actionItem);
760             } else {
761                 getActionListService().deleteActionItemNoOutbox(actionItem);
762             }
763         }
764     }
765 
766     private void accumulateActionItemsFromGraph(ActionRequestValue actionRequest, List<ActionItem> actionItems) {
767         actionItems.addAll(actionRequest.getActionItems());
768         for (ActionRequestValue childRequest : actionRequest.getChildrenRequests()) {
769             accumulateActionItemsFromGraph(childRequest, actionItems);
770         }
771     }
772 
773 
774     @Override
775     public List<ActionRequestValue> findByDocumentIdIgnoreCurrentInd(String documentId) {
776         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(equal(DOCUMENT_ID, documentId));
777         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
778     }
779 
780     @Override
781     public List<ActionRequestValue> findAllActionRequestsByDocumentId(String documentId) {
782         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
783                 equal(DOCUMENT_ID, documentId),
784                 equal(CURRENT_INDICATOR, Boolean.TRUE)
785         );
786         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
787     }
788 
789     @Override
790     public List<ActionRequestValue> findAllRootActionRequestsByDocumentId(String documentId) {
791         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
792                 equal(DOCUMENT_ID, documentId),
793                 equal(CURRENT_INDICATOR, Boolean.TRUE),
794                 isNull(PARENT_ACTION_REQUEST)
795         );
796         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
797     }
798 
799     @Override
800     public List<ActionRequestValue> findPendingByActionRequestedAndDocId(String actionRequestedCd, String documentId) {
801         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
802                 equal(DOCUMENT_ID, documentId),
803                 equal(CURRENT_INDICATOR, Boolean.TRUE),
804                 equal(ACTION_REQUESTED, actionRequestedCd),
805                 getPendingCriteria()
806         );
807         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
808     }
809 
810 
811 
812     @Override
813     public List<String> getPrincipalIdsWithPendingActionRequestByActionRequestedAndDocId(String actionRequestedCd, String documentId) {
814     	List<String> principalIds = new ArrayList<String>();
815     	List<ActionRequestValue> actionRequests = findPendingByActionRequestedAndDocId(actionRequestedCd, documentId);
816 		for(ActionRequestValue actionRequest: actionRequests){
817 			if(actionRequest.isUserRequest()){
818 				principalIds.add(actionRequest.getPrincipalId());
819 			} else if(actionRequest.isGroupRequest()){
820 				principalIds.addAll(
821 						KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId()));
822 			}
823 		}
824     	return principalIds;
825     }
826 
827     @Override
828     public List<ActionRequestValue> findPendingRootRequestsByDocId(String documentId) {
829         return getRootRequests(findPendingByDoc(documentId));
830     }
831 
832     @Override
833     public List<ActionRequestValue> findPendingRootRequestsByDocIdAtRouteNode(String documentId, String nodeInstanceId) {
834         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
835                 equal(DOCUMENT_ID, documentId),
836                 equal(CURRENT_INDICATOR, Boolean.TRUE),
837                 isNull(PARENT_ACTION_REQUEST),
838                 getPendingCriteria(),
839                 equal(ROUTE_NODE_INSTANCE_ID, nodeInstanceId)
840         );
841         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
842     }
843 
844     @Override
845     public List<ActionRequestValue> findRootRequestsByDocIdAtRouteNode(String documentId, String nodeInstanceId) {
846         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
847                 equal(DOCUMENT_ID, documentId),
848                 equal(CURRENT_INDICATOR, Boolean.TRUE),
849                 isNull(PARENT_ACTION_REQUEST),
850                 equal(ROUTE_NODE_INSTANCE_ID, nodeInstanceId)
851         );
852         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
853     }
854 
855     @Override
856     public List<ActionRequestValue> findPendingRootRequestsByDocumentType(String documentTypeId) {
857         return getActionRequestDAO().findPendingRootRequestsByDocumentType(documentTypeId);
858     }
859 
860     @Override
861     public ActionRequestValue saveActionRequest(ActionRequestValue actionRequest) {
862         return saveActionRequest(actionRequest, false);
863     }
864 
865     protected ActionRequestValue saveActionRequest(ActionRequestValue actionRequest, boolean simulation) {
866         if (actionRequest.isGroupRequest()) {
867             Group group = actionRequest.getGroup();
868             if (group == null)  {
869                 throw new RiceRuntimeException("Attempted to save an action request with a non-existent group.");
870             }
871             if (!group.isActive() && actionRequest.getRouteHeader().getDocumentType().getFailOnInactiveGroup().getPolicyValue()) {
872                 throw new RiceRuntimeException("Attempted to save an action request with an inactive group.");
873             }
874         }
875         if (actionRequest.getActionRequestId() == null) {
876             loadDefaultValues(actionRequest);
877         }
878         if ( actionRequest.getAnnotation() != null && actionRequest.getAnnotation().length() > 2000 ) {
879             actionRequest.setAnnotation( StringUtils.abbreviate(actionRequest.getAnnotation(), 2000) );
880         }
881         if (simulation) {
882             return actionRequest;
883         } else {
884             return getDataObjectService().save(actionRequest);
885         }
886     }
887 
888     private void loadDefaultValues(ActionRequestValue actionRequest) {
889         checkNull(actionRequest.getActionRequested(), "action requested");
890         checkNull(actionRequest.getResponsibilityId(), "responsibility ID");
891         checkNull(actionRequest.getRouteLevel(), "route level");
892         checkNull(actionRequest.getDocVersion(), "doc version");
893         if (actionRequest.getForceAction() == null) {
894             actionRequest.setForceAction(Boolean.FALSE);
895         }
896         if (actionRequest.getStatus() == null) {
897             actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
898         }
899         if (actionRequest.getPriority() == null) {
900             actionRequest.setPriority(KewApiConstants.ACTION_REQUEST_DEFAULT_PRIORITY);
901         }
902         if (actionRequest.getCurrentIndicator() == null) {
903             actionRequest.setCurrentIndicator(true);
904         }
905         actionRequest.setCreateDate(new Timestamp(System.currentTimeMillis()));
906     }
907 
908     private void checkNull(Object value, String valueName) throws RuntimeException {
909         if (value == null) {
910             throw new IllegalArgumentException("Null value for " + valueName);
911         }
912     }
913 
914     private List<ActionRequestValue> saveActionRequests(Collection<ActionRequestValue> actionRequests) {
915         // TODO validate only root requests are being saved here?
916         List<ActionRequestValue> savedRequests = new ArrayList<ActionRequestValue>(actionRequests.size());
917         for (ActionRequestValue actionRequest : actionRequests) {
918             savedRequests.add(saveActionRequest(actionRequest));
919         }
920         return savedRequests;
921     }
922 
923     @Override
924     public List<ActionRequestValue> findPendingByDoc(String documentId) {
925         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
926                 equal(DOCUMENT_ID, documentId),
927                 equal(CURRENT_INDICATOR, Boolean.TRUE),
928                 getPendingCriteria()
929         );
930         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
931     }
932 
933     @Override
934     public List<ActionRequestValue> findPendingByDocRequestCdNodeName(String documentId, String requestCode, String nodeName) {
935         List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
936         for (ActionRequestValue actionRequest : findPendingByDoc(documentId)) {
937             if (ActionRequestValue.compareActionCode(actionRequest.getActionRequested(), requestCode, true) > 0) {
938                 continue;
939             }
940             if (actionRequest.getNodeInstance() != null && actionRequest.getNodeInstance().getName().equals(nodeName)) {
941                 requests.add(actionRequest);
942             }
943         }
944         return requests;
945     }
946 
947     @Override
948     public List<ActionRequestValue> findActivatedByGroup(String groupId) {
949         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
950                 equal(STATUS, ActionRequestStatus.ACTIVATED.getCode()),
951                 equal(GROUP_ID, groupId),
952                 equal(CURRENT_INDICATOR, Boolean.TRUE),
953                 getPendingCriteria()
954         );
955         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
956     }
957 
958     @Override
959     public List<ActionRequestValue> findByStatusAndDocId(String statusCode, String documentId) {
960         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
961                 equal(STATUS, statusCode),
962                 equal(DOCUMENT_ID, documentId),
963                 equal(CURRENT_INDICATOR, Boolean.TRUE)
964         );
965         return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
966     }
967 
968     @Override
969     public Recipient findDelegator(List<ActionRequestValue> actionRequests) {
970         Recipient delegator = null;
971         String requestCode = KewApiConstants.ACTION_REQUEST_FYI_REQ;
972         for (Object actionRequest1 : actionRequests)
973         {
974             ActionRequestValue actionRequest = (ActionRequestValue) actionRequest1;
975             ActionRequestValue delegatorRequest = findDelegatorRequest(actionRequest);
976             if (delegatorRequest != null)
977             {
978                 if (ActionRequestValue.compareActionCode(delegatorRequest.getActionRequested(), requestCode, true) >= 0)
979                 {
980                     delegator = delegatorRequest.getRecipient();
981                     requestCode = delegatorRequest.getActionRequested();
982                 }
983             }
984         }
985         return delegator;
986     }
987 
988     @Override
989     public ActionRequestValue findDelegatorRequest(ActionRequestValue actionRequest) {
990         ActionRequestValue parentRequest = actionRequest.getParentActionRequest();
991         if (parentRequest != null && !(parentRequest.isUserRequest() || parentRequest.isGroupRequest())) {
992             parentRequest = findDelegatorRequest(parentRequest);
993         }
994         return parentRequest;
995     }
996 
997     @Override
998     public List<ActionRequestValue> getDelegateRequests(ActionRequestValue actionRequest) {
999         List<ActionRequestValue> delegateRequests = new ArrayList<ActionRequestValue>();
1000         List<ActionRequestValue> requests = getTopLevelRequests(actionRequest);
1001         for (Object request : requests)
1002         {
1003             ActionRequestValue parentActionRequest = (ActionRequestValue) request;
1004             delegateRequests.addAll(parentActionRequest.getChildrenRequests());
1005         }
1006         return delegateRequests;
1007     }
1008 
1009     @Override
1010     public List<ActionRequestValue> getTopLevelRequests(ActionRequestValue actionRequest) {
1011         List<ActionRequestValue> topLevelRequests = new ArrayList<ActionRequestValue>();
1012         if (actionRequest.isRoleRequest()) {
1013             topLevelRequests.addAll(actionRequest.getChildrenRequests());
1014         } else {
1015             topLevelRequests.add(actionRequest);
1016         }
1017         return topLevelRequests;
1018     }
1019 
1020     @Override
1021     public boolean doesPrincipalHaveRequest(String principalId, String documentId) {
1022         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
1023                 equal(PRINCIPAL_ID, principalId),
1024                 equal(DOCUMENT_ID, documentId),
1025                 equal(RECIPIENT_TYPE_CD, RecipientType.PRINCIPAL.getCode()),
1026                 equal(CURRENT_INDICATOR, Boolean.TRUE)
1027         );
1028 
1029         criteria.setCountFlag(CountFlag.ONLY);
1030         Integer count = getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getTotalRowCount();
1031         if (count != null && count > 0) {
1032             return true;
1033         }
1034         // TODO since we only store the workgroup id for workgroup requests, if the user is in a workgroup that has a request
1035         // than we need get all the requests with workgroup ids and see if our user is in that group
1036         List<String> groupIds = getActionRequestDAO().getRequestGroupIds(documentId);
1037         for (String groupId : groupIds) {
1038             if (KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, groupId)) {
1039                 return true;
1040             }
1041         }
1042         return false;
1043     }
1044 
1045     @Override
1046     public ActionRequestValue getActionRequestForRole(String actionTakenId) {
1047         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
1048                 equal(ACTION_TAKEN_ID, actionTakenId),
1049                 equal(CURRENT_INDICATOR, Boolean.TRUE),
1050                 equal(RECIPIENT_TYPE_CD, RecipientType.ROLE.getCode()),
1051                 isNull(PARENT_ACTION_REQUEST)
1052         );
1053         List<ActionRequestValue> actionTakenRoleRequests =
1054                 getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
1055         if (actionTakenRoleRequests.isEmpty()) {
1056             return null;
1057         }
1058         return actionTakenRoleRequests.get(0);
1059     }
1060 
1061     /**
1062      * Returns criteria for selecting "pending" action requests. A request is pending if it's status is activated
1063      * or initialized.
1064      *
1065      * @return criteria for selecting pending action requests
1066      */
1067     protected Predicate getPendingCriteria() {
1068         return or(
1069                 equal(STATUS, ActionRequestStatus.ACTIVATED.getCode()),
1070                 equal(STATUS, ActionRequestStatus.INITIALIZED.getCode())
1071         );
1072     }
1073 
1074     private ActionListService getActionListService() {
1075         return KEWServiceLocator.getActionListService();
1076     }
1077 
1078     private ActionTakenService getActionTakenService() {
1079         return KEWServiceLocator.getActionTakenService();
1080     }
1081 
1082     private RouteHeaderService getRouteHeaderService() {
1083         return KEWServiceLocator.getService(KEWServiceLocator.DOC_ROUTE_HEADER_SRV);
1084     }
1085 
1086     public DataObjectService getDataObjectService() {
1087         return dataObjectService;
1088     }
1089 
1090     public void setDataObjectService(DataObjectService dataObjectService) {
1091         this.dataObjectService = dataObjectService;
1092     }
1093 
1094     public ActionRequestDAO getActionRequestDAO() {
1095         return actionRequestDAO;
1096     }
1097 
1098     public void setActionRequestDAO(ActionRequestDAO actionRequestDAO) {
1099         this.actionRequestDAO = actionRequestDAO;
1100     }
1101 
1102 }