View Javadoc
1   /**
2    * Copyright 2005-2016 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.impl.action;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
22  import org.kuali.rice.core.api.exception.RiceRuntimeException;
23  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
24  import org.kuali.rice.core.api.uif.RemotableAttributeError;
25  import org.kuali.rice.core.api.uif.RemotableAttributeErrorContract;
26  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
27  import org.kuali.rice.kew.actionitem.ActionItem;
28  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
29  import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
30  import org.kuali.rice.kew.actionrequest.Recipient;
31  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
32  import org.kuali.rice.kew.api.KewApiConstants;
33  import org.kuali.rice.kew.api.KewApiServiceLocator;
34  import org.kuali.rice.kew.api.WorkflowRuntimeException;
35  import org.kuali.rice.kew.api.action.ActionRequest;
36  import org.kuali.rice.kew.api.action.ActionRequestType;
37  import org.kuali.rice.kew.api.action.ActionType;
38  import org.kuali.rice.kew.api.action.AdHocRevoke;
39  import org.kuali.rice.kew.api.action.AdHocToGroup;
40  import org.kuali.rice.kew.api.action.AdHocToGroup_v2_1_2;
41  import org.kuali.rice.kew.api.action.AdHocToPrincipal;
42  import org.kuali.rice.kew.api.action.AdHocToPrincipal_v2_1_2;
43  import org.kuali.rice.kew.api.action.DocumentActionParameters;
44  import org.kuali.rice.kew.api.action.DocumentActionResult;
45  import org.kuali.rice.kew.api.action.InvalidActionTakenException;
46  import org.kuali.rice.kew.api.action.MovePoint;
47  import org.kuali.rice.kew.api.action.RequestedActions;
48  import org.kuali.rice.kew.api.action.ReturnPoint;
49  import org.kuali.rice.kew.api.action.RoutingReportCriteria;
50  import org.kuali.rice.kew.api.action.ValidActions;
51  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
52  import org.kuali.rice.kew.api.doctype.DocumentTypeService;
53  import org.kuali.rice.kew.api.doctype.IllegalDocumentTypeException;
54  import org.kuali.rice.kew.api.document.Document;
55  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
56  import org.kuali.rice.kew.api.document.DocumentDetail;
57  import org.kuali.rice.kew.api.document.DocumentUpdate;
58  import org.kuali.rice.kew.api.document.PropertyDefinition;
59  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
60  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
61  import org.kuali.rice.kew.api.exception.WorkflowException;
62  import org.kuali.rice.kew.definition.AttributeDefinition;
63  import org.kuali.rice.kew.doctype.bo.DocumentType;
64  import org.kuali.rice.kew.dto.DTOConverter;
65  import org.kuali.rice.kew.engine.ActivationContext;
66  import org.kuali.rice.kew.engine.node.RouteNode;
67  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
68  import org.kuali.rice.kew.engine.simulation.SimulationCriteria;
69  import org.kuali.rice.kew.engine.simulation.SimulationResults;
70  import org.kuali.rice.kew.engine.simulation.SimulationWorkflowEngine;
71  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
72  import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
73  import org.kuali.rice.kew.rule.WorkflowRuleAttribute;
74  import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
75  import org.kuali.rice.kew.service.KEWServiceLocator;
76  import org.kuali.rice.kim.api.identity.principal.Principal;
77  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
78  import org.kuali.rice.krad.util.KRADConstants;
79  import org.kuali.rice.krad.util.ObjectUtils;
80  
81  import java.util.ArrayList;
82  import java.util.Collections;
83  import java.util.HashMap;
84  import java.util.HashSet;
85  import java.util.List;
86  import java.util.Map;
87  import java.util.Set;
88  
89  /**
90   * Reference implementation of the {@link WorkflowDocumentActionsService} api.
91   * 
92   * @author Kuali Rice Team (rice.collab@kuali.org)
93   * 
94   */
95  public class WorkflowDocumentActionsServiceImpl implements WorkflowDocumentActionsService {
96  
97      private static final Logger LOG = Logger.getLogger(WorkflowDocumentActionsServiceImpl.class);
98  
99      private DocumentTypeService documentTypeService;
100 
101     private static final DocumentActionCallback ACKNOWLEDGE_CALLBACK = new StandardDocumentActionCallback() {
102         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
103                 String annotation) throws WorkflowException {
104             return KEWServiceLocator.getWorkflowDocumentService().acknowledgeDocument(principalId, documentBo,
105                     annotation);
106         }
107 
108         public String getActionName() {
109             return ActionType.ACKNOWLEDGE.getLabel();
110         }
111     };
112 
113     private static final DocumentActionCallback APPROVE_CALLBACK = new StandardDocumentActionCallback() {
114         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
115                 String annotation) throws WorkflowException {
116             return KEWServiceLocator.getWorkflowDocumentService().approveDocument(principalId, documentBo, annotation);
117         }
118 
119         public String getActionName() {
120             return ActionType.APPROVE.getLabel();
121         }
122     };
123 
124     private static final DocumentActionCallback CANCEL_CALLBACK = new StandardDocumentActionCallback() {
125         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
126                 String annotation) throws WorkflowException {
127             return KEWServiceLocator.getWorkflowDocumentService().cancelDocument(principalId, documentBo, annotation);
128         }
129 
130         public String getActionName() {
131             return ActionType.CANCEL.getLabel();
132         }
133     };
134 
135     private static final DocumentActionCallback FYI_CALLBACK = new StandardDocumentActionCallback() {
136         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
137                 String annotation) throws WorkflowException {
138             return KEWServiceLocator.getWorkflowDocumentService().clearFYIDocument(principalId, documentBo, annotation);
139         }
140 
141         public String getActionName() {
142             return ActionType.FYI.getLabel();
143         }
144     };
145 
146     private static final DocumentActionCallback COMPLETE_CALLBACK = new StandardDocumentActionCallback() {
147         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
148                 String annotation) throws WorkflowException {
149             return KEWServiceLocator.getWorkflowDocumentService().completeDocument(principalId, documentBo, annotation);
150         }
151 
152         public String getActionName() {
153             return ActionType.COMPLETE.getLabel();
154         }
155     };
156 
157     private static final DocumentActionCallback DISAPPROVE_CALLBACK = new StandardDocumentActionCallback() {
158         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
159                 String annotation) throws WorkflowException {
160             return KEWServiceLocator.getWorkflowDocumentService().disapproveDocument(principalId, documentBo,
161                     annotation);
162         }
163 
164         public String getActionName() {
165             return ActionType.DISAPPROVE.getLabel();
166         }
167     };
168 
169     private static final DocumentActionCallback ROUTE_CALLBACK = new StandardDocumentActionCallback() {
170         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
171                 String annotation) throws WorkflowException {
172             return KEWServiceLocator.getWorkflowDocumentService().routeDocument(principalId, documentBo, annotation);
173         }
174 
175         public String getActionName() {
176             return ActionType.ROUTE.getLabel();
177         }
178     };
179 
180     private static final DocumentActionCallback BLANKET_APPROVE_CALLBACK = new StandardDocumentActionCallback() {
181         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
182                 String annotation) throws WorkflowException {
183             return KEWServiceLocator.getWorkflowDocumentService().blanketApproval(principalId, documentBo, annotation,
184                     new HashSet<String>());
185         }
186 
187         public String getActionName() {
188             return ActionType.BLANKET_APPROVE.getLabel();
189         }
190     };
191 
192     private static final DocumentActionCallback SAVE_CALLBACK = new StandardDocumentActionCallback() {
193         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
194                 String annotation) throws WorkflowException {
195             return KEWServiceLocator.getWorkflowDocumentService().saveDocument(principalId, documentBo, annotation);
196         }
197 
198         public String getActionName() {
199             return ActionType.SAVE.getLabel();
200         }
201     };
202 
203     private static final DocumentActionCallback PLACE_IN_EXCEPTION_CALLBACK = new StandardDocumentActionCallback() {
204         public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
205                 String annotation) throws WorkflowException {
206             return KEWServiceLocator.getWorkflowDocumentService().placeInExceptionRouting(principalId, documentBo,
207                     annotation);
208         }
209 
210         public String getActionName() {
211             return "Place In Exception";
212         }
213     };
214 
215     protected DocumentRouteHeaderValue init(DocumentActionParameters parameters) {
216         String documentId = parameters.getDocumentId();
217         String principalId = parameters.getPrincipalId();
218         DocumentUpdate documentUpdate = parameters.getDocumentUpdate();
219         DocumentContentUpdate documentContentUpdate = parameters.getDocumentContentUpdate();
220         incomingParamCheck(documentId, "documentId");
221         incomingParamCheck(principalId, "principalId");
222         if (LOG.isDebugEnabled()) {
223             LOG.debug("Initializing Document from incoming documentId: " + documentId);
224         }
225         KEWServiceLocator.getRouteHeaderService().lockRouteHeader(documentId, true);
226 
227         DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
228         if (document == null) {
229             throw new RiceIllegalArgumentException("Failed to locate a document for document id: " + documentId);
230         }
231         boolean modified = false;
232         if (documentUpdate != null) {
233             document.applyDocumentUpdate(documentUpdate);
234             modified = true;
235         }
236         if (documentContentUpdate != null) {
237             String newDocumentContent = DTOConverter.buildUpdatedDocumentContent(document.getDocContent(),
238                     documentContentUpdate, document.getDocumentTypeName());
239             document.setDocContent(newDocumentContent);
240             modified = true;
241         }
242 
243         if (modified) {
244             KEWServiceLocator.getRouteHeaderService().saveRouteHeader(document);
245 
246             /* 
247              * Branch data is not persisted when we call saveRouteHeader so we must Explicitly
248              * save the branch.  Noticed issue in: KULRICE-4074 when the future action request info,
249              * which is stored in the branch, was not being persisted.
250              * 
251              * The call to setRouteHeaderData will ensure that the variable data is in the branch, but we have
252              * to persist the route header before we can save the branch info.
253              * 
254              * Placing here to minimize system impact.  We should investigate placing this logic into 
255              * saveRouteHeader... but at that point we should just turn auto-update = true on the branch relationship
256              * 
257              */
258             this.saveRouteNodeInstances(document);
259 
260         }
261 
262         return document;
263     }
264 
265     /**
266      * This method explicitly saves the branch data if it exists in the routeHeaderValue
267      * 
268      * @param routeHeader
269      */
270     private void saveRouteNodeInstances(DocumentRouteHeaderValue routeHeader) {
271 
272         List<RouteNodeInstance> routeNodes = routeHeader.getInitialRouteNodeInstances();
273         if (routeNodes != null && !routeNodes.isEmpty()) {
274             for (RouteNodeInstance rni : routeNodes) {
275                 KEWServiceLocator.getRouteNodeService().save(rni);
276             }
277         }
278 
279     }
280 
281     @Override
282     public Document create(String documentTypeName,
283             String initiatorPrincipalId, DocumentUpdate documentUpdate,
284             DocumentContentUpdate documentContentUpdate)
285             throws RiceIllegalArgumentException, IllegalDocumentTypeException, InvalidActionTakenException {
286 
287         incomingParamCheck(documentTypeName, "documentTypeName");
288         incomingParamCheck(initiatorPrincipalId, "initiatorPrincipalId");
289 
290         if (LOG.isDebugEnabled()) {
291             LOG.debug("Create Document [documentTypeName=" + documentTypeName + ", initiatorPrincipalId="
292                     + initiatorPrincipalId + "]");
293         }
294 
295         String documentTypeId = documentTypeService.getIdByName(documentTypeName);
296         if (documentTypeId == null) {
297             throw new RiceIllegalArgumentException("Failed to locate a document type with the given name: "
298                     + documentTypeName);
299         }
300 
301         DocumentRouteHeaderValue documentBo = new DocumentRouteHeaderValue();
302         documentBo.setDocumentTypeId(documentTypeId);
303         documentBo.setInitiatorWorkflowId(initiatorPrincipalId);
304         if (documentUpdate != null) {
305             documentBo.setDocTitle(documentUpdate.getTitle());
306             documentBo.setAppDocId(documentUpdate.getApplicationDocumentId());
307         }
308         if (documentContentUpdate != null) {
309             String newDocumentContent = DTOConverter.buildUpdatedDocumentContent(null, documentContentUpdate,
310                     documentTypeName);
311             documentBo.setDocContent(newDocumentContent);
312         }
313 
314         try {
315             documentBo = KEWServiceLocator.getWorkflowDocumentService()
316                     .createDocument(initiatorPrincipalId, documentBo);
317         } catch (WorkflowException e) {
318             // TODO remove this once we stop throwing WorkflowException everywhere!
319             translateException(e);
320         }
321         return DocumentRouteHeaderValue.to(documentBo);
322     }
323 
324     @Override
325     public ValidActions determineValidActions(String documentId, String principalId) {
326         incomingParamCheck(documentId, "documentId");
327         incomingParamCheck(principalId, "principalId");
328         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
329         if (documentBo == null) {
330             throw new RiceIllegalArgumentException("Failed to locate a document for document id: " + documentId);
331         }
332         return determineValidActionsInternal(documentBo, principalId);
333     }
334 
335     protected ValidActions determineValidActionsInternal(DocumentRouteHeaderValue documentBo, String principalId) {
336         Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
337         return KEWServiceLocator.getActionRegistry().getValidActions(principal, documentBo);
338     }
339 
340     @Override
341     public RequestedActions determineRequestedActions(String documentId, String principalId) {
342         incomingParamCheck(documentId, "documentId");
343         incomingParamCheck(principalId, "principalId");
344         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
345         if (documentBo == null) {
346             throw new RiceIllegalArgumentException("Failed to locate a document for document id: " + documentId);
347         }
348         KEWServiceLocator.getIdentityHelperService().validatePrincipalId(principalId);
349         return determineRequestedActionsInternal(documentBo, principalId);
350     }
351 
352     protected RequestedActions determineRequestedActionsInternal(DocumentRouteHeaderValue documentBo, String principalId) {
353         Map<String, String> actionsRequested = KEWServiceLocator.getActionRequestService().getActionsRequested(documentBo,
354                 principalId, true);
355         boolean completeRequested = false;
356         boolean approveRequested = false;
357         boolean acknowledgeRequested = false;
358         boolean fyiRequested = false;
359         for (String actionRequestCode : actionsRequested.keySet()) {
360             if (ActionRequestType.FYI.getCode().equals(actionRequestCode)) {
361                 fyiRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
362             } else if (ActionRequestType.ACKNOWLEDGE.getCode().equals(actionRequestCode)) {
363                 acknowledgeRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
364             } else if (ActionRequestType.APPROVE.getCode().equals(actionRequestCode)) {
365                 approveRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
366             } else if (ActionRequestType.COMPLETE.getCode().equals(actionRequestCode)) {
367                 completeRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
368             }
369         }
370         return RequestedActions.create(completeRequested, approveRequested, acknowledgeRequested, fyiRequested);
371     }
372 
373     @Override
374     public DocumentDetail executeSimulation(RoutingReportCriteria reportCriteria) {
375         incomingParamCheck(reportCriteria, "reportCriteria");
376         if ( LOG.isDebugEnabled() ) {
377         	LOG.debug("Executing routing report [docId=" + reportCriteria.getDocumentId() + ", docTypeName=" + reportCriteria.getDocumentTypeName() + "]");
378         }
379         SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
380 
381         return DTOConverter.convertDocumentDetailNew(KEWServiceLocator.getRoutingReportService().report(criteria));
382     }
383 
384     protected DocumentActionResult constructDocumentActionResult(DocumentRouteHeaderValue documentBo, String principalId) {
385         Document document = DocumentRouteHeaderValue.to(documentBo);
386         ValidActions validActions = determineValidActionsInternal(documentBo, principalId);
387         RequestedActions requestedActions = determineRequestedActionsInternal(documentBo, principalId);
388         return DocumentActionResult.create(document, validActions, requestedActions);
389     }
390 
391     @Override
392     public DocumentActionResult acknowledge(DocumentActionParameters parameters) {
393         incomingParamCheck(parameters, "parameters");
394         return executeActionInternal(parameters, ACKNOWLEDGE_CALLBACK);
395     }
396 
397     @Override
398     public DocumentActionResult approve(DocumentActionParameters parameters) {
399         incomingParamCheck(parameters, "parameters");
400         return executeActionInternal(parameters, APPROVE_CALLBACK);
401     }
402 
403     @Override
404     public DocumentActionResult adHocToPrincipal(DocumentActionParameters parameters,
405             final AdHocToPrincipal adHocToPrincipal) {
406         incomingParamCheck(parameters, "parameters");
407         incomingParamCheck(adHocToPrincipal, "adHocToPrincipal");
408         return executeActionInternal(parameters,
409                 new DocumentActionCallback() {
410                     @Override
411                     public String getLogMessage(String documentId, String principalId, String annotation) {
412                         return "AdHoc Route To Principal [principalId=" + principalId +
413                                 ", docId=" + documentId +
414                                 ", actionRequest=" + adHocToPrincipal.getActionRequested() +
415                                 ", nodeName=" + adHocToPrincipal.getNodeName() +
416                                 ", targetPrincipalId=" + adHocToPrincipal.getTargetPrincipalId() +
417                                 ", forceAction=" + adHocToPrincipal.isForceAction() +
418                                 ", annotation=" + annotation +
419                                 ", requestLabel=" + adHocToPrincipal.getRequestLabel() + "]";
420                     }
421 
422                     @Override
423                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
424                             String principalId, String annotation) throws WorkflowException {
425                         return KEWServiceLocator.getWorkflowDocumentService().adHocRouteDocumentToPrincipal(
426                                 principalId,
427                                     documentBo,
428                                     adHocToPrincipal.getActionRequested().getCode(),
429                                     adHocToPrincipal.getNodeName(),
430                                     adHocToPrincipal.getPriority(),
431                                     annotation,
432                                     adHocToPrincipal.getTargetPrincipalId(),
433                                     adHocToPrincipal.getResponsibilityDescription(),
434                                     adHocToPrincipal.isForceAction(),
435                                     adHocToPrincipal.getRequestLabel());
436                     }
437                 });
438     }
439 
440     @Override
441     public DocumentActionResult adHocToPrincipal(DocumentActionParameters parameters, AdHocToPrincipal_v2_1_2 adHocToPrincipal) {
442         return adHocToPrincipal(parameters, AdHocToPrincipal_v2_1_2.to(adHocToPrincipal));
443     }
444 
445     @Override
446     public DocumentActionResult adHocToGroup(DocumentActionParameters parameters,
447             final AdHocToGroup adHocToGroup) {
448         incomingParamCheck(parameters, "parameters");
449         incomingParamCheck(adHocToGroup, "adHocToGroup");
450         return executeActionInternal(parameters,
451                 new DocumentActionCallback() {
452                     @Override
453                     public String getLogMessage(String documentId, String principalId, String annotation) {
454                         return "AdHoc Route To Group [principalId=" + principalId +
455                                 ", docId=" + documentId +
456                                 ", actionRequest=" + adHocToGroup.getActionRequested() +
457                                 ", nodeName=" + adHocToGroup.getNodeName() +
458                                 ", targetGroupId=" + adHocToGroup.getTargetGroupId() +
459                                 ", forceAction=" + adHocToGroup.isForceAction() +
460                                 ", annotation=" + annotation +
461                                 ", requestLabel=" + adHocToGroup.getRequestLabel() + "]";
462                     }
463 
464                     @Override
465                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
466                             String principalId, String annotation) throws WorkflowException {
467                         return KEWServiceLocator.getWorkflowDocumentService().adHocRouteDocumentToGroup(principalId,
468                                     documentBo,
469                                     adHocToGroup.getActionRequested().getCode(),
470                                     adHocToGroup.getNodeName(),
471                                     adHocToGroup.getPriority(),
472                                     annotation,
473                                     adHocToGroup.getTargetGroupId(),
474                                     adHocToGroup.getResponsibilityDescription(),
475                                     adHocToGroup.isForceAction(),
476                                     adHocToGroup.getRequestLabel());
477                     }
478                 });
479     }
480 
481     @Override
482     public DocumentActionResult adHocToGroup(DocumentActionParameters parameters, AdHocToGroup_v2_1_2 adHocToGroup) {
483         return adHocToGroup(parameters, AdHocToGroup_v2_1_2.to(adHocToGroup));
484     }
485 
486     @Override
487     public DocumentActionResult revokeAdHocRequestById(DocumentActionParameters parameters,
488             final String actionRequestId) {
489         incomingParamCheck(parameters, "parameters");
490         incomingParamCheck(actionRequestId, "actionRequestId");
491         return executeActionInternal(parameters,
492                 new DocumentActionCallback() {
493                     @Override
494                     public String getLogMessage(String documentId, String principalId, String annotation) {
495                         return "Revoke AdHoc from Principal [principalId=" + principalId +
496                                 ", documentId=" + documentId +
497                                 ", annotation=" + annotation +
498                                 ", actionRequestId=" + actionRequestId + "]";
499                     }
500 
501                     @Override
502                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
503                             String principalId, String annotation) throws WorkflowException {
504                         return KEWServiceLocator.getWorkflowDocumentService().revokeAdHocRequests(principalId,
505                                 documentBo, actionRequestId, annotation);
506                     }
507                 });
508     }
509 
510     @Override
511     public DocumentActionResult revokeAdHocRequests(DocumentActionParameters parameters,
512             final AdHocRevoke revoke) {
513         incomingParamCheck(parameters, "parameters");
514         incomingParamCheck(revoke, "revoke");
515         return executeActionInternal(parameters,
516                 new DocumentActionCallback() {
517                     @Override
518                     public String getLogMessage(String documentId, String principalId, String annotation) {
519                         return "Revoke AdHoc Requests [principalId=" + principalId +
520                                 ", docId=" + documentId +
521                                 ", annotation=" + annotation +
522                                 ", revoke=" + revoke.toString() + "]";
523                     }
524 
525                     @Override
526                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
527                             String principalId, String annotation) throws WorkflowException {
528                         return KEWServiceLocator.getWorkflowDocumentService().revokeAdHocRequests(principalId,
529                                 documentBo, revoke, annotation);
530                     }
531                 });
532     }
533 
534     @Override
535     public DocumentActionResult revokeAllAdHocRequests(DocumentActionParameters parameters) {
536         incomingParamCheck(parameters, "parameters");
537         return executeActionInternal(parameters,
538                 new DocumentActionCallback() {
539                     @Override
540                     public String getLogMessage(String documentId, String principalId, String annotation) {
541                         return "Revoke All AdHoc Requests [principalId=" + principalId +
542                                 ", docId=" + documentId +
543                                 ", annotation=" + annotation + "]";
544                     }
545 
546                     @Override
547                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
548                             String principalId, String annotation) throws WorkflowException {
549                         return KEWServiceLocator.getWorkflowDocumentService().revokeAdHocRequests(principalId,
550                                 documentBo, (AdHocRevoke) null, annotation);
551                     }
552                 });
553     }
554 
555     @Override
556     public DocumentActionResult cancel(DocumentActionParameters parameters) {
557         incomingParamCheck(parameters, "parameters");
558         return executeActionInternal(parameters, CANCEL_CALLBACK);
559     }
560 
561     @Override
562     public DocumentActionResult recall(DocumentActionParameters parameters, final boolean cancel) {
563         incomingParamCheck(parameters, "parameters");
564         incomingParamCheck(cancel, "cancel");
565         return executeActionInternal(parameters, new StandardDocumentActionCallback() {
566             public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
567                     String annotation) throws WorkflowException {
568                 return KEWServiceLocator.getWorkflowDocumentService().recallDocument(principalId, documentBo, annotation, cancel);
569             }
570             public String getActionName() {
571                 return ActionType.RECALL.getLabel();
572             }
573         });
574     }
575 
576     @Override
577     public DocumentActionResult clearFyi(DocumentActionParameters parameters) {
578         incomingParamCheck(parameters, "parameters");
579         return executeActionInternal(parameters, FYI_CALLBACK);
580     }
581 
582     @Override
583     public DocumentActionResult complete(DocumentActionParameters parameters) {
584         incomingParamCheck(parameters, "parameters");
585         return executeActionInternal(parameters, COMPLETE_CALLBACK);
586     }
587 
588     @Override
589     public DocumentActionResult disapprove(DocumentActionParameters parameters) {
590         incomingParamCheck(parameters, "parameters");
591         return executeActionInternal(parameters, DISAPPROVE_CALLBACK);
592     }
593 
594     @Override
595     public DocumentActionResult route(DocumentActionParameters parameters) {
596         incomingParamCheck(parameters, "parameters");
597         return executeActionInternal(parameters, ROUTE_CALLBACK);
598     }
599 
600     @Override
601     public DocumentActionResult blanketApprove(DocumentActionParameters parameters) {
602         incomingParamCheck(parameters, "parameters");
603         return executeActionInternal(parameters, BLANKET_APPROVE_CALLBACK);
604     }
605 
606     @Override
607     public DocumentActionResult blanketApproveToNodes(DocumentActionParameters parameters,
608             final Set<String> nodeNames) {
609         incomingParamCheck(parameters, "parameters");
610         incomingParamCheck(nodeNames, "nodeNames");
611         return executeActionInternal(parameters,
612                 new DocumentActionCallback() {
613                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
614                             String principalId, String annotation) throws WorkflowException {
615                         return KEWServiceLocator.getWorkflowDocumentService().blanketApproval(principalId, documentBo,
616                                 annotation, nodeNames);
617                     }
618 
619                     public String getLogMessage(String documentId, String principalId, String annotation) {
620                         return "Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
621                                 + ", annotation=" + annotation + ", nodeNames=" + nodeNames + "]";
622                     }
623                 });
624     }
625 
626     @Override
627     public DocumentActionResult returnToPreviousNode(DocumentActionParameters parameters,
628             final ReturnPoint returnPoint) {
629         incomingParamCheck(parameters, "parameters");
630         incomingParamCheck(returnPoint, "returnPoint");
631         return executeActionInternal(parameters,
632                 new DocumentActionCallback() {
633                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
634                             String principalId, String annotation) throws WorkflowException {
635                         return KEWServiceLocator.getWorkflowDocumentService().returnDocumentToPreviousNode(principalId,
636                                 documentBo, returnPoint.getNodeName(), annotation);
637                     }
638 
639                     public String getLogMessage(String documentId, String principalId, String annotation) {
640                         return "Return to Previous [principalId=" + principalId + ", documentId=" + documentId
641                                 + ", annotation=" + annotation + ", destNodeName=" + returnPoint.getNodeName() + "]";
642                     }
643                 });
644     }
645 
646     @Override
647     public DocumentActionResult move(DocumentActionParameters parameters,
648             final MovePoint movePoint) {
649         incomingParamCheck(parameters, "parameters");
650         incomingParamCheck(movePoint, "movePoint");
651         return executeActionInternal(parameters,
652                 new DocumentActionCallback() {
653                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
654                             String principalId, String annotation) throws WorkflowException {
655                         return KEWServiceLocator.getWorkflowDocumentService().moveDocument(principalId, documentBo,
656                                 movePoint, annotation);
657                     }
658 
659                     public String getLogMessage(String documentId, String principalId, String annotation) {
660                         return "Move Document [principalId=" + principalId + ", documentId=" + documentId
661                                 + ", annotation=" + annotation + ", movePoint=" + movePoint + "]";
662                     }
663                 });
664     }
665 
666     @Override
667     public DocumentActionResult takeGroupAuthority(DocumentActionParameters parameters,
668             final String groupId) {
669         incomingParamCheck(parameters, "parameters");
670         incomingParamCheck(groupId, "groupId");
671         return executeActionInternal(parameters,
672                 new StandardDocumentActionCallback() {
673                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
674                             String principalId, String annotation) throws WorkflowException {
675                         return KEWServiceLocator.getWorkflowDocumentService().takeGroupAuthority(principalId,
676                                 documentBo, groupId, annotation);
677                     }
678 
679                     public String getActionName() {
680                         return ActionType.TAKE_GROUP_AUTHORITY.getLabel();
681                     }
682                 });
683     }
684 
685     @Override
686     public DocumentActionResult releaseGroupAuthority(DocumentActionParameters parameters,
687             final String groupId) {
688         incomingParamCheck(parameters, "parameters");
689         incomingParamCheck(groupId, "groupId");
690         return executeActionInternal(parameters,
691                 new StandardDocumentActionCallback() {
692                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
693                             String principalId, String annotation) throws WorkflowException {
694                         return KEWServiceLocator.getWorkflowDocumentService().releaseGroupAuthority(principalId,
695                                 documentBo, groupId, annotation);
696                     }
697 
698                     public String getActionName() {
699                         return ActionType.RELEASE_GROUP_AUTHORITY.getLabel();
700                     }
701                 });
702 
703     }
704 
705     @Override
706     public DocumentActionResult save(DocumentActionParameters parameters) {
707         incomingParamCheck(parameters, "parameters");
708         return executeActionInternal(parameters, SAVE_CALLBACK);
709     }
710 
711     @Override
712     public DocumentActionResult saveDocumentData(DocumentActionParameters parameters) {
713         incomingParamCheck(parameters, "parameters");
714         return executeActionInternal(parameters, new DocumentActionCallback() {
715 
716             @Override
717             public String getLogMessage(String documentId, String principalId, String annotation) {
718                 return "Saving Routing Data [principalId=" + principalId + ", docId=" + documentId + "]";
719             }
720 
721             @Override
722             public DocumentRouteHeaderValue doInDocumentBo(
723                     DocumentRouteHeaderValue documentBo, String principalId,
724                     String annotation) throws WorkflowException {
725                 return KEWServiceLocator.getWorkflowDocumentService().saveRoutingData(principalId, documentBo);
726             }
727         });
728     }
729 
730     @Override
731     public Document delete(String documentId, String principalId) {
732         incomingParamCheck(documentId, "documentId");
733         incomingParamCheck(principalId, "principalId");
734         DocumentRouteHeaderValue documentBo = init(DocumentActionParameters.create(documentId, principalId, null));
735         if (LOG.isDebugEnabled()) {
736             LOG.debug("Delete [principalId=" + principalId + ", documentId=" + documentId + "]");
737         }
738         Document document = null;
739         try {
740             document = DocumentRouteHeaderValue.to(documentBo);
741             KEWServiceLocator.getWorkflowDocumentService().deleteDocument(principalId, documentBo);
742 
743         } catch (WorkflowException e) {
744             translateException(e);
745         }
746         return document;
747     }
748 
749     @Override
750     public void logAnnotation(String documentId, String principalId, String annotation) {
751         incomingParamCheck(documentId, "documentId");
752         incomingParamCheck(principalId, "principalId");
753         incomingParamCheck(annotation, "annotation");
754         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
755         try {
756             KEWServiceLocator.getWorkflowDocumentService().logDocumentAction(principalId, documentBo, annotation);
757         } catch (WorkflowException e) {
758             translateException(e);
759         }
760     }
761 
762     @Override
763     public void initiateIndexing(String documentId) {
764         incomingParamCheck(documentId, "documentId");
765         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
766         if (documentBo.getDocumentType().hasSearchableAttributes()) {
767             DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentBo.getDocumentType().getApplicationId());
768             queue.indexDocument(documentId);
769         }
770     }
771 
772     @Override
773     public DocumentActionResult superUserBlanketApprove(DocumentActionParameters parameters,
774             final boolean executePostProcessor) {
775         incomingParamCheck(parameters, "parameters");
776         return executeActionInternal(parameters,
777                 new DocumentActionCallback() {
778                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
779                             String principalId, String annotation) throws WorkflowException {
780                         return KEWServiceLocator.getWorkflowDocumentService().superUserApprove(principalId, documentBo,
781                                 annotation, executePostProcessor);
782                     }
783 
784                     public String getLogMessage(String documentId, String principalId, String annotation) {
785                         return "SU Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
786                                 + ", annotation=" + annotation + "]";
787                     }
788                 });
789     }
790 
791     @Override
792     public DocumentActionResult superUserNodeApprove(DocumentActionParameters parameters,
793             final boolean executePostProcessor, final String nodeName) {
794                 incomingParamCheck(parameters, "parameters");
795                 incomingParamCheck(nodeName, "nodeName");
796         return executeActionInternal(parameters,
797                 new DocumentActionCallback() {
798                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
799                             String principalId, String annotation) throws WorkflowException {
800                         return KEWServiceLocator.getWorkflowDocumentService().superUserNodeApproveAction(principalId,
801                                 documentBo, nodeName, annotation, executePostProcessor);
802                     }
803 
804                     public String getLogMessage(String documentId, String principalId, String annotation) {
805                         return "SU Node Approve Action [principalId=" + principalId + ", documentId=" + documentId
806                                 + ", nodeName=" + nodeName + ", annotation=" + annotation + "]";
807                     }
808                 });
809 
810     }
811 
812     @Override
813     public DocumentActionResult superUserTakeRequestedAction(DocumentActionParameters parameters,
814             final boolean executePostProcessor, final String actionRequestId) {
815                 incomingParamCheck(parameters, "parameters");
816                 incomingParamCheck(actionRequestId, "actionRequestId");
817         return executeActionInternal(parameters,
818                 new DocumentActionCallback() {
819                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
820                             String principalId, String annotation) throws WorkflowException {
821                         return KEWServiceLocator.getWorkflowDocumentService().superUserActionRequestApproveAction(
822                                 principalId, documentBo, actionRequestId, annotation,
823                                 executePostProcessor);
824                     }
825 
826                     public String getLogMessage(String documentId, String principalId, String annotation) {
827                         return "SU Take Requested Action [principalId=" + principalId + ", docume tId=" + documentId
828                                 + ", actionRequestId=" + actionRequestId + ", annotation=" + annotation + "]";
829                     }
830                 });
831     }
832 
833     @Override
834     public DocumentActionResult superUserDisapprove(DocumentActionParameters parameters,
835             final boolean executePostProcessor) {
836                         incomingParamCheck(parameters, "parameters");
837         return executeActionInternal(parameters,
838                 new DocumentActionCallback() {
839                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
840                             String principalId, String annotation) throws WorkflowException {
841                         return KEWServiceLocator.getWorkflowDocumentService().superUserDisapproveAction(principalId,
842                                 documentBo, annotation, executePostProcessor);
843                     }
844 
845                     public String getLogMessage(String documentId, String principalId, String annotation) {
846                         return "SU Disapprove [principalId=" + principalId + ", documentId=" + documentId
847                                 + ", annotation=" + annotation + "]";
848                     }
849                 });
850     }
851 
852     @Override
853     public DocumentActionResult superUserCancel(DocumentActionParameters parameters, final boolean executePostProcessor) {
854                         incomingParamCheck(parameters, "parameters");
855         return executeActionInternal(parameters,
856                 new DocumentActionCallback() {
857                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
858                             String principalId, String annotation) throws WorkflowException {
859                         return KEWServiceLocator.getWorkflowDocumentService().superUserCancelAction(principalId,
860                                 documentBo, annotation, executePostProcessor);
861                     }
862 
863                     public String getLogMessage(String documentId, String principalId, String annotation) {
864                         return "SU Cancel [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
865                                 + annotation + "]";
866                     }
867                 });
868     }
869 
870     @Override
871     public DocumentActionResult superUserReturnToPreviousNode(DocumentActionParameters parameters,
872             final boolean executePostProcessor, final ReturnPoint returnPoint) {
873             incomingParamCheck(parameters, "parameters");
874             incomingParamCheck(returnPoint, "returnPoint");
875         return executeActionInternal(parameters,
876                 new DocumentActionCallback() {
877                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
878                             String principalId, String annotation) throws WorkflowException {
879                         return KEWServiceLocator.getWorkflowDocumentService().superUserReturnDocumentToPreviousNode(
880                                 principalId, documentBo, returnPoint.getNodeName(), annotation, executePostProcessor);
881                     }
882 
883                     public String getLogMessage(String documentId, String principalId, String annotation) {
884                         return "SU Return to Previous Node [principalId=" + principalId + ", documentId=" + documentId
885                                 + ", annotation=" + annotation + ", returnPoint=" + returnPoint + "]";
886                     }
887                 });
888 
889     }
890 
891     @Override
892     public DocumentActionResult placeInExceptionRouting(DocumentActionParameters parameters) {
893         incomingParamCheck(parameters, "parameters");
894         return executeActionInternal(parameters, PLACE_IN_EXCEPTION_CALLBACK);
895     }
896 
897     @Override
898     public boolean documentWillHaveAtLeastOneActionRequest(RoutingReportCriteria reportCriteria, List<String> actionRequestedCodes, boolean ignoreCurrentActionRequests) {
899         incomingParamCheck(reportCriteria, "reportCriteria");
900         incomingParamCheck(actionRequestedCodes, "actionRequestedCodes");
901         try {
902 	        SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
903 	        SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
904 	        // set activate requests to true by default so force action works correctly
905 	        criteria.setActivateRequests(Boolean.TRUE);
906 	        SimulationResults results = simulationEngine.runSimulation(criteria);
907             List<ActionRequestValue> actionRequestsToProcess = results.getSimulatedActionRequests();
908             if (!ignoreCurrentActionRequests) {
909                 actionRequestsToProcess.addAll(results.getDocument().getActionRequests());
910             }
911             for (ActionRequestValue actionRequest : actionRequestsToProcess) {
912                 if (actionRequest.isDone()) {
913                     // an action taken has eliminated this request from being active
914                     continue;
915                 }
916 				// if no action request codes are passed in.... assume any request found is
917 		    	if (CollectionUtils.isEmpty(actionRequestedCodes) ) {
918 		    		// we found an action request
919 		    		return true;
920 		    	}
921 		    	// check the action requested codes passed in
922 		    	for (String requestedActionRequestCode : actionRequestedCodes) {
923 					if (requestedActionRequestCode.equals(actionRequest.getActionRequested())) {
924 					    boolean satisfiesDestinationUserCriteria = (criteria.getDestinationRecipients().isEmpty()) || (isRecipientRoutedRequest(actionRequest,criteria.getDestinationRecipients()));
925 					    if (satisfiesDestinationUserCriteria) {
926 					        if (StringUtils.isBlank(criteria.getDestinationNodeName())) {
927 					            return true;
928 					        } else if (StringUtils.equals(criteria.getDestinationNodeName(),actionRequest.getNodeInstance().getName())) {
929 					            return true;
930 					        }
931 					    }
932 					}
933 				}
934 			}
935 	        return false;
936         } catch (Exception ex) {
937         	String error = "Problems evaluating documentWillHaveAtLeastOneActionRequest: " + ex.getMessage();
938             LOG.error(error,ex);
939             if (ex instanceof RuntimeException) {
940             	throw (RuntimeException)ex;
941             }
942             throw new RuntimeException(error, ex);
943         }
944     }
945 
946     private boolean isRecipientRoutedRequest(ActionRequestValue actionRequest, List<Recipient> recipients) throws WorkflowException {
947         for (Recipient recipient : recipients) {
948             if (actionRequest.isRecipientRoutedRequest(recipient)) {
949                 return true;
950             }
951         }
952         return false;
953     }
954 
955     @Override
956     public void reResolveRoleByDocTypeName(String documentTypeName, String roleName, String qualifiedRoleNameLabel) {
957         incomingParamCheck(documentTypeName, "documentTypeName");
958         incomingParamCheck(roleName, "roleName");
959         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
960         if ( LOG.isDebugEnabled() ) {
961         	LOG.debug("Re-resolving Role [docTypeName=" + documentTypeName + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
962         }
963     	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
964     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
965     		KEWServiceLocator.getRoleService().reResolveRole(documentType, roleName);
966     	} else {
967     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(documentType, roleName, qualifiedRoleNameLabel);
968     	}
969     }
970 
971     public void reResolveRoleByDocumentId(String documentId, String roleName, String qualifiedRoleNameLabel) {
972         incomingParamCheck(documentId, "documentId");
973         incomingParamCheck(roleName, "roleName");
974         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
975         if ( LOG.isDebugEnabled() ) {
976         	LOG.debug("Re-resolving Role [documentId=" + documentId + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
977         }
978         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
979     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
980     		KEWServiceLocator.getRoleService().reResolveRole(routeHeader, roleName);
981     	} else {
982     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(routeHeader, roleName, qualifiedRoleNameLabel);
983     	}
984     }
985 
986     @Override
987     public List<RemotableAttributeError> validateWorkflowAttributeDefinition(
988             WorkflowAttributeDefinition definition) {
989         if (definition == null) {
990             throw new RiceIllegalArgumentException("definition was null");
991         }
992         if ( LOG.isDebugEnabled() ) {
993             LOG.debug("Validating WorkflowAttributeDefinition [attributeName="+definition.getAttributeName()+"]");
994         }
995         AttributeDefinition attributeDefinition = DTOConverter.convertWorkflowAttributeDefinition(definition);
996         WorkflowRuleAttribute attribute = null;
997         if (attributeDefinition != null) {
998             attribute = (WorkflowRuleAttribute) GlobalResourceLoader.getObject(attributeDefinition.getObjectDefinition());
999         }
1000         if (attribute instanceof GenericXMLRuleAttribute) {
1001             Map<String, String> attributePropMap = new HashMap<String, String>();
1002             GenericXMLRuleAttribute xmlAttribute = (GenericXMLRuleAttribute)attribute;
1003             xmlAttribute.setExtensionDefinition(attributeDefinition.getExtensionDefinition());
1004             for (PropertyDefinition propertyDefinition : definition.getPropertyDefinitions()) {
1005                 attributePropMap.put(propertyDefinition.getName(), propertyDefinition.getValue());
1006             }
1007             xmlAttribute.setParamMap(attributePropMap);
1008     }
1009         List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
1010         //validate inputs from client application if the attribute is capable
1011         if (attribute instanceof WorkflowAttributeXmlValidator) {
1012             List<? extends RemotableAttributeErrorContract> validationErrors = ((WorkflowAttributeXmlValidator)attribute).validateClientRoutingData();
1013             if (validationErrors != null) {
1014                 for (RemotableAttributeErrorContract validationError : validationErrors) {
1015                     errors.add(RemotableAttributeError.Builder.create(validationError).build());
1016                 }
1017             }
1018         }
1019         return errors;
1020     }
1021 
1022     @Override
1023     public boolean isFinalApprover(String documentId, String principalId) {
1024         incomingParamCheck(documentId, "documentId");
1025         incomingParamCheck(principalId, "principalId");
1026         if ( LOG.isDebugEnabled() ) {
1027         	LOG.debug("Evaluating isFinalApprover [docId=" + documentId + ", principalId=" + principalId + "]");
1028         }
1029         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1030         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(documentId);
1031         List<RouteNode> finalApproverNodes = KEWServiceLocator.getRouteNodeService().findFinalApprovalRouteNodes(routeHeader.getDocumentType().getDocumentTypeId());
1032         if (finalApproverNodes.isEmpty()) {
1033         	if ( LOG.isDebugEnabled() ) {
1034         		LOG.debug("Could not locate final approval nodes for document " + documentId);
1035         	}
1036             return false;
1037         }
1038         Set<String> finalApproverNodeNames = new HashSet<String>();
1039         for (RouteNode node : finalApproverNodes) {
1040             finalApproverNodeNames.add(node.getRouteNodeName());
1041         }
1042 
1043         int approveRequest = 0;
1044         for (ActionRequestValue request : requests) {
1045             RouteNodeInstance nodeInstance = request.getNodeInstance();
1046             if (nodeInstance == null) {
1047             	if ( LOG.isDebugEnabled() ) {
1048             		LOG.debug("Found an action request on the document with a null node instance, indicating EXCEPTION routing.");
1049             	}
1050                 return false;
1051             }
1052             if (finalApproverNodeNames.contains(nodeInstance.getRouteNode().getRouteNodeName())) {
1053                 if (request.isApproveOrCompleteRequest()) {
1054                     approveRequest++;
1055                     if ( LOG.isDebugEnabled() ) {
1056                     	LOG.debug("Found request is approver " + request.getActionRequestId());
1057                     }
1058                     if (! request.isRecipientRoutedRequest(principalId)) {
1059                     	if ( LOG.isDebugEnabled() ) {
1060                     		LOG.debug("Action Request not for user " + principalId);
1061                     	}
1062                         return false;
1063                     }
1064                 }
1065             }
1066         }
1067 
1068         if (approveRequest == 0) {
1069             return false;
1070         }
1071         if ( LOG.isDebugEnabled() ) {
1072         	LOG.debug("Principal "+principalId+" is final approver for document " + documentId);
1073         }
1074         return true;
1075     }
1076 
1077     @Override
1078     public boolean routeNodeHasApproverActionRequest(String documentTypeName, String docContent, String nodeName) {
1079         incomingParamCheck(documentTypeName, "documentTypeName");
1080         incomingParamCheck(docContent, "docContent");
1081         incomingParamCheck(nodeName, "nodeName");
1082         if ( LOG.isDebugEnabled() ) {
1083         	LOG.debug("Evaluating routeNodeHasApproverActionRequest [docTypeName=" + documentTypeName + ", nodeName=" + nodeName + "]");
1084         }
1085         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
1086         RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(documentType.getDocumentTypeId(), nodeName);
1087         return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, new Integer(KewApiConstants.INVALID_ROUTE_LEVEL));
1088     }
1089 
1090     /**
1091      * Really this method needs to be implemented using the executeSimulation functionality (the SimulationEngine).
1092      * This would get rid of the needs for us to call to FlexRM directly.
1093      */
1094     private boolean routeNodeHasApproverActionRequest(DocumentType documentType, String docContent, RouteNode node, Integer routeLevel) {
1095         incomingParamCheck(documentType, "documentType");
1096         incomingParamCheck(docContent, "docContent");
1097         incomingParamCheck(node, "node");
1098         incomingParamCheck(routeLevel, "routeLevel");
1099 
1100 /*        DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
1101         routeHeader.setDocumentId("");
1102         routeHeader.setDocumentTypeId(documentType.getDocumentTypeId());
1103         routeHeader.setDocRouteLevel(routeLevel);
1104         routeHeader.setDocVersion(new Integer(KewApiConstants.DocumentContentVersions.CURRENT));*/
1105 
1106         RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(documentType.getName());
1107         builder.setNodeNames(Collections.singletonList(node.getName()));
1108         builder.setXmlContent(docContent);
1109         DocumentDetail docDetail = executeSimulation(builder.build());
1110         if (docDetail != null) {
1111             for (ActionRequest actionRequest : docDetail.getActionRequests()) {
1112                 if (actionRequest.isApprovalRequest()) {
1113                     return true;
1114                 }
1115             }
1116         }
1117         /*if (node.getRuleTemplate() != null && node.isFlexRM()) {
1118             String ruleTemplateName = node.getRuleTemplate().getName();
1119             builder.setXmlContent(docContent);
1120             routeHeader.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
1121             FlexRM flexRM = new FlexRM();
1122     		RouteContext context = RouteContext.getCurrentRouteContext();
1123     		context.setDocument(routeHeader);
1124     		try {
1125     			List actionRequests = flexRM.getActionRequests(routeHeader, node, null, ruleTemplateName);
1126     			for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
1127     				ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
1128     				if (actionRequest.isApproveOrCompleteRequest()) {
1129     					return true;
1130     				}
1131     			}
1132     		} finally {
1133     			RouteContext.clearCurrentRouteContext();
1134     		}
1135         }*/
1136         return false;
1137     }
1138 
1139     @Override
1140     public boolean isLastApproverAtNode(String documentId, String principalId, String nodeName)  {
1141         incomingParamCheck(documentId, "documentId");
1142         incomingParamCheck(principalId, "principalId");
1143         incomingParamCheck(nodeName, "nodeName");
1144         if ( LOG.isDebugEnabled() ) {
1145         	LOG.debug("Evaluating isLastApproverAtNode [docId=" + documentId + ", principalId=" + principalId + ", nodeName=" + nodeName + "]");
1146         }
1147         loadDocument(documentId);
1148         // If this app constant is set to true, then we will attempt to simulate activation of non-active requests before
1149         // attempting to deactivate them, this is in order to address the force action issue reported by EPIC in issue
1150         // http://fms.dfa.cornell.edu:8080/browse/KULWF-366
1151         Boolean activateFirst = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
1152                 KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.FEATURE_DETAIL_TYPE, KewApiConstants.IS_LAST_APPROVER_ACTIVATE_FIRST_IND);
1153         if (activateFirst == null) {
1154             activateFirst = Boolean.FALSE;
1155         }
1156 
1157         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDocRequestCdNodeName(documentId, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, nodeName);
1158         if (requests == null || requests.isEmpty()) {
1159             return false;
1160         }
1161 
1162         // Deep-copy the action requests for the simulation.
1163         List<ActionRequestValue> copiedRequests = new ArrayList<ActionRequestValue>();
1164         for (ActionRequestValue request : requests) {
1165         	ActionRequestValue actionRequest = (ActionRequestValue) ObjectUtils.deepCopy(
1166                     (ActionRequestValue) request);
1167         	// Deep-copy the action items as well, since they are indirectly retrieved from the action request via service calls.
1168         	for (ActionItem actionItem : actionRequest.getActionItems()) {
1169         		actionRequest.getSimulatedActionItems().add((ActionItem) ObjectUtils.deepCopy(actionItem));
1170         	}
1171         	copiedRequests.add(actionRequest);
1172         }
1173 
1174         ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
1175         for (ActionRequestValue request : copiedRequests) {
1176             if (activateFirst.booleanValue() && !request.isActive()) {
1177                 KEWServiceLocator.getActionRequestService().activateRequest(request, activationContext);
1178             }
1179             if (request.isUserRequest() && request.getPrincipalId().equals(principalId)) {
1180                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1181             } else if (request.isGroupRequest() && KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, request.getGroup().getId())) {
1182                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1183             }
1184         }
1185         boolean allDeactivated = true;
1186         for (ActionRequestValue actionRequest: copiedRequests) {
1187             allDeactivated = allDeactivated && actionRequest.isDeactivated();
1188         }
1189         return allDeactivated;
1190     }
1191 
1192     @Override
1193     public boolean isUserInRouteLog(String documentId, String principalId, boolean lookFuture) {
1194     	incomingParamCheck(documentId, "documentId");
1195         incomingParamCheck(principalId, "principalId");
1196         return isUserInRouteLogWithOptionalFlattening(documentId, principalId, lookFuture, false);
1197     }
1198 
1199     @Override
1200     public boolean isUserInRouteLogWithOptionalFlattening(String documentId, String principalId, boolean lookFuture, boolean flattenNodes) {
1201         incomingParamCheck(documentId, "documentId");
1202         incomingParamCheck(principalId, "principalId");
1203         boolean authorized = false;
1204         if ( LOG.isDebugEnabled() ) {
1205         	LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", principalId=" + principalId + ", lookFuture=" + lookFuture + "]");
1206         }
1207         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1208         if (routeHeader == null) {
1209             throw new IllegalArgumentException("Document for documentId: " + documentId + " does not exist");
1210         }
1211         Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
1212         if (principal == null) {
1213             throw new IllegalArgumentException("Principal for principalId: " + principalId + " does not exist");
1214         }
1215         List<ActionTakenValue> actionsTaken = KEWServiceLocator.getActionTakenService().findByDocumentIdWorkflowId(documentId, principal.getPrincipalId());
1216 
1217         if(routeHeader.getInitiatorWorkflowId().equals(principal.getPrincipalId())){
1218         	return true;
1219         }
1220 
1221         if (!actionsTaken.isEmpty()) {
1222         	LOG.debug("found action taken by user");
1223         	authorized = true;
1224         }
1225 
1226         List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1227         if (actionRequestListHasPrincipal(principal, actionRequests)) {
1228         	authorized = true;
1229         }
1230 
1231         if (!lookFuture || authorized) {
1232         	return authorized;
1233         }
1234 
1235 
1236         SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1237         SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1238         criteria.setDestinationNodeName(null); // process entire document to conclusion
1239         criteria.getDestinationRecipients().add(new KimPrincipalRecipient(principal));
1240         criteria.setFlattenNodes(flattenNodes);
1241 
1242         try {
1243         	SimulationResults results = simulationEngine.runSimulation(criteria);
1244         	if (actionRequestListHasPrincipal(principal, results.getSimulatedActionRequests())) {
1245         		authorized = true;
1246         	}
1247         } catch (Exception e) {
1248         	throw new RiceRuntimeException(e);
1249         }
1250 
1251         return authorized;
1252     }
1253 
1254     private boolean actionRequestListHasPrincipal(Principal principal, List<ActionRequestValue> actionRequests) {
1255         for (ActionRequestValue actionRequest : actionRequests) {
1256             if (actionRequest.isRecipientRoutedRequest(new KimPrincipalRecipient(principal))) {
1257                 return true;
1258             }
1259         }
1260         return false;
1261     }
1262 
1263     public List<String> getPrincipalIdsInRouteLog(String documentId, boolean lookFuture) {
1264         if (StringUtils.isEmpty(documentId)) {
1265             throw new IllegalArgumentException("documentId passed in is null or blank");
1266         }
1267     	Set<String> principalIds = new HashSet<String>();
1268         try {
1269         	if ( LOG.isDebugEnabled() ) {
1270         		LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", lookFuture=" + lookFuture + "]");
1271         	}
1272             DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1273             List<ActionTakenValue> actionsTakens =
1274             	(List<ActionTakenValue>)KEWServiceLocator.getActionTakenService().findByDocumentId(documentId);
1275             //TODO: confirm that the initiator is not already there in the actionstaken
1276             principalIds.add(routeHeader.getInitiatorWorkflowId());
1277             for(ActionTakenValue actionTaken: actionsTakens){
1278             	principalIds.add(actionTaken.getPrincipalId());
1279             }
1280             List<ActionRequestValue> actionRequests =
1281             	KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1282             for(ActionRequestValue actionRequest: actionRequests){
1283             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1284             }
1285             if (!lookFuture) {
1286             	return new ArrayList<String>(principalIds);
1287             }
1288             SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1289             SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1290             criteria.setDestinationNodeName(null); // process entire document to conclusion
1291             SimulationResults results = simulationEngine.runSimulation(criteria);
1292             actionRequests = (List<ActionRequestValue>)results.getSimulatedActionRequests();
1293             for(ActionRequestValue actionRequest: actionRequests){
1294             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1295             }
1296         } catch (Exception ex) {
1297             LOG.warn("Problems getting principalIds in Route Log for documentId: "+documentId+". Exception:"+ex.getMessage(),ex);
1298         }
1299     	return new ArrayList<String>(principalIds);
1300     }
1301 
1302     private DocumentRouteHeaderValue loadDocument(String documentId) {
1303         return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
1304     }
1305 
1306     /**
1307 	 * This method gets all of the principalIds for the given ActionRequestValue.  It drills down into
1308 	 * groups if need be.
1309 	 *
1310 	 * @param actionRequest
1311 	 */
1312 	private List<String> getPrincipalIdsForActionRequest(ActionRequestValue actionRequest) {
1313 		List<String> results = Collections.emptyList();
1314 		if (actionRequest.getPrincipalId() != null) {
1315 			results = Collections.singletonList(actionRequest.getPrincipalId());
1316 		} else if (actionRequest.getGroupId() != null) {
1317 			List<String> principalIdsForGroup =
1318 				KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
1319 			if (principalIdsForGroup != null) {
1320 				results = principalIdsForGroup;
1321 			}
1322 		}
1323 		return results;
1324 	}
1325 
1326     private void incomingParamCheck(Object object, String name) {
1327         if (object == null) {
1328             throw new RiceIllegalArgumentException(name + " was null");
1329         } else if (object instanceof String
1330                 && StringUtils.isBlank((String) object)) {
1331             throw new RiceIllegalArgumentException(name + " was blank");
1332         }
1333     }
1334 
1335     public void setDocumentTypeService(DocumentTypeService documentTypeService) {
1336         this.documentTypeService = documentTypeService;
1337     }
1338 
1339     /**
1340      * TODO - this code is temporary until we get rid of all the crazy throwing of
1341      * "WorkflowException"
1342      */
1343     private void translateException(WorkflowException e) {
1344         if (e instanceof org.kuali.rice.kew.api.exception.InvalidActionTakenException) {
1345             throw new InvalidActionTakenException(e.getMessage(), e);
1346         }
1347         throw new WorkflowRuntimeException(e.getMessage(), e);
1348     }
1349 
1350     protected DocumentActionResult executeActionInternal(DocumentActionParameters parameters,
1351             DocumentActionCallback callback) {
1352         if (parameters == null) {
1353             throw new RiceIllegalArgumentException("Document action parameters was null.");
1354         }
1355         if (LOG.isDebugEnabled()) {
1356             LOG.debug(callback.getLogMessage(parameters.getDocumentId(), parameters.getPrincipalId(),
1357                     parameters.getAnnotation()));
1358         }
1359         DocumentRouteHeaderValue documentBo = init(parameters);
1360         try {
1361             documentBo = callback.doInDocumentBo(documentBo, parameters.getPrincipalId(), parameters.getAnnotation());
1362         } catch (WorkflowException e) {
1363             // TODO fix this up once the checked exception goes away
1364             translateException(e);
1365         }
1366         return constructDocumentActionResult(documentBo, parameters.getPrincipalId());
1367     }
1368 
1369     protected static interface DocumentActionCallback {
1370 
1371         DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
1372                 String annotation) throws WorkflowException;
1373 
1374         String getLogMessage(String documentId, String principalId, String annotation);
1375 
1376     }
1377 
1378     protected static abstract class StandardDocumentActionCallback implements DocumentActionCallback {
1379 
1380         public final String getLogMessage(String documentId, String principalId, String annotation) {
1381             return getActionName() + " [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
1382                     + annotation + "]";
1383         }
1384 
1385         protected abstract String getActionName();
1386 
1387     }
1388 
1389 }