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().getNewValidActions(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 clearFyi(DocumentActionParameters parameters) {
551         incomingParamCheck(parameters, "parameters");
552         return executeActionInternal(parameters, FYI_CALLBACK);
553     }
554 
555     @Override
556     public DocumentActionResult complete(DocumentActionParameters parameters) {
557         incomingParamCheck(parameters, "parameters");
558         return executeActionInternal(parameters, COMPLETE_CALLBACK);
559     }
560 
561     @Override
562     public DocumentActionResult disapprove(DocumentActionParameters parameters) {
563         incomingParamCheck(parameters, "parameters");
564         return executeActionInternal(parameters, DISAPPROVE_CALLBACK);
565     }
566 
567     @Override
568     public DocumentActionResult route(DocumentActionParameters parameters) {
569         incomingParamCheck(parameters, "parameters");
570         return executeActionInternal(parameters, ROUTE_CALLBACK);
571     }
572 
573     @Override
574     public DocumentActionResult blanketApprove(DocumentActionParameters parameters) {
575         incomingParamCheck(parameters, "parameters");
576         return executeActionInternal(parameters, BLANKET_APPROVE_CALLBACK);
577     }
578 
579     @Override
580     public DocumentActionResult blanketApproveToNodes(DocumentActionParameters parameters,
581             final Set<String> nodeNames) {
582         incomingParamCheck(parameters, "parameters");
583         incomingParamCheck(nodeNames, "nodeNames");
584         return executeActionInternal(parameters,
585                 new DocumentActionCallback() {
586                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
587                             String principalId, String annotation) throws WorkflowException {
588                         return KEWServiceLocator.getWorkflowDocumentService().blanketApproval(principalId, documentBo,
589                                 annotation, nodeNames);
590                     }
591 
592                     public String getLogMessage(String documentId, String principalId, String annotation) {
593                         return "Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
594                                 + ", annotation=" + annotation + ", nodeNames=" + nodeNames + "]";
595                     }
596                 });
597     }
598 
599     @Override
600     public DocumentActionResult returnToPreviousNode(DocumentActionParameters parameters,
601             final ReturnPoint returnPoint) {
602         incomingParamCheck(parameters, "parameters");
603         incomingParamCheck(returnPoint, "returnPoint");
604         return executeActionInternal(parameters,
605                 new DocumentActionCallback() {
606                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
607                             String principalId, String annotation) throws WorkflowException {
608                         return KEWServiceLocator.getWorkflowDocumentService().returnDocumentToPreviousNode(principalId,
609                                 documentBo, returnPoint.getNodeName(), annotation);
610                     }
611 
612                     public String getLogMessage(String documentId, String principalId, String annotation) {
613                         return "Return to Previous [principalId=" + principalId + ", documentId=" + documentId
614                                 + ", annotation=" + annotation + ", destNodeName=" + returnPoint.getNodeName() + "]";
615                     }
616                 });
617     }
618 
619     @Override
620     public DocumentActionResult move(DocumentActionParameters parameters,
621             final MovePoint movePoint) {
622         incomingParamCheck(parameters, "parameters");
623         incomingParamCheck(movePoint, "movePoint");
624         return executeActionInternal(parameters,
625                 new DocumentActionCallback() {
626                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
627                             String principalId, String annotation) throws WorkflowException {
628                         return KEWServiceLocator.getWorkflowDocumentService().moveDocument(principalId, documentBo,
629                                 movePoint, annotation);
630                     }
631 
632                     public String getLogMessage(String documentId, String principalId, String annotation) {
633                         return "Move Document [principalId=" + principalId + ", documentId=" + documentId
634                                 + ", annotation=" + annotation + ", movePoint=" + movePoint + "]";
635                     }
636                 });
637     }
638 
639     @Override
640     public DocumentActionResult takeGroupAuthority(DocumentActionParameters parameters,
641             final String groupId) {
642         incomingParamCheck(parameters, "parameters");
643         incomingParamCheck(groupId, "groupId");
644         return executeActionInternal(parameters,
645                 new StandardDocumentActionCallback() {
646                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
647                             String principalId, String annotation) throws WorkflowException {
648                         return KEWServiceLocator.getWorkflowDocumentService().takeGroupAuthority(principalId,
649                                 documentBo, groupId, annotation);
650                     }
651 
652                     public String getActionName() {
653                         return ActionType.TAKE_GROUP_AUTHORITY.getLabel();
654                     }
655                 });
656     }
657 
658     @Override
659     public DocumentActionResult releaseGroupAuthority(DocumentActionParameters parameters,
660             final String groupId) {
661         incomingParamCheck(parameters, "parameters");
662         incomingParamCheck(groupId, "groupId");
663         return executeActionInternal(parameters,
664                 new StandardDocumentActionCallback() {
665                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
666                             String principalId, String annotation) throws WorkflowException {
667                         return KEWServiceLocator.getWorkflowDocumentService().releaseGroupAuthority(principalId,
668                                 documentBo, groupId, annotation);
669                     }
670 
671                     public String getActionName() {
672                         return ActionType.RELEASE_GROUP_AUTHORITY.getLabel();
673                     }
674                 });
675 
676     }
677 
678     @Override
679     public DocumentActionResult save(DocumentActionParameters parameters) {
680         incomingParamCheck(parameters, "parameters");
681         return executeActionInternal(parameters, SAVE_CALLBACK);
682     }
683 
684     @Override
685     public DocumentActionResult saveDocumentData(DocumentActionParameters parameters) {
686         incomingParamCheck(parameters, "parameters");
687         return executeActionInternal(parameters, new DocumentActionCallback() {
688 
689             @Override
690             public String getLogMessage(String documentId, String principalId, String annotation) {
691                 return "Saving Routing Data [principalId=" + principalId + ", docId=" + documentId + "]";
692             }
693 
694             @Override
695             public DocumentRouteHeaderValue doInDocumentBo(
696                     DocumentRouteHeaderValue documentBo, String principalId,
697                     String annotation) throws WorkflowException {
698                 return KEWServiceLocator.getWorkflowDocumentService().saveRoutingData(principalId, documentBo);
699             }
700         });
701     }
702 
703     @Override
704     public Document delete(String documentId, String principalId) {
705         incomingParamCheck(documentId, "documentId");
706         incomingParamCheck(principalId, "principalId");
707         DocumentRouteHeaderValue documentBo = init(DocumentActionParameters.create(documentId, principalId, null));
708         if (LOG.isDebugEnabled()) {
709             LOG.debug("Delete [principalId=" + principalId + ", documentId=" + documentId + "]");
710         }
711         Document document = null;
712         try {
713             document = DocumentRouteHeaderValue.to(documentBo);
714             KEWServiceLocator.getWorkflowDocumentService().deleteDocument(principalId, documentBo);
715 
716         } catch (WorkflowException e) {
717             translateException(e);
718         }
719         return document;
720     }
721 
722     @Override
723     public void logAnnotation(String documentId, String principalId, String annotation) {
724         incomingParamCheck(documentId, "documentId");
725         incomingParamCheck(principalId, "principalId");
726         incomingParamCheck(annotation, "annotation");
727         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
728         try {
729             KEWServiceLocator.getWorkflowDocumentService().logDocumentAction(principalId, documentBo, annotation);
730         } catch (WorkflowException e) {
731             translateException(e);
732         }
733     }
734 
735     @Override
736     public void initiateIndexing(String documentId) {
737         incomingParamCheck(documentId, "documentId");
738         // TODO ewestfal - THIS METHOD NEEDS JAVADOCS
739         throw new UnsupportedOperationException("implement me!!!");
740     }
741 
742     @Override
743     public DocumentActionResult superUserBlanketApprove(DocumentActionParameters parameters,
744             final boolean executePostProcessor) {
745         incomingParamCheck(parameters, "parameters");
746         return executeActionInternal(parameters,
747                 new DocumentActionCallback() {
748                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
749                             String principalId, String annotation) throws WorkflowException {
750                         return KEWServiceLocator.getWorkflowDocumentService().superUserApprove(principalId, documentBo,
751                                 annotation, executePostProcessor);
752                     }
753 
754                     public String getLogMessage(String documentId, String principalId, String annotation) {
755                         return "SU Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
756                                 + ", annotation=" + annotation + "]";
757                     }
758                 });
759     }
760 
761     @Override
762     public DocumentActionResult superUserNodeApprove(DocumentActionParameters parameters,
763             final boolean executePostProcessor, final String nodeName) {
764                 incomingParamCheck(parameters, "parameters");
765                 incomingParamCheck(nodeName, "nodeName");
766         return executeActionInternal(parameters,
767                 new DocumentActionCallback() {
768                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
769                             String principalId, String annotation) throws WorkflowException {
770                         return KEWServiceLocator.getWorkflowDocumentService().superUserNodeApproveAction(principalId,
771                                 documentBo, nodeName, annotation, executePostProcessor);
772                     }
773 
774                     public String getLogMessage(String documentId, String principalId, String annotation) {
775                         return "SU Node Approve Action [principalId=" + principalId + ", documentId=" + documentId
776                                 + ", nodeName=" + nodeName + ", annotation=" + annotation + "]";
777                     }
778                 });
779 
780     }
781 
782     @Override
783     public DocumentActionResult superUserTakeRequestedAction(DocumentActionParameters parameters,
784             final boolean executePostProcessor, final String actionRequestId) {
785                 incomingParamCheck(parameters, "parameters");
786                 incomingParamCheck(actionRequestId, "actionRequestId");
787         return executeActionInternal(parameters,
788                 new DocumentActionCallback() {
789                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
790                             String principalId, String annotation) throws WorkflowException {
791                         return KEWServiceLocator.getWorkflowDocumentService().superUserActionRequestApproveAction(
792                                 principalId, documentBo, actionRequestId, annotation,
793                                 executePostProcessor);
794                     }
795 
796                     public String getLogMessage(String documentId, String principalId, String annotation) {
797                         return "SU Take Requested Action [principalId=" + principalId + ", docume tId=" + documentId
798                                 + ", actionRequestId=" + actionRequestId + ", annotation=" + annotation + "]";
799                     }
800                 });
801     }
802 
803     @Override
804     public DocumentActionResult superUserDisapprove(DocumentActionParameters parameters,
805             final boolean executePostProcessor) {
806                         incomingParamCheck(parameters, "parameters");
807         return executeActionInternal(parameters,
808                 new DocumentActionCallback() {
809                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
810                             String principalId, String annotation) throws WorkflowException {
811                         return KEWServiceLocator.getWorkflowDocumentService().superUserDisapproveAction(principalId,
812                                 documentBo, annotation, executePostProcessor);
813                     }
814 
815                     public String getLogMessage(String documentId, String principalId, String annotation) {
816                         return "SU Disapprove [principalId=" + principalId + ", documentId=" + documentId
817                                 + ", annotation=" + annotation + "]";
818                     }
819                 });
820     }
821 
822     @Override
823     public DocumentActionResult superUserCancel(DocumentActionParameters parameters, final boolean executePostProcessor) {
824                         incomingParamCheck(parameters, "parameters");
825         return executeActionInternal(parameters,
826                 new DocumentActionCallback() {
827                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
828                             String principalId, String annotation) throws WorkflowException {
829                         return KEWServiceLocator.getWorkflowDocumentService().superUserCancelAction(principalId,
830                                 documentBo, annotation, executePostProcessor);
831                     }
832 
833                     public String getLogMessage(String documentId, String principalId, String annotation) {
834                         return "SU Cancel [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
835                                 + annotation + "]";
836                     }
837                 });
838     }
839 
840     @Override
841     public DocumentActionResult superUserReturnToPreviousNode(DocumentActionParameters parameters,
842             final boolean executePostProcessor, final ReturnPoint returnPoint) {
843             incomingParamCheck(parameters, "parameters");
844             incomingParamCheck(returnPoint, "returnPoint");
845         return executeActionInternal(parameters,
846                 new DocumentActionCallback() {
847                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
848                             String principalId, String annotation) throws WorkflowException {
849                         return KEWServiceLocator.getWorkflowDocumentService().superUserReturnDocumentToPreviousNode(
850                                 principalId, documentBo, returnPoint.getNodeName(), annotation, executePostProcessor);
851                     }
852 
853                     public String getLogMessage(String documentId, String principalId, String annotation) {
854                         return "SU Return to Previous Node [principalId=" + principalId + ", documentId=" + documentId
855                                 + ", annotation=" + annotation + ", returnPoint=" + returnPoint + "]";
856                     }
857                 });
858 
859     }
860 
861     @Override
862     public DocumentActionResult placeInExceptionRouting(DocumentActionParameters parameters) {
863         incomingParamCheck(parameters, "parameters");
864         return executeActionInternal(parameters, PLACE_IN_EXCEPTION_CALLBACK);
865     }
866 
867     @Override
868     public boolean documentWillHaveAtLeastOneActionRequest(RoutingReportCriteria reportCriteria, List<String> actionRequestedCodes, boolean ignoreCurrentActionRequests) {
869         incomingParamCheck(reportCriteria, "reportCriteria");
870         incomingParamCheck(actionRequestedCodes, "actionRequestedCodes");
871         try {
872 	        SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
873 	        SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
874 	        // set activate requests to true by default so force action works correctly
875 	        criteria.setActivateRequests(Boolean.TRUE);
876 	        SimulationResults results = simulationEngine.runSimulation(criteria);
877             List<ActionRequestValue> actionRequestsToProcess = results.getSimulatedActionRequests();
878             if (!ignoreCurrentActionRequests) {
879                 actionRequestsToProcess.addAll(results.getDocument().getActionRequests());
880             }
881             for (ActionRequestValue actionRequest : actionRequestsToProcess) {
882                 if (actionRequest.isDone()) {
883                     // an action taken has eliminated this request from being active
884                     continue;
885                 }
886 				// if no action request codes are passed in.... assume any request found is
887 		    	if (CollectionUtils.isEmpty(actionRequestedCodes) ) {
888 		    		// we found an action request
889 		    		return true;
890 		    	}
891 		    	// check the action requested codes passed in
892 		    	for (String requestedActionRequestCode : actionRequestedCodes) {
893 					if (requestedActionRequestCode.equals(actionRequest.getActionRequested())) {
894 					    boolean satisfiesDestinationUserCriteria = (criteria.getDestinationRecipients().isEmpty()) || (isRecipientRoutedRequest(actionRequest,criteria.getDestinationRecipients()));
895 					    if (satisfiesDestinationUserCriteria) {
896 					        if (StringUtils.isBlank(criteria.getDestinationNodeName())) {
897 					            return true;
898 					        } else if (StringUtils.equals(criteria.getDestinationNodeName(),actionRequest.getNodeInstance().getName())) {
899 					            return true;
900 					        }
901 					    }
902 					}
903 				}
904 			}
905 	        return false;
906         } catch (Exception ex) {
907         	String error = "Problems evaluating documentWillHaveAtLeastOneActionRequest: " + ex.getMessage();
908             LOG.error(error,ex);
909             if (ex instanceof RuntimeException) {
910             	throw (RuntimeException)ex;
911             }
912             throw new RuntimeException(error, ex);
913         }
914     }
915 
916     private boolean isRecipientRoutedRequest(ActionRequestValue actionRequest, List<Recipient> recipients) throws WorkflowException {
917         for (Recipient recipient : recipients) {
918             if (actionRequest.isRecipientRoutedRequest(recipient)) {
919                 return true;
920             }
921         }
922         return false;
923     }
924 
925     @Override
926     public void reResolveRoleByDocTypeName(String documentTypeName, String roleName, String qualifiedRoleNameLabel) {
927         incomingParamCheck(documentTypeName, "documentTypeName");
928         incomingParamCheck(roleName, "roleName");
929         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
930         if ( LOG.isDebugEnabled() ) {
931         	LOG.debug("Re-resolving Role [docTypeName=" + documentTypeName + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
932         }
933     	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
934     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
935     		KEWServiceLocator.getRoleService().reResolveRole(documentType, roleName);
936     	} else {
937     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(documentType, roleName, qualifiedRoleNameLabel);
938     	}
939     }
940 
941     public void reResolveRoleByDocumentId(String documentId, String roleName, String qualifiedRoleNameLabel) {
942         incomingParamCheck(documentId, "documentId");
943         incomingParamCheck(roleName, "roleName");
944         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
945         if ( LOG.isDebugEnabled() ) {
946         	LOG.debug("Re-resolving Role [documentId=" + documentId + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
947         }
948         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
949     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
950     		KEWServiceLocator.getRoleService().reResolveRole(routeHeader, roleName);
951     	} else {
952     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(routeHeader, roleName, qualifiedRoleNameLabel);
953     	}
954     }
955 
956     @Override
957     public List<RemotableAttributeError> validateWorkflowAttributeDefinition(
958             WorkflowAttributeDefinition definition) {
959         if (definition == null) {
960             throw new RiceIllegalArgumentException("definition was null");
961         }
962         if ( LOG.isDebugEnabled() ) {
963             LOG.debug("Validating WorkflowAttributeDefinition [attributeName="+definition.getAttributeName()+"]");
964         }
965         AttributeDefinition attributeDefinition = DTOConverter.convertWorkflowAttributeDefinition(definition);
966         WorkflowRuleAttribute attribute = null;
967         if (attributeDefinition != null) {
968             attribute = (WorkflowRuleAttribute) GlobalResourceLoader.getObject(attributeDefinition.getObjectDefinition());
969         }
970         if (attribute instanceof GenericXMLRuleAttribute) {
971             Map<String, String> attributePropMap = new HashMap<String, String>();
972             GenericXMLRuleAttribute xmlAttribute = (GenericXMLRuleAttribute)attribute;
973             xmlAttribute.setExtensionDefinition(attributeDefinition.getExtensionDefinition());
974             for (PropertyDefinition propertyDefinition : definition.getPropertyDefinitions()) {
975                 attributePropMap.put(propertyDefinition.getName(), propertyDefinition.getValue());
976             }
977             xmlAttribute.setParamMap(attributePropMap);
978     }
979         List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
980         //validate inputs from client application if the attribute is capable
981         if (attribute instanceof WorkflowAttributeXmlValidator) {
982             List<? extends RemotableAttributeErrorContract> validationErrors = ((WorkflowAttributeXmlValidator)attribute).validateClientRoutingData();
983             if (validationErrors != null) {
984                 for (RemotableAttributeErrorContract validationError : validationErrors) {
985                     errors.add(RemotableAttributeError.Builder.create(validationError).build());
986                 }
987             }
988         }
989         return errors;
990     }
991 
992     @Override
993     public boolean isFinalApprover(String documentId, String principalId) {
994         incomingParamCheck(documentId, "documentId");
995         incomingParamCheck(principalId, "principalId");
996         if ( LOG.isDebugEnabled() ) {
997         	LOG.debug("Evaluating isFinalApprover [docId=" + documentId + ", principalId=" + principalId + "]");
998         }
999         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1000         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(documentId);
1001         List<RouteNode> finalApproverNodes = KEWServiceLocator.getRouteNodeService().findFinalApprovalRouteNodes(routeHeader.getDocumentType().getDocumentTypeId());
1002         if (finalApproverNodes.isEmpty()) {
1003         	if ( LOG.isDebugEnabled() ) {
1004         		LOG.debug("Could not locate final approval nodes for document " + documentId);
1005         	}
1006             return false;
1007         }
1008         Set<String> finalApproverNodeNames = new HashSet<String>();
1009         for (RouteNode node : finalApproverNodes) {
1010             finalApproverNodeNames.add(node.getRouteNodeName());
1011         }
1012 
1013         int approveRequest = 0;
1014         for (ActionRequestValue request : requests) {
1015             RouteNodeInstance nodeInstance = request.getNodeInstance();
1016             if (nodeInstance == null) {
1017             	if ( LOG.isDebugEnabled() ) {
1018             		LOG.debug("Found an action request on the document with a null node instance, indicating EXCEPTION routing.");
1019             	}
1020                 return false;
1021             }
1022             if (finalApproverNodeNames.contains(nodeInstance.getRouteNode().getRouteNodeName())) {
1023                 if (request.isApproveOrCompleteRequest()) {
1024                     approveRequest++;
1025                     if ( LOG.isDebugEnabled() ) {
1026                     	LOG.debug("Found request is approver " + request.getActionRequestId());
1027                     }
1028                     if (! request.isRecipientRoutedRequest(principalId)) {
1029                     	if ( LOG.isDebugEnabled() ) {
1030                     		LOG.debug("Action Request not for user " + principalId);
1031                     	}
1032                         return false;
1033                     }
1034                 }
1035             }
1036         }
1037 
1038         if (approveRequest == 0) {
1039             return false;
1040         }
1041         if ( LOG.isDebugEnabled() ) {
1042         	LOG.debug("Principal "+principalId+" is final approver for document " + documentId);
1043         }
1044         return true;
1045     }
1046 
1047     @Override
1048     public boolean routeNodeHasApproverActionRequest(String documentTypeName, String docContent, String nodeName) {
1049         incomingParamCheck(documentTypeName, "documentTypeName");
1050         incomingParamCheck(docContent, "docContent");
1051         incomingParamCheck(nodeName, "nodeName");
1052         if ( LOG.isDebugEnabled() ) {
1053         	LOG.debug("Evaluating routeNodeHasApproverActionRequest [docTypeName=" + documentTypeName + ", nodeName=" + nodeName + "]");
1054         }
1055         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
1056         RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(documentType.getDocumentTypeId(), nodeName);
1057         return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, new Integer(KewApiConstants.INVALID_ROUTE_LEVEL));
1058     }
1059 
1060     /**
1061      * Really this method needs to be implemented using the executeSimulation functionality (the SimulationEngine).
1062      * This would get rid of the needs for us to call to FlexRM directly.
1063      */
1064     private boolean routeNodeHasApproverActionRequest(DocumentType documentType, String docContent, RouteNode node, Integer routeLevel) {
1065         incomingParamCheck(documentType, "documentType");
1066         incomingParamCheck(docContent, "docContent");
1067         incomingParamCheck(node, "node");
1068         incomingParamCheck(routeLevel, "routeLevel");
1069 
1070 /*        DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
1071         routeHeader.setDocumentId("");
1072         routeHeader.setDocumentTypeId(documentType.getDocumentTypeId());
1073         routeHeader.setDocRouteLevel(routeLevel);
1074         routeHeader.setDocVersion(new Integer(KewApiConstants.DocumentContentVersions.CURRENT));*/
1075 
1076         //TODO THIS NEEDS TESTING!!!!! IT WAS A GUESS ON HOW THIS WORKS
1077         RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(documentType.getName());
1078         builder.setTargetNodeName(node.getName());
1079         builder.setXmlContent(docContent);
1080         DocumentDetail docDetail = executeSimulation(builder.build());
1081         if (docDetail != null) {
1082             for (ActionRequest actionRequest : docDetail.getActionRequests()) {
1083                 if (actionRequest.isApprovalRequest()) {
1084                     return true;
1085                 }
1086             }
1087         }
1088         /*if (node.getRuleTemplate() != null && node.isFlexRM()) {
1089             String ruleTemplateName = node.getRuleTemplate().getName();
1090             builder.setXmlContent(docContent);
1091             routeHeader.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
1092             FlexRM flexRM = new FlexRM();
1093     		RouteContext context = RouteContext.getCurrentRouteContext();
1094     		context.setDocument(routeHeader);
1095     		try {
1096     			List actionRequests = flexRM.getActionRequests(routeHeader, node, null, ruleTemplateName);
1097     			for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
1098     				ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
1099     				if (actionRequest.isApproveOrCompleteRequest()) {
1100     					return true;
1101     				}
1102     			}
1103     		} finally {
1104     			RouteContext.clearCurrentRouteContext();
1105     		}
1106         }*/
1107         return false;
1108     }
1109 
1110     @Override
1111     public boolean isLastApproverAtNode(String documentId, String principalId, String nodeName)  {
1112         incomingParamCheck(documentId, "documentId");
1113         incomingParamCheck(principalId, "principalId");
1114         incomingParamCheck(nodeName, "nodeName");
1115         if ( LOG.isDebugEnabled() ) {
1116         	LOG.debug("Evaluating isLastApproverAtNode [docId=" + documentId + ", principalId=" + principalId + ", nodeName=" + nodeName + "]");
1117         }
1118         loadDocument(documentId);
1119         // If this app constant is set to true, then we will attempt to simulate activation of non-active requests before
1120         // attempting to deactivate them, this is in order to address the force action issue reported by EPIC in issue
1121         // http://fms.dfa.cornell.edu:8080/browse/KULWF-366
1122         Boolean activateFirst = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
1123                 KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.FEATURE_DETAIL_TYPE, KewApiConstants.IS_LAST_APPROVER_ACTIVATE_FIRST_IND);
1124         if (activateFirst == null) {
1125             activateFirst = Boolean.FALSE;
1126         }
1127 
1128         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDocRequestCdNodeName(documentId, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, nodeName);
1129         if (requests == null || requests.isEmpty()) {
1130             return false;
1131         }
1132 
1133         // Deep-copy the action requests for the simulation.
1134         List<ActionRequestValue> copiedRequests = new ArrayList<ActionRequestValue>();
1135         for (ActionRequestValue request : requests) {
1136         	ActionRequestValue actionRequest = (ActionRequestValue) ObjectUtils.deepCopy(
1137                     (ActionRequestValue) request);
1138         	// Deep-copy the action items as well, since they are indirectly retrieved from the action request via service calls.
1139         	for (ActionItem actionItem : actionRequest.getActionItems()) {
1140         		actionRequest.getSimulatedActionItems().add((ActionItem) ObjectUtils.deepCopy(actionItem));
1141         	}
1142         	copiedRequests.add(actionRequest);
1143         }
1144 
1145         ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
1146         for (ActionRequestValue request : copiedRequests) {
1147             if (activateFirst.booleanValue() && !request.isActive()) {
1148                 KEWServiceLocator.getActionRequestService().activateRequest(request, activationContext);
1149             }
1150             if (request.isUserRequest() && request.getPrincipalId().equals(principalId)) {
1151                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1152             } else if (request.isGroupRequest() && KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, request.getGroup().getId())) {
1153                 KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1154             }
1155         }
1156         boolean allDeactivated = true;
1157         for (ActionRequestValue actionRequest: copiedRequests) {
1158             allDeactivated = allDeactivated && actionRequest.isDeactivated();
1159         }
1160         return allDeactivated;
1161     }
1162 
1163     @Override
1164     public boolean isUserInRouteLog(String documentId, String principalId, boolean lookFuture) {
1165     	incomingParamCheck(documentId, "documentId");
1166         incomingParamCheck(principalId, "principalId");
1167         return isUserInRouteLogWithOptionalFlattening(documentId, principalId, lookFuture, false);
1168     }
1169 
1170     @Override
1171     public boolean isUserInRouteLogWithOptionalFlattening(String documentId, String principalId, boolean lookFuture, boolean flattenNodes) {
1172         incomingParamCheck(documentId, "documentId");
1173         incomingParamCheck(principalId, "principalId");
1174         boolean authorized = false;
1175         if ( LOG.isDebugEnabled() ) {
1176         	LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", principalId=" + principalId + ", lookFuture=" + lookFuture + "]");
1177         }
1178         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1179         if (routeHeader == null) {
1180             throw new IllegalArgumentException("Document for documentId: " + documentId + " does not exist");
1181         }
1182         Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
1183         if (principal == null) {
1184             throw new IllegalArgumentException("Principal for principalId: " + principalId + " does not exist");
1185         }
1186         List<ActionTakenValue> actionsTaken = KEWServiceLocator.getActionTakenService().findByDocumentIdWorkflowId(documentId, principal.getPrincipalId());
1187 
1188         if(routeHeader.getInitiatorWorkflowId().equals(principal.getPrincipalId())){
1189         	return true;
1190         }
1191 
1192         if (!actionsTaken.isEmpty()) {
1193         	LOG.debug("found action taken by user");
1194         	authorized = true;
1195         }
1196 
1197         List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1198         if (actionRequestListHasPrincipal(principal, actionRequests)) {
1199         	authorized = true;
1200         }
1201 
1202         if (!lookFuture || authorized) {
1203         	return authorized;
1204         }
1205 
1206 
1207         SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1208         SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1209         criteria.setDestinationNodeName(null); // process entire document to conclusion
1210         criteria.getDestinationRecipients().add(new KimPrincipalRecipient(principal));
1211         criteria.setFlattenNodes(flattenNodes);
1212 
1213         try {
1214         	SimulationResults results = simulationEngine.runSimulation(criteria);
1215         	if (actionRequestListHasPrincipal(principal, results.getSimulatedActionRequests())) {
1216         		authorized = true;
1217         	}
1218         } catch (Exception e) {
1219         	throw new RiceRuntimeException(e);
1220         }
1221 
1222         return authorized;
1223     }
1224 
1225     private boolean actionRequestListHasPrincipal(Principal principal, List<ActionRequestValue> actionRequests) {
1226         for (ActionRequestValue actionRequest : actionRequests) {
1227             if (actionRequest.isRecipientRoutedRequest(new KimPrincipalRecipient(principal))) {
1228                 return true;
1229             }
1230         }
1231         return false;
1232     }
1233 
1234     public List<String> getPrincipalIdsInRouteLog(String documentId, boolean lookFuture) {
1235         if (StringUtils.isEmpty(documentId)) {
1236             throw new IllegalArgumentException("documentId passed in is null or blank");
1237         }
1238     	Set<String> principalIds = new HashSet<String>();
1239         try {
1240         	if ( LOG.isDebugEnabled() ) {
1241         		LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", lookFuture=" + lookFuture + "]");
1242         	}
1243             DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1244             List<ActionTakenValue> actionsTakens =
1245             	(List<ActionTakenValue>)KEWServiceLocator.getActionTakenService().findByDocumentId(documentId);
1246             //TODO: confirm that the initiator is not already there in the actionstaken
1247             principalIds.add(routeHeader.getInitiatorWorkflowId());
1248             for(ActionTakenValue actionTaken: actionsTakens){
1249             	principalIds.add(actionTaken.getPrincipalId());
1250             }
1251             List<ActionRequestValue> actionRequests =
1252             	KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1253             for(ActionRequestValue actionRequest: actionRequests){
1254             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1255             }
1256             if (!lookFuture) {
1257             	return new ArrayList<String>(principalIds);
1258             }
1259             SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1260             SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1261             criteria.setDestinationNodeName(null); // process entire document to conclusion
1262             SimulationResults results = simulationEngine.runSimulation(criteria);
1263             actionRequests = (List<ActionRequestValue>)results.getSimulatedActionRequests();
1264             for(ActionRequestValue actionRequest: actionRequests){
1265             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1266             }
1267         } catch (Exception ex) {
1268             LOG.warn("Problems getting principalIds in Route Log for documentId: "+documentId+". Exception:"+ex.getMessage(),ex);
1269         }
1270     	return new ArrayList<String>(principalIds);
1271     }
1272 
1273     private DocumentRouteHeaderValue loadDocument(String documentId) {
1274         return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
1275     }
1276 
1277     /**
1278 	 * This method gets all of the principalIds for the given ActionRequestValue.  It drills down into
1279 	 * groups if need be.
1280 	 *
1281 	 * @param actionRequest
1282 	 */
1283 	private List<String> getPrincipalIdsForActionRequest(ActionRequestValue actionRequest) {
1284 		List<String> results = Collections.emptyList();
1285 		if (actionRequest.getPrincipalId() != null) {
1286 			results = Collections.singletonList(actionRequest.getPrincipalId());
1287 		} else if (actionRequest.getGroupId() != null) {
1288 			List<String> principalIdsForGroup =
1289 				KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
1290 			if (principalIdsForGroup != null) {
1291 				results = principalIdsForGroup;
1292 			}
1293 		}
1294 		return results;
1295 	}
1296 
1297     private void incomingParamCheck(Object object, String name) {
1298         if (object == null) {
1299             throw new RiceIllegalArgumentException(name + " was null");
1300         } else if (object instanceof String
1301                 && StringUtils.isBlank((String) object)) {
1302             throw new RiceIllegalArgumentException(name + " was blank");
1303         }
1304     }
1305 
1306     public void setDocumentTypeService(DocumentTypeService documentTypeService) {
1307         this.documentTypeService = documentTypeService;
1308     }
1309 
1310     /**
1311      * TODO - this code is temporary until we get rid of all the crazy throwing of
1312      * "WorkflowException"
1313      */
1314     private void translateException(WorkflowException e) {
1315         if (e instanceof org.kuali.rice.kew.api.exception.InvalidActionTakenException) {
1316             throw new InvalidActionTakenException(e.getMessage(), e);
1317         }
1318         throw new WorkflowRuntimeException(e.getMessage(), e);
1319     }
1320 
1321     protected DocumentActionResult executeActionInternal(DocumentActionParameters parameters,
1322             DocumentActionCallback callback) {
1323         if (parameters == null) {
1324             throw new RiceIllegalArgumentException("Document action parameters was null.");
1325         }
1326         if (LOG.isDebugEnabled()) {
1327             LOG.debug(callback.getLogMessage(parameters.getDocumentId(), parameters.getPrincipalId(),
1328                     parameters.getAnnotation()));
1329         }
1330         DocumentRouteHeaderValue documentBo = init(parameters);
1331         try {
1332             documentBo = callback.doInDocumentBo(documentBo, parameters.getPrincipalId(), parameters.getAnnotation());
1333         } catch (WorkflowException e) {
1334             // TODO fix this up once the checked exception goes away
1335             translateException(e);
1336         }
1337         return constructDocumentActionResult(documentBo, parameters.getPrincipalId());
1338     }
1339 
1340     protected static interface DocumentActionCallback {
1341 
1342         DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
1343                 String annotation) throws WorkflowException;
1344 
1345         String getLogMessage(String documentId, String principalId, String annotation);
1346 
1347     }
1348 
1349     protected static abstract class StandardDocumentActionCallback implements DocumentActionCallback {
1350 
1351         public final String getLogMessage(String documentId, String principalId, String annotation) {
1352             return getActionName() + " [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
1353                     + annotation + "]";
1354         }
1355 
1356         protected abstract String getActionName();
1357 
1358     }
1359 
1360 }