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