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