001/**
002 * Copyright 2005-2017 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.actionrequest.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.core.api.config.CoreConfigHelper;
021import org.kuali.rice.core.api.config.property.ConfigContext;
022import org.kuali.rice.core.api.criteria.CountFlag;
023import org.kuali.rice.core.api.criteria.Predicate;
024import org.kuali.rice.core.api.criteria.QueryByCriteria;
025import org.kuali.rice.core.api.exception.RiceRuntimeException;
026import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
027import org.kuali.rice.kew.actionitem.ActionItem;
028import org.kuali.rice.kew.actionlist.service.ActionListService;
029import org.kuali.rice.kew.actionrequest.ActionRequestValue;
030import org.kuali.rice.kew.actionrequest.Recipient;
031import org.kuali.rice.kew.actionrequest.dao.ActionRequestDAO;
032import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
033import org.kuali.rice.kew.actiontaken.ActionTakenValue;
034import org.kuali.rice.kew.actiontaken.service.ActionTakenService;
035import org.kuali.rice.kew.api.KewApiConstants;
036import org.kuali.rice.kew.api.KewApiServiceLocator;
037import org.kuali.rice.kew.api.action.ActionRequestPolicy;
038import org.kuali.rice.kew.api.action.ActionRequestStatus;
039import org.kuali.rice.kew.api.action.RecipientType;
040import org.kuali.rice.kew.api.document.DocumentRefreshQueue;
041import org.kuali.rice.kew.doctype.bo.DocumentType;
042import org.kuali.rice.kew.engine.ActivationContext;
043import org.kuali.rice.kew.engine.node.RouteNodeInstance;
044import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
045import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
046import org.kuali.rice.kew.routemodule.RouteModule;
047import org.kuali.rice.kew.service.KEWServiceLocator;
048import org.kuali.rice.kew.util.FutureRequestDocumentStateManager;
049import org.kuali.rice.kew.util.PerformanceLogger;
050import org.kuali.rice.kew.util.ResponsibleParty;
051import org.kuali.rice.kim.api.group.Group;
052import org.kuali.rice.kim.api.identity.principal.Principal;
053import org.kuali.rice.kim.api.services.KimApiServiceLocator;
054import org.kuali.rice.krad.data.DataObjectService;
055import org.kuali.rice.krad.data.PersistenceOption;
056import org.kuali.rice.krad.util.KRADConstants;
057
058import java.sql.Timestamp;
059import java.util.ArrayList;
060import java.util.Collection;
061import java.util.Collections;
062import java.util.HashMap;
063import java.util.HashSet;
064import java.util.List;
065import java.util.Map;
066import java.util.Set;
067
068import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
069
070/**
071 * Default implementation of the {@link ActionRequestService}.
072 *
073 * @author Kuali Rice Team (rice.collab@kuali.org)
074 */
075public class ActionRequestServiceImpl implements ActionRequestService {
076
077    private static final Logger LOG = Logger.getLogger(ActionRequestServiceImpl.class);
078
079    private static final String STATUS = "status";
080    private static final String DOCUMENT_ID = "documentId";
081    private static final String CURRENT_INDICATOR = "currentIndicator";
082    private static final String PARENT_ACTION_REQUEST = "parentActionRequest";
083    private static final String ACTION_REQUESTED = "actionRequested";
084    private static final String ROUTE_NODE_INSTANCE_ID = "nodeInstance.routeNodeInstanceId";
085    private static final String GROUP_ID = "groupId";
086    private static final String ACTION_TAKEN_ID = "actionTaken.actionTakenId";
087    private static final String RECIPIENT_TYPE_CD = "recipientTypeCd";
088    private static final String PRINCIPAL_ID = "principalId";
089
090    private DataObjectService dataObjectService;
091    private ActionRequestDAO actionRequestDAO;
092
093    @Override
094    public ActionRequestValue findByActionRequestId(String actionRequestId) {
095        return getDataObjectService().find(ActionRequestValue.class, actionRequestId);
096    }
097
098    @Override
099    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        String actionTakenId = actionRequest.getActionTakenId();
708        // delete from the root, it should cascade down to the children
709        getDataObjectService().delete(actionRequest);
710        // go ahead and flush to ensure that the deletion happens before we return control to the calling code
711        getDataObjectService().flush(ActionRequestValue.class);
712
713        // If there are no other actions taken associated with this action request, delete the action taken as well.
714        if (actionTakenId != null) {
715            ActionTakenValue actionTaken = getActionTakenService().findByActionTakenId(actionTakenId);
716            if (actionTaken != null) {
717                Collection<ActionRequestValue> actionRequestValues = actionTaken.getActionRequests();
718                if (actionRequestValues.size() == 0) {
719                    getActionTakenService().delete(actionTaken);
720                }
721            }
722        }
723    }
724
725    /**
726     * Deletes the action items for the action request
727     * @param actionRequest the action request whose action items to delete
728     */
729    private void deleteActionItems(ActionRequestValue actionRequest, boolean populateOutbox) {
730        List<ActionItem> actionItems = actionRequest.getActionItems();
731        if ( LOG.isDebugEnabled() ) {
732                LOG.debug("deleting " + actionItems.size() + " action items for action request: " + actionRequest);
733        }
734        for (ActionItem actionItem: actionItems) {
735                if ( LOG.isDebugEnabled() ) {
736                        LOG.debug("deleting action item: " + actionItem);
737                }
738            if (populateOutbox) {
739                getActionListService().deleteActionItem(actionItem);
740            } else {
741                getActionListService().deleteActionItemNoOutbox(actionItem);
742            }
743        }
744    }
745
746    /**
747     * Deletes the action items for the *root* action request.
748     *
749     * @param actionRequest the action request whose action items to delete
750     */
751    private void deleteActionItemsFromGraph(ActionRequestValue actionRequest, boolean populateOutbox) {
752        if (actionRequest.getParentActionRequest() != null) {
753            throw new IllegalArgumentException("Must delete action item from root of action request graph!");
754        }
755        List<ActionItem> actionItemsToDelete = new ArrayList<ActionItem>();
756        accumulateActionItemsFromGraph(actionRequest, actionItemsToDelete);
757        if ( LOG.isDebugEnabled() ) {
758            LOG.debug("deleting " + actionItemsToDelete.size() + " action items for action request graph: " + actionRequest);
759        }
760        for (ActionItem actionItem : actionItemsToDelete) {
761            if ( LOG.isDebugEnabled() ) {
762                LOG.debug("deleting action item: " + actionItem);
763            }
764            if (populateOutbox) {
765                getActionListService().deleteActionItem(actionItem);
766            } else {
767                getActionListService().deleteActionItemNoOutbox(actionItem);
768            }
769        }
770    }
771
772    private void accumulateActionItemsFromGraph(ActionRequestValue actionRequest, List<ActionItem> actionItems) {
773        actionItems.addAll(actionRequest.getActionItems());
774        for (ActionRequestValue childRequest : actionRequest.getChildrenRequests()) {
775            accumulateActionItemsFromGraph(childRequest, actionItems);
776        }
777    }
778
779
780    @Override
781    public List<ActionRequestValue> findByDocumentIdIgnoreCurrentInd(String documentId) {
782        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(equal(DOCUMENT_ID, documentId));
783        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
784    }
785
786    @Override
787    public List<ActionRequestValue> findAllActionRequestsByDocumentId(String documentId) {
788        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
789                equal(DOCUMENT_ID, documentId),
790                equal(CURRENT_INDICATOR, Boolean.TRUE)
791        );
792        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
793    }
794
795    @Override
796    public List<ActionRequestValue> findAllRootActionRequestsByDocumentId(String documentId) {
797        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
798                equal(DOCUMENT_ID, documentId),
799                equal(CURRENT_INDICATOR, Boolean.TRUE),
800                isNull(PARENT_ACTION_REQUEST)
801        );
802        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
803    }
804
805    @Override
806    public List<ActionRequestValue> findPendingByActionRequestedAndDocId(String actionRequestedCd, String documentId) {
807        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
808                equal(DOCUMENT_ID, documentId),
809                equal(CURRENT_INDICATOR, Boolean.TRUE),
810                equal(ACTION_REQUESTED, actionRequestedCd),
811                getPendingCriteria()
812        );
813        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
814    }
815
816
817
818    @Override
819    public List<String> getPrincipalIdsWithPendingActionRequestByActionRequestedAndDocId(String actionRequestedCd, String documentId) {
820        List<String> principalIds = new ArrayList<String>();
821        List<ActionRequestValue> actionRequests = findPendingByActionRequestedAndDocId(actionRequestedCd, documentId);
822                for(ActionRequestValue actionRequest: actionRequests){
823                        if(actionRequest.isUserRequest()){
824                                principalIds.add(actionRequest.getPrincipalId());
825                        } else if(actionRequest.isGroupRequest()){
826                                principalIds.addAll(
827                                                KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId()));
828                        }
829                }
830        return principalIds;
831    }
832
833    @Override
834    public List<ActionRequestValue> findPendingRootRequestsByDocId(String documentId) {
835        return getRootRequests(findPendingByDoc(documentId));
836    }
837
838    @Override
839    public List<ActionRequestValue> findPendingRootRequestsByDocIdAtRouteNode(String documentId, String nodeInstanceId) {
840        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
841                equal(DOCUMENT_ID, documentId),
842                equal(CURRENT_INDICATOR, Boolean.TRUE),
843                isNull(PARENT_ACTION_REQUEST),
844                getPendingCriteria(),
845                equal(ROUTE_NODE_INSTANCE_ID, nodeInstanceId)
846        );
847        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
848    }
849
850    @Override
851    public List<ActionRequestValue> findRootRequestsByDocIdAtRouteNode(String documentId, String nodeInstanceId) {
852        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
853                equal(DOCUMENT_ID, documentId),
854                equal(CURRENT_INDICATOR, Boolean.TRUE),
855                isNull(PARENT_ACTION_REQUEST),
856                equal(ROUTE_NODE_INSTANCE_ID, nodeInstanceId)
857        );
858        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
859    }
860
861    @Override
862    public List<ActionRequestValue> findPendingRootRequestsByDocumentType(String documentTypeId) {
863        return getActionRequestDAO().findPendingRootRequestsByDocumentType(documentTypeId);
864    }
865
866    @Override
867    public ActionRequestValue saveActionRequest(ActionRequestValue actionRequest) {
868        return saveActionRequest(actionRequest, false);
869    }
870
871    protected ActionRequestValue saveActionRequest(ActionRequestValue actionRequest, boolean simulation) {
872        if (actionRequest.isGroupRequest()) {
873            Group group = actionRequest.getGroup();
874            if (group == null)  {
875                throw new RiceRuntimeException("Attempted to save an action request with a non-existent group.");
876            }
877            if (!group.isActive() && actionRequest.getRouteHeader().getDocumentType().getFailOnInactiveGroup().getPolicyValue()) {
878                throw new RiceRuntimeException("Attempted to save an action request with an inactive group.");
879            }
880        }
881        if (actionRequest.getActionRequestId() == null) {
882            loadDefaultValues(actionRequest);
883        }
884        if ( actionRequest.getAnnotation() != null && actionRequest.getAnnotation().length() > 2000 ) {
885            actionRequest.setAnnotation( StringUtils.abbreviate(actionRequest.getAnnotation(), 2000) );
886        }
887        if (simulation) {
888            return actionRequest;
889        } else {
890            return getDataObjectService().save(actionRequest);
891        }
892    }
893
894    private void loadDefaultValues(ActionRequestValue actionRequest) {
895        checkNull(actionRequest.getActionRequested(), "action requested");
896        checkNull(actionRequest.getResponsibilityId(), "responsibility ID");
897        checkNull(actionRequest.getRouteLevel(), "route level");
898        checkNull(actionRequest.getDocVersion(), "doc version");
899        if (actionRequest.getForceAction() == null) {
900            actionRequest.setForceAction(Boolean.FALSE);
901        }
902        if (actionRequest.getStatus() == null) {
903            actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
904        }
905        if (actionRequest.getPriority() == null) {
906            actionRequest.setPriority(KewApiConstants.ACTION_REQUEST_DEFAULT_PRIORITY);
907        }
908        if (actionRequest.getCurrentIndicator() == null) {
909            actionRequest.setCurrentIndicator(true);
910        }
911        actionRequest.setCreateDate(new Timestamp(System.currentTimeMillis()));
912    }
913
914    private void checkNull(Object value, String valueName) throws RuntimeException {
915        if (value == null) {
916            throw new IllegalArgumentException("Null value for " + valueName);
917        }
918    }
919
920    private List<ActionRequestValue> saveActionRequests(Collection<ActionRequestValue> actionRequests) {
921        // TODO validate only root requests are being saved here?
922        List<ActionRequestValue> savedRequests = new ArrayList<ActionRequestValue>(actionRequests.size());
923        for (ActionRequestValue actionRequest : actionRequests) {
924            savedRequests.add(saveActionRequest(actionRequest));
925        }
926        return savedRequests;
927    }
928
929    @Override
930    public List<ActionRequestValue> findPendingByDoc(String documentId) {
931        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
932                equal(DOCUMENT_ID, documentId),
933                equal(CURRENT_INDICATOR, Boolean.TRUE),
934                getPendingCriteria()
935        );
936        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
937    }
938
939    @Override
940    public List<ActionRequestValue> findPendingByDocRequestCdNodeName(String documentId, String requestCode, String nodeName) {
941        List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
942        for (ActionRequestValue actionRequest : findPendingByDoc(documentId)) {
943            if (ActionRequestValue.compareActionCode(actionRequest.getActionRequested(), requestCode, true) > 0) {
944                continue;
945            }
946            if (actionRequest.getNodeInstance() != null && actionRequest.getNodeInstance().getName().equals(nodeName)) {
947                requests.add(actionRequest);
948            }
949        }
950        return requests;
951    }
952
953    @Override
954    public List<ActionRequestValue> findActivatedByGroup(String groupId) {
955        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
956                equal(STATUS, ActionRequestStatus.ACTIVATED.getCode()),
957                equal(GROUP_ID, groupId),
958                equal(CURRENT_INDICATOR, Boolean.TRUE),
959                getPendingCriteria()
960        );
961        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
962    }
963
964    @Override
965    public List<ActionRequestValue> findByStatusAndDocId(String statusCode, String documentId) {
966        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
967                equal(STATUS, statusCode),
968                equal(DOCUMENT_ID, documentId),
969                equal(CURRENT_INDICATOR, Boolean.TRUE)
970        );
971        return getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
972    }
973
974    @Override
975    public Recipient findDelegator(List<ActionRequestValue> actionRequests) {
976        Recipient delegator = null;
977        String requestCode = KewApiConstants.ACTION_REQUEST_FYI_REQ;
978        for (Object actionRequest1 : actionRequests)
979        {
980            ActionRequestValue actionRequest = (ActionRequestValue) actionRequest1;
981            ActionRequestValue delegatorRequest = findDelegatorRequest(actionRequest);
982            if (delegatorRequest != null)
983            {
984                if (ActionRequestValue.compareActionCode(delegatorRequest.getActionRequested(), requestCode, true) >= 0)
985                {
986                    delegator = delegatorRequest.getRecipient();
987                    requestCode = delegatorRequest.getActionRequested();
988                }
989            }
990        }
991        return delegator;
992    }
993
994    @Override
995    public ActionRequestValue findDelegatorRequest(ActionRequestValue actionRequest) {
996        ActionRequestValue parentRequest = actionRequest.getParentActionRequest();
997        if (parentRequest != null && !(parentRequest.isUserRequest() || parentRequest.isGroupRequest())) {
998            parentRequest = findDelegatorRequest(parentRequest);
999        }
1000        return parentRequest;
1001    }
1002
1003    @Override
1004    public List<ActionRequestValue> getDelegateRequests(ActionRequestValue actionRequest) {
1005        List<ActionRequestValue> delegateRequests = new ArrayList<ActionRequestValue>();
1006        List<ActionRequestValue> requests = getTopLevelRequests(actionRequest);
1007        for (Object request : requests)
1008        {
1009            ActionRequestValue parentActionRequest = (ActionRequestValue) request;
1010            delegateRequests.addAll(parentActionRequest.getChildrenRequests());
1011        }
1012        return delegateRequests;
1013    }
1014
1015    @Override
1016    public List<ActionRequestValue> getTopLevelRequests(ActionRequestValue actionRequest) {
1017        List<ActionRequestValue> topLevelRequests = new ArrayList<ActionRequestValue>();
1018        if (actionRequest.isRoleRequest()) {
1019            topLevelRequests.addAll(actionRequest.getChildrenRequests());
1020        } else {
1021            topLevelRequests.add(actionRequest);
1022        }
1023        return topLevelRequests;
1024    }
1025
1026    @Override
1027    public boolean doesPrincipalHaveRequest(String principalId, String documentId) {
1028        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
1029                equal(PRINCIPAL_ID, principalId),
1030                equal(DOCUMENT_ID, documentId),
1031                equal(RECIPIENT_TYPE_CD, RecipientType.PRINCIPAL.getCode()),
1032                equal(CURRENT_INDICATOR, Boolean.TRUE)
1033        );
1034
1035        criteria.setCountFlag(CountFlag.ONLY);
1036        Integer count = getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getTotalRowCount();
1037        if (count != null && count > 0) {
1038            return true;
1039        }
1040        // TODO since we only store the workgroup id for workgroup requests, if the user is in a workgroup that has a request
1041        // than we need get all the requests with workgroup ids and see if our user is in that group
1042        List<String> groupIds = getActionRequestDAO().getRequestGroupIds(documentId);
1043        for (String groupId : groupIds) {
1044            if (KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, groupId)) {
1045                return true;
1046            }
1047        }
1048        return false;
1049    }
1050
1051    @Override
1052    public ActionRequestValue getActionRequestForRole(String actionTakenId) {
1053        QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create().setPredicates(
1054                equal(ACTION_TAKEN_ID, actionTakenId),
1055                equal(CURRENT_INDICATOR, Boolean.TRUE),
1056                equal(RECIPIENT_TYPE_CD, RecipientType.ROLE.getCode()),
1057                isNull(PARENT_ACTION_REQUEST)
1058        );
1059        List<ActionRequestValue> actionTakenRoleRequests =
1060                getDataObjectService().findMatching(ActionRequestValue.class, criteria.build()).getResults();
1061        if (actionTakenRoleRequests.isEmpty()) {
1062            return null;
1063        }
1064        return actionTakenRoleRequests.get(0);
1065    }
1066
1067    /**
1068     * Returns criteria for selecting "pending" action requests. A request is pending if it's status is activated
1069     * or initialized.
1070     *
1071     * @return criteria for selecting pending action requests
1072     */
1073    protected Predicate getPendingCriteria() {
1074        return or(
1075                equal(STATUS, ActionRequestStatus.ACTIVATED.getCode()),
1076                equal(STATUS, ActionRequestStatus.INITIALIZED.getCode())
1077        );
1078    }
1079
1080    private ActionListService getActionListService() {
1081        return KEWServiceLocator.getActionListService();
1082    }
1083
1084    private ActionTakenService getActionTakenService() {
1085        return KEWServiceLocator.getActionTakenService();
1086    }
1087
1088    private RouteHeaderService getRouteHeaderService() {
1089        return KEWServiceLocator.getService(KEWServiceLocator.DOC_ROUTE_HEADER_SRV);
1090    }
1091
1092    public DataObjectService getDataObjectService() {
1093        return dataObjectService;
1094    }
1095
1096    public void setDataObjectService(DataObjectService dataObjectService) {
1097        this.dataObjectService = dataObjectService;
1098    }
1099
1100    public ActionRequestDAO getActionRequestDAO() {
1101        return actionRequestDAO;
1102    }
1103
1104    public void setActionRequestDAO(ActionRequestDAO actionRequestDAO) {
1105        this.actionRequestDAO = actionRequestDAO;
1106    }
1107
1108}