View Javadoc

1   /**
2    * Copyright 2005-2012 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.RemotableAttributeErrorContract;
25  import org.kuali.rice.core.api.uif.RemotableAttributeError;
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.KewApiServiceLocator;
33  import org.kuali.rice.kew.api.WorkflowRuntimeException;
34  import org.kuali.rice.kew.api.action.ActionRequest;
35  import org.kuali.rice.kew.api.action.ActionRequestType;
36  import org.kuali.rice.kew.api.action.ActionType;
37  import org.kuali.rice.kew.api.action.AdHocRevoke;
38  import org.kuali.rice.kew.api.action.AdHocToGroup;
39  import org.kuali.rice.kew.api.action.AdHocToGroup_v2_1_2;
40  import org.kuali.rice.kew.api.action.AdHocToPrincipal;
41  import org.kuali.rice.kew.api.action.AdHocToPrincipal_v2_1_2;
42  import org.kuali.rice.kew.api.action.DocumentActionParameters;
43  import org.kuali.rice.kew.api.action.DocumentActionResult;
44  import org.kuali.rice.kew.api.action.InvalidActionTakenException;
45  import org.kuali.rice.kew.api.action.MovePoint;
46  import org.kuali.rice.kew.api.action.RequestedActions;
47  import org.kuali.rice.kew.api.action.ReturnPoint;
48  import org.kuali.rice.kew.api.action.RoutingReportCriteria;
49  import org.kuali.rice.kew.api.action.ValidActions;
50  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
51  import org.kuali.rice.kew.api.doctype.DocumentTypeService;
52  import org.kuali.rice.kew.api.doctype.IllegalDocumentTypeException;
53  import org.kuali.rice.kew.api.document.Document;
54  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
55  import org.kuali.rice.kew.api.document.DocumentDetail;
56  import org.kuali.rice.kew.api.document.DocumentUpdate;
57  import org.kuali.rice.kew.api.document.PropertyDefinition;
58  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
59  import org.kuali.rice.kew.api.exception.WorkflowException;
60  import org.kuali.rice.kew.definition.AttributeDefinition;
61  import org.kuali.rice.kew.doctype.bo.DocumentType;
62  import org.kuali.rice.kew.dto.DTOConverter;
63  import org.kuali.rice.kew.engine.ActivationContext;
64  import org.kuali.rice.kew.engine.node.RouteNode;
65  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
66  import org.kuali.rice.kew.engine.simulation.SimulationCriteria;
67  import org.kuali.rice.kew.engine.simulation.SimulationResults;
68  import org.kuali.rice.kew.engine.simulation.SimulationWorkflowEngine;
69  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
70  import org.kuali.rice.kew.rule.WorkflowRuleAttribute;
71  import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
72  import org.kuali.rice.kew.rule.bo.RuleAttribute;
73  import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
74  import org.kuali.rice.kew.service.KEWServiceLocator;
75  import org.kuali.rice.kew.api.KewApiConstants;
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         // TODO ewestfal - THIS METHOD NEEDS JAVADOCS
766         throw new UnsupportedOperationException("implement me!!!");
767     }
768 
769     @Override
770     public DocumentActionResult superUserBlanketApprove(DocumentActionParameters parameters,
771             final boolean executePostProcessor) {
772         incomingParamCheck(parameters, "parameters");
773         return executeActionInternal(parameters,
774                 new DocumentActionCallback() {
775                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
776                             String principalId, String annotation) throws WorkflowException {
777                         return KEWServiceLocator.getWorkflowDocumentService().superUserApprove(principalId, documentBo,
778                                 annotation, executePostProcessor);
779                     }
780 
781                     public String getLogMessage(String documentId, String principalId, String annotation) {
782                         return "SU Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
783                                 + ", annotation=" + annotation + "]";
784                     }
785                 });
786     }
787 
788     @Override
789     public DocumentActionResult superUserNodeApprove(DocumentActionParameters parameters,
790             final boolean executePostProcessor, final String nodeName) {
791                 incomingParamCheck(parameters, "parameters");
792                 incomingParamCheck(nodeName, "nodeName");
793         return executeActionInternal(parameters,
794                 new DocumentActionCallback() {
795                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
796                             String principalId, String annotation) throws WorkflowException {
797                         return KEWServiceLocator.getWorkflowDocumentService().superUserNodeApproveAction(principalId,
798                                 documentBo, nodeName, annotation, executePostProcessor);
799                     }
800 
801                     public String getLogMessage(String documentId, String principalId, String annotation) {
802                         return "SU Node Approve Action [principalId=" + principalId + ", documentId=" + documentId
803                                 + ", nodeName=" + nodeName + ", annotation=" + annotation + "]";
804                     }
805                 });
806 
807     }
808 
809     @Override
810     public DocumentActionResult superUserTakeRequestedAction(DocumentActionParameters parameters,
811             final boolean executePostProcessor, final String actionRequestId) {
812                 incomingParamCheck(parameters, "parameters");
813                 incomingParamCheck(actionRequestId, "actionRequestId");
814         return executeActionInternal(parameters,
815                 new DocumentActionCallback() {
816                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
817                             String principalId, String annotation) throws WorkflowException {
818                         return KEWServiceLocator.getWorkflowDocumentService().superUserActionRequestApproveAction(
819                                 principalId, documentBo, actionRequestId, annotation,
820                                 executePostProcessor);
821                     }
822 
823                     public String getLogMessage(String documentId, String principalId, String annotation) {
824                         return "SU Take Requested Action [principalId=" + principalId + ", docume tId=" + documentId
825                                 + ", actionRequestId=" + actionRequestId + ", annotation=" + annotation + "]";
826                     }
827                 });
828     }
829 
830     @Override
831     public DocumentActionResult superUserDisapprove(DocumentActionParameters parameters,
832             final boolean executePostProcessor) {
833                         incomingParamCheck(parameters, "parameters");
834         return executeActionInternal(parameters,
835                 new DocumentActionCallback() {
836                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
837                             String principalId, String annotation) throws WorkflowException {
838                         return KEWServiceLocator.getWorkflowDocumentService().superUserDisapproveAction(principalId,
839                                 documentBo, annotation, executePostProcessor);
840                     }
841 
842                     public String getLogMessage(String documentId, String principalId, String annotation) {
843                         return "SU Disapprove [principalId=" + principalId + ", documentId=" + documentId
844                                 + ", annotation=" + annotation + "]";
845                     }
846                 });
847     }
848 
849     @Override
850     public DocumentActionResult superUserCancel(DocumentActionParameters parameters, final boolean executePostProcessor) {
851                         incomingParamCheck(parameters, "parameters");
852         return executeActionInternal(parameters,
853                 new DocumentActionCallback() {
854                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
855                             String principalId, String annotation) throws WorkflowException {
856                         return KEWServiceLocator.getWorkflowDocumentService().superUserCancelAction(principalId,
857                                 documentBo, annotation, executePostProcessor);
858                     }
859 
860                     public String getLogMessage(String documentId, String principalId, String annotation) {
861                         return "SU Cancel [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
862                                 + annotation + "]";
863                     }
864                 });
865     }
866 
867     @Override
868     public DocumentActionResult superUserReturnToPreviousNode(DocumentActionParameters parameters,
869             final boolean executePostProcessor, final ReturnPoint returnPoint) {
870             incomingParamCheck(parameters, "parameters");
871             incomingParamCheck(returnPoint, "returnPoint");
872         return executeActionInternal(parameters,
873                 new DocumentActionCallback() {
874                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
875                             String principalId, String annotation) throws WorkflowException {
876                         return KEWServiceLocator.getWorkflowDocumentService().superUserReturnDocumentToPreviousNode(
877                                 principalId, documentBo, returnPoint.getNodeName(), annotation, executePostProcessor);
878                     }
879 
880                     public String getLogMessage(String documentId, String principalId, String annotation) {
881                         return "SU Return to Previous Node [principalId=" + principalId + ", documentId=" + documentId
882                                 + ", annotation=" + annotation + ", returnPoint=" + returnPoint + "]";
883                     }
884                 });
885 
886     }
887 
888     @Override
889     public DocumentActionResult placeInExceptionRouting(DocumentActionParameters parameters) {
890         incomingParamCheck(parameters, "parameters");
891         return executeActionInternal(parameters, PLACE_IN_EXCEPTION_CALLBACK);
892     }
893 
894     @Override
895     public boolean documentWillHaveAtLeastOneActionRequest(RoutingReportCriteria reportCriteria, List<String> actionRequestedCodes, boolean ignoreCurrentActionRequests) {
896         incomingParamCheck(reportCriteria, "reportCriteria");
897         incomingParamCheck(actionRequestedCodes, "actionRequestedCodes");
898         try {
899 	        SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
900 	        SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
901 	        // set activate requests to true by default so force action works correctly
902 	        criteria.setActivateRequests(Boolean.TRUE);
903 	        SimulationResults results = simulationEngine.runSimulation(criteria);
904             List<ActionRequestValue> actionRequestsToProcess = results.getSimulatedActionRequests();
905             if (!ignoreCurrentActionRequests) {
906                 actionRequestsToProcess.addAll(results.getDocument().getActionRequests());
907             }
908             for (ActionRequestValue actionRequest : actionRequestsToProcess) {
909                 if (actionRequest.isDone()) {
910                     // an action taken has eliminated this request from being active
911                     continue;
912                 }
913 				// if no action request codes are passed in.... assume any request found is
914 		    	if (CollectionUtils.isEmpty(actionRequestedCodes) ) {
915 		    		// we found an action request
916 		    		return true;
917 		    	}
918 		    	// check the action requested codes passed in
919 		    	for (String requestedActionRequestCode : actionRequestedCodes) {
920 					if (requestedActionRequestCode.equals(actionRequest.getActionRequested())) {
921 					    boolean satisfiesDestinationUserCriteria = (criteria.getDestinationRecipients().isEmpty()) || (isRecipientRoutedRequest(actionRequest,criteria.getDestinationRecipients()));
922 					    if (satisfiesDestinationUserCriteria) {
923 					        if (StringUtils.isBlank(criteria.getDestinationNodeName())) {
924 					            return true;
925 					        } else if (StringUtils.equals(criteria.getDestinationNodeName(),actionRequest.getNodeInstance().getName())) {
926 					            return true;
927 					        }
928 					    }
929 					}
930 				}
931 			}
932 	        return false;
933         } catch (Exception ex) {
934         	String error = "Problems evaluating documentWillHaveAtLeastOneActionRequest: " + ex.getMessage();
935             LOG.error(error,ex);
936             if (ex instanceof RuntimeException) {
937             	throw (RuntimeException)ex;
938             }
939             throw new RuntimeException(error, ex);
940         }
941     }
942 
943     private boolean isRecipientRoutedRequest(ActionRequestValue actionRequest, List<Recipient> recipients) throws WorkflowException {
944         for (Recipient recipient : recipients) {
945             if (actionRequest.isRecipientRoutedRequest(recipient)) {
946                 return true;
947             }
948         }
949         return false;
950     }
951 
952     @Override
953     public void reResolveRoleByDocTypeName(String documentTypeName, String roleName, String qualifiedRoleNameLabel) {
954         incomingParamCheck(documentTypeName, "documentTypeName");
955         incomingParamCheck(roleName, "roleName");
956         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
957         if ( LOG.isDebugEnabled() ) {
958         	LOG.debug("Re-resolving Role [docTypeName=" + documentTypeName + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
959         }
960     	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
961     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
962     		KEWServiceLocator.getRoleService().reResolveRole(documentType, roleName);
963     	} else {
964     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(documentType, roleName, qualifiedRoleNameLabel);
965     	}
966     }
967 
968     public void reResolveRoleByDocumentId(String documentId, String roleName, String qualifiedRoleNameLabel) {
969         incomingParamCheck(documentId, "documentId");
970         incomingParamCheck(roleName, "roleName");
971         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
972         if ( LOG.isDebugEnabled() ) {
973         	LOG.debug("Re-resolving Role [documentId=" + documentId + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
974         }
975         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
976     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
977     		KEWServiceLocator.getRoleService().reResolveRole(routeHeader, roleName);
978     	} else {
979     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(routeHeader, roleName, qualifiedRoleNameLabel);
980     	}
981     }
982 
983     @Override
984     public List<RemotableAttributeError> validateWorkflowAttributeDefinition(
985             WorkflowAttributeDefinition definition) {
986         if (definition == null) {
987             throw new RiceIllegalArgumentException("definition was null");
988         }
989         if ( LOG.isDebugEnabled() ) {
990             LOG.debug("Validating WorkflowAttributeDefinition [attributeName="+definition.getAttributeName()+"]");
991         }
992         AttributeDefinition attributeDefinition = DTOConverter.convertWorkflowAttributeDefinition(definition);
993         WorkflowRuleAttribute attribute = null;
994         if (attributeDefinition != null) {
995             attribute = (WorkflowRuleAttribute) GlobalResourceLoader.getObject(attributeDefinition.getObjectDefinition());
996         }
997         if (attribute instanceof GenericXMLRuleAttribute) {
998             Map<String, String> attributePropMap = new HashMap<String, String>();
999             GenericXMLRuleAttribute xmlAttribute = (GenericXMLRuleAttribute)attribute;
1000             xmlAttribute.setExtensionDefinition(attributeDefinition.getExtensionDefinition());
1001             for (PropertyDefinition propertyDefinition : definition.getPropertyDefinitions()) {
1002                 attributePropMap.put(propertyDefinition.getName(), propertyDefinition.getValue());
1003             }
1004             xmlAttribute.setParamMap(attributePropMap);
1005     }
1006         List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
1007         //validate inputs from client application if the attribute is capable
1008         if (attribute instanceof WorkflowAttributeXmlValidator) {
1009             List<? extends RemotableAttributeErrorContract> validationErrors = ((WorkflowAttributeXmlValidator)attribute).validateClientRoutingData();
1010             if (validationErrors != null) {
1011                 for (RemotableAttributeErrorContract validationError : validationErrors) {
1012                     errors.add(RemotableAttributeError.Builder.create(validationError).build());
1013                 }
1014             }
1015         }
1016         return errors;
1017     }
1018 
1019     @Override
1020     public boolean isFinalApprover(String documentId, String principalId) {
1021         incomingParamCheck(documentId, "documentId");
1022         incomingParamCheck(principalId, "principalId");
1023         if ( LOG.isDebugEnabled() ) {
1024         	LOG.debug("Evaluating isFinalApprover [docId=" + documentId + ", principalId=" + principalId + "]");
1025         }
1026         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1027         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(documentId);
1028         List<RouteNode> finalApproverNodes = KEWServiceLocator.getRouteNodeService().findFinalApprovalRouteNodes(routeHeader.getDocumentType().getDocumentTypeId());
1029         if (finalApproverNodes.isEmpty()) {
1030         	if ( LOG.isDebugEnabled() ) {
1031         		LOG.debug("Could not locate final approval nodes for document " + documentId);
1032         	}
1033             return false;
1034         }
1035         Set<String> finalApproverNodeNames = new HashSet<String>();
1036         for (RouteNode node : finalApproverNodes) {
1037             finalApproverNodeNames.add(node.getRouteNodeName());
1038         }
1039 
1040         int approveRequest = 0;
1041         for (ActionRequestValue request : requests) {
1042             RouteNodeInstance nodeInstance = request.getNodeInstance();
1043             if (nodeInstance == null) {
1044             	if ( LOG.isDebugEnabled() ) {
1045             		LOG.debug("Found an action request on the document with a null node instance, indicating EXCEPTION routing.");
1046             	}
1047                 return false;
1048             }
1049             if (finalApproverNodeNames.contains(nodeInstance.getRouteNode().getRouteNodeName())) {
1050                 if (request.isApproveOrCompleteRequest()) {
1051                     approveRequest++;
1052                     if ( LOG.isDebugEnabled() ) {
1053                     	LOG.debug("Found request is approver " + request.getActionRequestId());
1054                     }
1055                     if (! request.isRecipientRoutedRequest(principalId)) {
1056                     	if ( LOG.isDebugEnabled() ) {
1057                     		LOG.debug("Action Request not for user " + principalId);
1058                     	}
1059                         return false;
1060                     }
1061                 }
1062             }
1063         }
1064 
1065         if (approveRequest == 0) {
1066             return false;
1067         }
1068         if ( LOG.isDebugEnabled() ) {
1069         	LOG.debug("Principal "+principalId+" is final approver for document " + documentId);
1070         }
1071         return true;
1072     }
1073 
1074     @Override
1075     public boolean routeNodeHasApproverActionRequest(String documentTypeName, String docContent, String nodeName) {
1076         incomingParamCheck(documentTypeName, "documentTypeName");
1077         incomingParamCheck(docContent, "docContent");
1078         incomingParamCheck(nodeName, "nodeName");
1079         if ( LOG.isDebugEnabled() ) {
1080         	LOG.debug("Evaluating routeNodeHasApproverActionRequest [docTypeName=" + documentTypeName + ", nodeName=" + nodeName + "]");
1081         }
1082         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
1083         RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(documentType.getDocumentTypeId(), nodeName);
1084         return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, new Integer(KewApiConstants.INVALID_ROUTE_LEVEL));
1085     }
1086 
1087     /**
1088      * Really this method needs to be implemented using the executeSimulation functionality (the SimulationEngine).
1089      * This would get rid of the needs for us to call to FlexRM directly.
1090      */
1091     private boolean routeNodeHasApproverActionRequest(DocumentType documentType, String docContent, RouteNode node, Integer routeLevel) {
1092         incomingParamCheck(documentType, "documentType");
1093         incomingParamCheck(docContent, "docContent");
1094         incomingParamCheck(node, "node");
1095         incomingParamCheck(routeLevel, "routeLevel");
1096 
1097 /*        DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
1098         routeHeader.setDocumentId("");
1099         routeHeader.setDocumentTypeId(documentType.getDocumentTypeId());
1100         routeHeader.setDocRouteLevel(routeLevel);
1101         routeHeader.setDocVersion(new Integer(KewApiConstants.DocumentContentVersions.CURRENT));*/
1102 
1103         RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(documentType.getName());
1104         builder.setNodeNames(Collections.singletonList(node.getName()));
1105         builder.setXmlContent(docContent);
1106         DocumentDetail docDetail = executeSimulation(builder.build());
1107         if (docDetail != null) {
1108             for (ActionRequest actionRequest : docDetail.getActionRequests()) {
1109                 if (actionRequest.isApprovalRequest()) {
1110                     return true;
1111                 }
1112             }
1113         }
1114         /*if (node.getRuleTemplate() != null && node.isFlexRM()) {
1115             String ruleTemplateName = node.getRuleTemplate().getName();
1116             builder.setXmlContent(docContent);
1117             routeHeader.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
1118             FlexRM flexRM = new FlexRM();
1119     		RouteContext context = RouteContext.getCurrentRouteContext();
1120     		context.setDocument(routeHeader);
1121     		try {
1122     			List actionRequests = flexRM.getActionRequests(routeHeader, node, null, ruleTemplateName);
1123     			for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
1124     				ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
1125     				if (actionRequest.isApproveOrCompleteRequest()) {
1126     					return true;
1127     				}
1128     			}
1129     		} finally {
1130     			RouteContext.clearCurrentRouteContext();
1131     		}
1132         }*/
1133         return false;
1134     }
1135 
1136     @Override
1137     public boolean isLastApproverAtNode(String documentId, String principalId, String nodeName)  {
1138         incomingParamCheck(documentId, "documentId");
1139         incomingParamCheck(principalId, "principalId");
1140         incomingParamCheck(nodeName, "nodeName");
1141         if ( LOG.isDebugEnabled() ) {
1142         	LOG.debug("Evaluating isLastApproverAtNode [docId=" + documentId + ", principalId=" + principalId + ", nodeName=" + nodeName + "]");
1143         }
1144         loadDocument(documentId);
1145         // If this app constant is set to true, then we will attempt to simulate activation of non-active requests before
1146         // attempting to deactivate them, this is in order to address the force action issue reported by EPIC in issue
1147         // http://fms.dfa.cornell.edu:8080/browse/KULWF-366
1148         Boolean activateFirst = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
1149                 KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.FEATURE_DETAIL_TYPE, KewApiConstants.IS_LAST_APPROVER_ACTIVATE_FIRST_IND);
1150         if (activateFirst == null) {
1151             activateFirst = Boolean.FALSE;
1152         }
1153 
1154         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDocRequestCdNodeName(documentId, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, nodeName);
1155         if (requests == null || requests.isEmpty()) {
1156             return false;
1157         }
1158 
1159         // Deep-copy the action requests for the simulation.
1160         List<ActionRequestValue> copiedRequests = new ArrayList<ActionRequestValue>();
1161         for (ActionRequestValue request : requests) {
1162         	ActionRequestValue actionRequest = (ActionRequestValue) ObjectUtils.deepCopy(
1163                     (ActionRequestValue) request);
1164         	// Deep-copy the action items as well, since they are indirectly retrieved from the action request via service calls.
1165         	for (ActionItem actionItem : actionRequest.getActionItems()) {
1166         		actionRequest.getSimulatedActionItems().add((ActionItem) ObjectUtils.deepCopy(actionItem));
1167         	}
1168         	copiedRequests.add(actionRequest);
1169         }
1170 
1171         ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
1172         for (ActionRequestValue request : copiedRequests) {
1173             if (activateFirst.booleanValue() && !request.isActive()) {
1174                 KEWServiceLocator.getActionRequestService().activateRequest(request, activationContext);
1175             }
1176             if (request.isUserRequest() && request.getPrincipalId().equals(principalId)) {
1177                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1178             } else if (request.isGroupRequest() && KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, request.getGroup().getId())) {
1179                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1180             }
1181         }
1182         boolean allDeactivated = true;
1183         for (ActionRequestValue actionRequest: copiedRequests) {
1184             allDeactivated = allDeactivated && actionRequest.isDeactivated();
1185         }
1186         return allDeactivated;
1187     }
1188 
1189     @Override
1190     public boolean isUserInRouteLog(String documentId, String principalId, boolean lookFuture) {
1191     	incomingParamCheck(documentId, "documentId");
1192         incomingParamCheck(principalId, "principalId");
1193         return isUserInRouteLogWithOptionalFlattening(documentId, principalId, lookFuture, false);
1194     }
1195 
1196     @Override
1197     public boolean isUserInRouteLogWithOptionalFlattening(String documentId, String principalId, boolean lookFuture, boolean flattenNodes) {
1198         incomingParamCheck(documentId, "documentId");
1199         incomingParamCheck(principalId, "principalId");
1200         boolean authorized = false;
1201         if ( LOG.isDebugEnabled() ) {
1202         	LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", principalId=" + principalId + ", lookFuture=" + lookFuture + "]");
1203         }
1204         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1205         if (routeHeader == null) {
1206             throw new IllegalArgumentException("Document for documentId: " + documentId + " does not exist");
1207         }
1208         Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
1209         if (principal == null) {
1210             throw new IllegalArgumentException("Principal for principalId: " + principalId + " does not exist");
1211         }
1212         List<ActionTakenValue> actionsTaken = KEWServiceLocator.getActionTakenService().findByDocumentIdWorkflowId(documentId, principal.getPrincipalId());
1213 
1214         if(routeHeader.getInitiatorWorkflowId().equals(principal.getPrincipalId())){
1215         	return true;
1216         }
1217 
1218         if (!actionsTaken.isEmpty()) {
1219         	LOG.debug("found action taken by user");
1220         	authorized = true;
1221         }
1222 
1223         List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1224         if (actionRequestListHasPrincipal(principal, actionRequests)) {
1225         	authorized = true;
1226         }
1227 
1228         if (!lookFuture || authorized) {
1229         	return authorized;
1230         }
1231 
1232 
1233         SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1234         SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1235         criteria.setDestinationNodeName(null); // process entire document to conclusion
1236         criteria.getDestinationRecipients().add(new KimPrincipalRecipient(principal));
1237         criteria.setFlattenNodes(flattenNodes);
1238 
1239         try {
1240         	SimulationResults results = simulationEngine.runSimulation(criteria);
1241         	if (actionRequestListHasPrincipal(principal, results.getSimulatedActionRequests())) {
1242         		authorized = true;
1243         	}
1244         } catch (Exception e) {
1245         	throw new RiceRuntimeException(e);
1246         }
1247 
1248         return authorized;
1249     }
1250 
1251     private boolean actionRequestListHasPrincipal(Principal principal, List<ActionRequestValue> actionRequests) {
1252         for (ActionRequestValue actionRequest : actionRequests) {
1253             if (actionRequest.isRecipientRoutedRequest(new KimPrincipalRecipient(principal))) {
1254                 return true;
1255             }
1256         }
1257         return false;
1258     }
1259 
1260     public List<String> getPrincipalIdsInRouteLog(String documentId, boolean lookFuture) {
1261         if (StringUtils.isEmpty(documentId)) {
1262             throw new IllegalArgumentException("documentId passed in is null or blank");
1263         }
1264     	Set<String> principalIds = new HashSet<String>();
1265         try {
1266         	if ( LOG.isDebugEnabled() ) {
1267         		LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", lookFuture=" + lookFuture + "]");
1268         	}
1269             DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1270             List<ActionTakenValue> actionsTakens =
1271             	(List<ActionTakenValue>)KEWServiceLocator.getActionTakenService().findByDocumentId(documentId);
1272             //TODO: confirm that the initiator is not already there in the actionstaken
1273             principalIds.add(routeHeader.getInitiatorWorkflowId());
1274             for(ActionTakenValue actionTaken: actionsTakens){
1275             	principalIds.add(actionTaken.getPrincipalId());
1276             }
1277             List<ActionRequestValue> actionRequests =
1278             	KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1279             for(ActionRequestValue actionRequest: actionRequests){
1280             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1281             }
1282             if (!lookFuture) {
1283             	return new ArrayList<String>(principalIds);
1284             }
1285             SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1286             SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1287             criteria.setDestinationNodeName(null); // process entire document to conclusion
1288             SimulationResults results = simulationEngine.runSimulation(criteria);
1289             actionRequests = (List<ActionRequestValue>)results.getSimulatedActionRequests();
1290             for(ActionRequestValue actionRequest: actionRequests){
1291             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1292             }
1293         } catch (Exception ex) {
1294             LOG.warn("Problems getting principalIds in Route Log for documentId: "+documentId+". Exception:"+ex.getMessage(),ex);
1295         }
1296     	return new ArrayList<String>(principalIds);
1297     }
1298 
1299     private DocumentRouteHeaderValue loadDocument(String documentId) {
1300         return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
1301     }
1302 
1303     /**
1304 	 * This method gets all of the principalIds for the given ActionRequestValue.  It drills down into
1305 	 * groups if need be.
1306 	 *
1307 	 * @param actionRequest
1308 	 */
1309 	private List<String> getPrincipalIdsForActionRequest(ActionRequestValue actionRequest) {
1310 		List<String> results = Collections.emptyList();
1311 		if (actionRequest.getPrincipalId() != null) {
1312 			results = Collections.singletonList(actionRequest.getPrincipalId());
1313 		} else if (actionRequest.getGroupId() != null) {
1314 			List<String> principalIdsForGroup =
1315 				KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
1316 			if (principalIdsForGroup != null) {
1317 				results = principalIdsForGroup;
1318 			}
1319 		}
1320 		return results;
1321 	}
1322 
1323     private void incomingParamCheck(Object object, String name) {
1324         if (object == null) {
1325             throw new RiceIllegalArgumentException(name + " was null");
1326         } else if (object instanceof String
1327                 && StringUtils.isBlank((String) object)) {
1328             throw new RiceIllegalArgumentException(name + " was blank");
1329         }
1330     }
1331 
1332     public void setDocumentTypeService(DocumentTypeService documentTypeService) {
1333         this.documentTypeService = documentTypeService;
1334     }
1335 
1336     /**
1337      * TODO - this code is temporary until we get rid of all the crazy throwing of
1338      * "WorkflowException"
1339      */
1340     private void translateException(WorkflowException e) {
1341         if (e instanceof org.kuali.rice.kew.api.exception.InvalidActionTakenException) {
1342             throw new InvalidActionTakenException(e.getMessage(), e);
1343         }
1344         throw new WorkflowRuntimeException(e.getMessage(), e);
1345     }
1346 
1347     protected DocumentActionResult executeActionInternal(DocumentActionParameters parameters,
1348             DocumentActionCallback callback) {
1349         if (parameters == null) {
1350             throw new RiceIllegalArgumentException("Document action parameters was null.");
1351         }
1352         if (LOG.isDebugEnabled()) {
1353             LOG.debug(callback.getLogMessage(parameters.getDocumentId(), parameters.getPrincipalId(),
1354                     parameters.getAnnotation()));
1355         }
1356         DocumentRouteHeaderValue documentBo = init(parameters);
1357         try {
1358             documentBo = callback.doInDocumentBo(documentBo, parameters.getPrincipalId(), parameters.getAnnotation());
1359         } catch (WorkflowException e) {
1360             // TODO fix this up once the checked exception goes away
1361             translateException(e);
1362         }
1363         return constructDocumentActionResult(documentBo, parameters.getPrincipalId());
1364     }
1365 
1366     protected static interface DocumentActionCallback {
1367 
1368         DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
1369                 String annotation) throws WorkflowException;
1370 
1371         String getLogMessage(String documentId, String principalId, String annotation);
1372 
1373     }
1374 
1375     protected static abstract class StandardDocumentActionCallback implements DocumentActionCallback {
1376 
1377         public final String getLogMessage(String documentId, String principalId, String annotation) {
1378             return getActionName() + " [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
1379                     + annotation + "]";
1380         }
1381 
1382         protected abstract String getActionName();
1383 
1384     }
1385 
1386 }