View Javadoc
1   /**
2    * Copyright 2005-2015 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 boolean isValidAction(String actionTypeCode, String documentId, String principalId)
345             throws RiceIllegalArgumentException {
346         incomingParamCheck(actionTypeCode, "actionTypeCode");
347         incomingParamCheck(documentId, "documentId");
348         incomingParamCheck(principalId, "principalId");
349 
350         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
351         if (documentBo == null) {
352             throw new RiceIllegalArgumentException("Failed to locate a document for document id: " + documentId);
353         }
354 
355         return determineValidActionInternal(actionTypeCode, documentBo, principalId);
356     }
357 
358     protected boolean determineValidActionInternal(String actionTypeCode, DocumentRouteHeaderValue documentBo,
359             String principalId) {
360         Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
361         return KEWServiceLocator.getActionRegistry().isValidAction(actionTypeCode, principal, documentBo);
362     }
363 
364     @Override
365     public RequestedActions determineRequestedActions(String documentId, String principalId) {
366         incomingParamCheck(documentId, "documentId");
367         incomingParamCheck(principalId, "principalId");
368         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
369         if (documentBo == null) {
370             throw new RiceIllegalArgumentException("Failed to locate a document for document id: " + documentId);
371         }
372         KEWServiceLocator.getIdentityHelperService().validatePrincipalId(principalId);
373         return determineRequestedActionsInternal(documentBo, principalId);
374     }
375 
376     protected RequestedActions determineRequestedActionsInternal(DocumentRouteHeaderValue documentBo, String principalId) {
377         Map<String, String> actionsRequested = KEWServiceLocator.getActionRequestService().getActionsRequested(documentBo,
378                 principalId, true);
379         boolean completeRequested = false;
380         boolean approveRequested = false;
381         boolean acknowledgeRequested = false;
382         boolean fyiRequested = false;
383         for (String actionRequestCode : actionsRequested.keySet()) {
384             if (ActionRequestType.FYI.getCode().equals(actionRequestCode)) {
385                 fyiRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
386             } else if (ActionRequestType.ACKNOWLEDGE.getCode().equals(actionRequestCode)) {
387                 acknowledgeRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
388             } else if (ActionRequestType.APPROVE.getCode().equals(actionRequestCode)) {
389                 approveRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
390             } else if (ActionRequestType.COMPLETE.getCode().equals(actionRequestCode)) {
391                 completeRequested = Boolean.parseBoolean(actionsRequested.get(actionRequestCode));
392             }
393         }
394         return RequestedActions.create(completeRequested, approveRequested, acknowledgeRequested, fyiRequested);
395     }
396 
397     @Override
398     public DocumentDetail executeSimulation(RoutingReportCriteria reportCriteria) {
399         incomingParamCheck(reportCriteria, "reportCriteria");
400         if ( LOG.isDebugEnabled() ) {
401         	LOG.debug("Executing routing report [docId=" + reportCriteria.getDocumentId() + ", docTypeName=" + reportCriteria.getDocumentTypeName() + "]");
402         }
403         SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
404 
405         return DTOConverter.convertDocumentDetailNew(KEWServiceLocator.getRoutingReportService().report(criteria));
406     }
407 
408     protected DocumentActionResult constructDocumentActionResult(DocumentRouteHeaderValue documentBo, String principalId) {
409         Document document = DocumentRouteHeaderValue.to(documentBo);
410         ValidActions validActions = determineValidActionsInternal(documentBo, principalId);
411         RequestedActions requestedActions = determineRequestedActionsInternal(documentBo, principalId);
412         return DocumentActionResult.create(document, validActions, requestedActions);
413     }
414 
415     @Override
416     public DocumentActionResult acknowledge(DocumentActionParameters parameters) {
417         incomingParamCheck(parameters, "parameters");
418         return executeActionInternal(parameters, ACKNOWLEDGE_CALLBACK);
419     }
420 
421     @Override
422     public DocumentActionResult approve(DocumentActionParameters parameters) {
423         incomingParamCheck(parameters, "parameters");
424         return executeActionInternal(parameters, APPROVE_CALLBACK);
425     }
426 
427     @Override
428     public DocumentActionResult adHocToPrincipal(DocumentActionParameters parameters,
429             final AdHocToPrincipal adHocToPrincipal) {
430         incomingParamCheck(parameters, "parameters");
431         incomingParamCheck(adHocToPrincipal, "adHocToPrincipal");
432         return executeActionInternal(parameters,
433                 new DocumentActionCallback() {
434                     @Override
435                     public String getLogMessage(String documentId, String principalId, String annotation) {
436                         return "AdHoc Route To Principal [principalId=" + principalId +
437                                 ", docId=" + documentId +
438                                 ", actionRequest=" + adHocToPrincipal.getActionRequested() +
439                                 ", nodeName=" + adHocToPrincipal.getNodeName() +
440                                 ", targetPrincipalId=" + adHocToPrincipal.getTargetPrincipalId() +
441                                 ", forceAction=" + adHocToPrincipal.isForceAction() +
442                                 ", annotation=" + annotation +
443                                 ", requestLabel=" + adHocToPrincipal.getRequestLabel() + "]";
444                     }
445 
446                     @Override
447                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
448                             String principalId, String annotation) throws WorkflowException {
449                         return KEWServiceLocator.getWorkflowDocumentService().adHocRouteDocumentToPrincipal(
450                                 principalId,
451                                     documentBo,
452                                     adHocToPrincipal.getActionRequested().getCode(),
453                                     adHocToPrincipal.getNodeName(),
454                                     adHocToPrincipal.getPriority(),
455                                     annotation,
456                                     adHocToPrincipal.getTargetPrincipalId(),
457                                     adHocToPrincipal.getResponsibilityDescription(),
458                                     adHocToPrincipal.isForceAction(),
459                                     adHocToPrincipal.getRequestLabel());
460                     }
461                 });
462     }
463 
464     @Override
465     public DocumentActionResult adHocToPrincipal(DocumentActionParameters parameters, AdHocToPrincipal_v2_1_2 adHocToPrincipal) {
466         return adHocToPrincipal(parameters, AdHocToPrincipal_v2_1_2.to(adHocToPrincipal));
467     }
468 
469     @Override
470     public DocumentActionResult adHocToGroup(DocumentActionParameters parameters,
471             final AdHocToGroup adHocToGroup) {
472         incomingParamCheck(parameters, "parameters");
473         incomingParamCheck(adHocToGroup, "adHocToGroup");
474         return executeActionInternal(parameters,
475                 new DocumentActionCallback() {
476                     @Override
477                     public String getLogMessage(String documentId, String principalId, String annotation) {
478                         return "AdHoc Route To Group [principalId=" + principalId +
479                                 ", docId=" + documentId +
480                                 ", actionRequest=" + adHocToGroup.getActionRequested() +
481                                 ", nodeName=" + adHocToGroup.getNodeName() +
482                                 ", targetGroupId=" + adHocToGroup.getTargetGroupId() +
483                                 ", forceAction=" + adHocToGroup.isForceAction() +
484                                 ", annotation=" + annotation +
485                                 ", requestLabel=" + adHocToGroup.getRequestLabel() + "]";
486                     }
487 
488                     @Override
489                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
490                             String principalId, String annotation) throws WorkflowException {
491                         return KEWServiceLocator.getWorkflowDocumentService().adHocRouteDocumentToGroup(principalId,
492                                     documentBo,
493                                     adHocToGroup.getActionRequested().getCode(),
494                                     adHocToGroup.getNodeName(),
495                                     adHocToGroup.getPriority(),
496                                     annotation,
497                                     adHocToGroup.getTargetGroupId(),
498                                     adHocToGroup.getResponsibilityDescription(),
499                                     adHocToGroup.isForceAction(),
500                                     adHocToGroup.getRequestLabel());
501                     }
502                 });
503     }
504 
505     @Override
506     public DocumentActionResult adHocToGroup(DocumentActionParameters parameters, AdHocToGroup_v2_1_2 adHocToGroup) {
507         return adHocToGroup(parameters, AdHocToGroup_v2_1_2.to(adHocToGroup));
508     }
509 
510     @Override
511     public DocumentActionResult revokeAdHocRequestById(DocumentActionParameters parameters,
512             final String actionRequestId) {
513         incomingParamCheck(parameters, "parameters");
514         incomingParamCheck(actionRequestId, "actionRequestId");
515         return executeActionInternal(parameters,
516                 new DocumentActionCallback() {
517                     @Override
518                     public String getLogMessage(String documentId, String principalId, String annotation) {
519                         return "Revoke AdHoc from Principal [principalId=" + principalId +
520                                 ", documentId=" + documentId +
521                                 ", annotation=" + annotation +
522                                 ", actionRequestId=" + actionRequestId + "]";
523                     }
524 
525                     @Override
526                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
527                             String principalId, String annotation) throws WorkflowException {
528                         return KEWServiceLocator.getWorkflowDocumentService().revokeAdHocRequests(principalId,
529                                 documentBo, actionRequestId, annotation);
530                     }
531                 });
532     }
533 
534     @Override
535     public DocumentActionResult revokeAdHocRequests(DocumentActionParameters parameters,
536             final AdHocRevoke revoke) {
537         incomingParamCheck(parameters, "parameters");
538         incomingParamCheck(revoke, "revoke");
539         return executeActionInternal(parameters,
540                 new DocumentActionCallback() {
541                     @Override
542                     public String getLogMessage(String documentId, String principalId, String annotation) {
543                         return "Revoke AdHoc Requests [principalId=" + principalId +
544                                 ", docId=" + documentId +
545                                 ", annotation=" + annotation +
546                                 ", revoke=" + revoke.toString() + "]";
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, revoke, annotation);
554                     }
555                 });
556     }
557 
558     @Override
559     public DocumentActionResult revokeAllAdHocRequests(DocumentActionParameters parameters) {
560         incomingParamCheck(parameters, "parameters");
561         return executeActionInternal(parameters,
562                 new DocumentActionCallback() {
563                     @Override
564                     public String getLogMessage(String documentId, String principalId, String annotation) {
565                         return "Revoke All AdHoc Requests [principalId=" + principalId +
566                                 ", docId=" + documentId +
567                                 ", annotation=" + annotation + "]";
568                     }
569 
570                     @Override
571                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
572                             String principalId, String annotation) throws WorkflowException {
573                         return KEWServiceLocator.getWorkflowDocumentService().revokeAdHocRequests(principalId,
574                                 documentBo, (AdHocRevoke) null, annotation);
575                     }
576                 });
577     }
578 
579     @Override
580     public DocumentActionResult cancel(DocumentActionParameters parameters) {
581         incomingParamCheck(parameters, "parameters");
582         return executeActionInternal(parameters, CANCEL_CALLBACK);
583     }
584 
585     @Override
586     public DocumentActionResult recall(DocumentActionParameters parameters, final boolean cancel) {
587         incomingParamCheck(parameters, "parameters");
588         incomingParamCheck(cancel, "cancel");
589         return executeActionInternal(parameters, new StandardDocumentActionCallback() {
590             public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
591                     String annotation) throws WorkflowException {
592                 return KEWServiceLocator.getWorkflowDocumentService().recallDocument(principalId, documentBo, annotation, cancel);
593             }
594             public String getActionName() {
595                 return ActionType.RECALL.getLabel();
596             }
597         });
598     }
599 
600     @Override
601     public DocumentActionResult clearFyi(DocumentActionParameters parameters) {
602         incomingParamCheck(parameters, "parameters");
603         return executeActionInternal(parameters, FYI_CALLBACK);
604     }
605 
606     @Override
607     public DocumentActionResult complete(DocumentActionParameters parameters) {
608         incomingParamCheck(parameters, "parameters");
609         return executeActionInternal(parameters, COMPLETE_CALLBACK);
610     }
611 
612     @Override
613     public DocumentActionResult disapprove(DocumentActionParameters parameters) {
614         incomingParamCheck(parameters, "parameters");
615         return executeActionInternal(parameters, DISAPPROVE_CALLBACK);
616     }
617 
618     @Override
619     public DocumentActionResult route(DocumentActionParameters parameters) {
620         incomingParamCheck(parameters, "parameters");
621         return executeActionInternal(parameters, ROUTE_CALLBACK);
622     }
623 
624     @Override
625     public DocumentActionResult blanketApprove(DocumentActionParameters parameters) {
626         incomingParamCheck(parameters, "parameters");
627         return executeActionInternal(parameters, BLANKET_APPROVE_CALLBACK);
628     }
629 
630     @Override
631     public DocumentActionResult blanketApproveToNodes(DocumentActionParameters parameters,
632             final Set<String> nodeNames) {
633         incomingParamCheck(parameters, "parameters");
634         incomingParamCheck(nodeNames, "nodeNames");
635         return executeActionInternal(parameters,
636                 new DocumentActionCallback() {
637                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
638                             String principalId, String annotation) throws WorkflowException {
639                         return KEWServiceLocator.getWorkflowDocumentService().blanketApproval(principalId, documentBo,
640                                 annotation, nodeNames);
641                     }
642 
643                     public String getLogMessage(String documentId, String principalId, String annotation) {
644                         return "Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
645                                 + ", annotation=" + annotation + ", nodeNames=" + nodeNames + "]";
646                     }
647                 });
648     }
649 
650     @Override
651     public DocumentActionResult returnToPreviousNode(DocumentActionParameters parameters,
652             final ReturnPoint returnPoint) {
653         incomingParamCheck(parameters, "parameters");
654         incomingParamCheck(returnPoint, "returnPoint");
655         return executeActionInternal(parameters,
656                 new DocumentActionCallback() {
657                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
658                             String principalId, String annotation) throws WorkflowException {
659                         return KEWServiceLocator.getWorkflowDocumentService().returnDocumentToPreviousNode(principalId,
660                                 documentBo, returnPoint.getNodeName(), annotation);
661                     }
662 
663                     public String getLogMessage(String documentId, String principalId, String annotation) {
664                         return "Return to Previous [principalId=" + principalId + ", documentId=" + documentId
665                                 + ", annotation=" + annotation + ", destNodeName=" + returnPoint.getNodeName() + "]";
666                     }
667                 });
668     }
669 
670     @Override
671     public DocumentActionResult move(DocumentActionParameters parameters,
672             final MovePoint movePoint) {
673         incomingParamCheck(parameters, "parameters");
674         incomingParamCheck(movePoint, "movePoint");
675         return executeActionInternal(parameters,
676                 new DocumentActionCallback() {
677                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
678                             String principalId, String annotation) throws WorkflowException {
679                         return KEWServiceLocator.getWorkflowDocumentService().moveDocument(principalId, documentBo,
680                                 movePoint, annotation);
681                     }
682 
683                     public String getLogMessage(String documentId, String principalId, String annotation) {
684                         return "Move Document [principalId=" + principalId + ", documentId=" + documentId
685                                 + ", annotation=" + annotation + ", movePoint=" + movePoint + "]";
686                     }
687                 });
688     }
689 
690     @Override
691     public DocumentActionResult takeGroupAuthority(DocumentActionParameters parameters,
692             final String groupId) {
693         incomingParamCheck(parameters, "parameters");
694         incomingParamCheck(groupId, "groupId");
695         return executeActionInternal(parameters,
696                 new StandardDocumentActionCallback() {
697                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
698                             String principalId, String annotation) throws WorkflowException {
699                         return KEWServiceLocator.getWorkflowDocumentService().takeGroupAuthority(principalId,
700                                 documentBo, groupId, annotation);
701                     }
702 
703                     public String getActionName() {
704                         return ActionType.TAKE_GROUP_AUTHORITY.getLabel();
705                     }
706                 });
707     }
708 
709     @Override
710     public DocumentActionResult releaseGroupAuthority(DocumentActionParameters parameters,
711             final String groupId) {
712         incomingParamCheck(parameters, "parameters");
713         incomingParamCheck(groupId, "groupId");
714         return executeActionInternal(parameters,
715                 new StandardDocumentActionCallback() {
716                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
717                             String principalId, String annotation) throws WorkflowException {
718                         return KEWServiceLocator.getWorkflowDocumentService().releaseGroupAuthority(principalId,
719                                 documentBo, groupId, annotation);
720                     }
721 
722                     public String getActionName() {
723                         return ActionType.RELEASE_GROUP_AUTHORITY.getLabel();
724                     }
725                 });
726 
727     }
728 
729     @Override
730     public DocumentActionResult save(DocumentActionParameters parameters) {
731         incomingParamCheck(parameters, "parameters");
732         return executeActionInternal(parameters, SAVE_CALLBACK);
733     }
734 
735     @Override
736     public DocumentActionResult saveDocumentData(DocumentActionParameters parameters) {
737         incomingParamCheck(parameters, "parameters");
738         return executeActionInternal(parameters, new DocumentActionCallback() {
739 
740             @Override
741             public String getLogMessage(String documentId, String principalId, String annotation) {
742                 return "Saving Routing Data [principalId=" + principalId + ", docId=" + documentId + "]";
743             }
744 
745             @Override
746             public DocumentRouteHeaderValue doInDocumentBo(
747                     DocumentRouteHeaderValue documentBo, String principalId,
748                     String annotation) throws WorkflowException {
749                 return KEWServiceLocator.getWorkflowDocumentService().saveRoutingData(principalId, documentBo);
750             }
751         });
752     }
753 
754     @Override
755     public Document delete(String documentId, String principalId) {
756         incomingParamCheck(documentId, "documentId");
757         incomingParamCheck(principalId, "principalId");
758         DocumentRouteHeaderValue documentBo = init(DocumentActionParameters.create(documentId, principalId, null));
759         if (LOG.isDebugEnabled()) {
760             LOG.debug("Delete [principalId=" + principalId + ", documentId=" + documentId + "]");
761         }
762         Document document = null;
763         try {
764             document = DocumentRouteHeaderValue.to(documentBo);
765             KEWServiceLocator.getWorkflowDocumentService().deleteDocument(principalId, documentBo);
766 
767         } catch (WorkflowException e) {
768             translateException(e);
769         }
770         return document;
771     }
772 
773     @Override
774     public void logAnnotation(String documentId, String principalId, String annotation) {
775         incomingParamCheck(documentId, "documentId");
776         incomingParamCheck(principalId, "principalId");
777         incomingParamCheck(annotation, "annotation");
778         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
779         try {
780             KEWServiceLocator.getWorkflowDocumentService().logDocumentAction(principalId, documentBo, annotation);
781         } catch (WorkflowException e) {
782             translateException(e);
783         }
784     }
785 
786     @Override
787     public void initiateIndexing(String documentId) {
788         incomingParamCheck(documentId, "documentId");
789         DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
790         if (documentBo.getDocumentType().hasSearchableAttributes()) {
791             DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentBo.getDocumentType().getApplicationId());
792             queue.indexDocument(documentId);
793         }
794     }
795 
796     @Override
797     public DocumentActionResult superUserBlanketApprove(DocumentActionParameters parameters,
798             final boolean executePostProcessor) {
799         incomingParamCheck(parameters, "parameters");
800         return executeActionInternal(parameters,
801                 new DocumentActionCallback() {
802                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
803                             String principalId, String annotation) throws WorkflowException {
804                         return KEWServiceLocator.getWorkflowDocumentService().superUserApprove(principalId, documentBo,
805                                 annotation, executePostProcessor);
806                     }
807 
808                     public String getLogMessage(String documentId, String principalId, String annotation) {
809                         return "SU Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
810                                 + ", annotation=" + annotation + "]";
811                     }
812                 });
813     }
814 
815     @Override
816     public DocumentActionResult superUserNodeApprove(DocumentActionParameters parameters,
817             final boolean executePostProcessor, final String nodeName) {
818                 incomingParamCheck(parameters, "parameters");
819                 incomingParamCheck(nodeName, "nodeName");
820         return executeActionInternal(parameters,
821                 new DocumentActionCallback() {
822                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
823                             String principalId, String annotation) throws WorkflowException {
824                         return KEWServiceLocator.getWorkflowDocumentService().superUserNodeApproveAction(principalId,
825                                 documentBo, nodeName, annotation, executePostProcessor);
826                     }
827 
828                     public String getLogMessage(String documentId, String principalId, String annotation) {
829                         return "SU Node Approve Action [principalId=" + principalId + ", documentId=" + documentId
830                                 + ", nodeName=" + nodeName + ", annotation=" + annotation + "]";
831                     }
832                 });
833 
834     }
835 
836     @Override
837     public DocumentActionResult superUserTakeRequestedAction(DocumentActionParameters parameters,
838             final boolean executePostProcessor, final String actionRequestId) {
839                 incomingParamCheck(parameters, "parameters");
840                 incomingParamCheck(actionRequestId, "actionRequestId");
841         return executeActionInternal(parameters,
842                 new DocumentActionCallback() {
843                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
844                             String principalId, String annotation) throws WorkflowException {
845                         return KEWServiceLocator.getWorkflowDocumentService().superUserActionRequestApproveAction(
846                                 principalId, documentBo, actionRequestId, annotation,
847                                 executePostProcessor);
848                     }
849 
850                     public String getLogMessage(String documentId, String principalId, String annotation) {
851                         return "SU Take Requested Action [principalId=" + principalId + ", docume tId=" + documentId
852                                 + ", actionRequestId=" + actionRequestId + ", annotation=" + annotation + "]";
853                     }
854                 });
855     }
856 
857     @Override
858     public DocumentActionResult superUserDisapprove(DocumentActionParameters parameters,
859             final boolean executePostProcessor) {
860                         incomingParamCheck(parameters, "parameters");
861         return executeActionInternal(parameters,
862                 new DocumentActionCallback() {
863                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
864                             String principalId, String annotation) throws WorkflowException {
865                         return KEWServiceLocator.getWorkflowDocumentService().superUserDisapproveAction(principalId,
866                                 documentBo, annotation, executePostProcessor);
867                     }
868 
869                     public String getLogMessage(String documentId, String principalId, String annotation) {
870                         return "SU Disapprove [principalId=" + principalId + ", documentId=" + documentId
871                                 + ", annotation=" + annotation + "]";
872                     }
873                 });
874     }
875 
876     @Override
877     public DocumentActionResult superUserCancel(DocumentActionParameters parameters, final boolean executePostProcessor) {
878                         incomingParamCheck(parameters, "parameters");
879         return executeActionInternal(parameters,
880                 new DocumentActionCallback() {
881                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
882                             String principalId, String annotation) throws WorkflowException {
883                         return KEWServiceLocator.getWorkflowDocumentService().superUserCancelAction(principalId,
884                                 documentBo, annotation, executePostProcessor);
885                     }
886 
887                     public String getLogMessage(String documentId, String principalId, String annotation) {
888                         return "SU Cancel [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
889                                 + annotation + "]";
890                     }
891                 });
892     }
893 
894     @Override
895     public DocumentActionResult superUserReturnToPreviousNode(DocumentActionParameters parameters,
896             final boolean executePostProcessor, final ReturnPoint returnPoint) {
897             incomingParamCheck(parameters, "parameters");
898             incomingParamCheck(returnPoint, "returnPoint");
899         return executeActionInternal(parameters,
900                 new DocumentActionCallback() {
901                     public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
902                             String principalId, String annotation) throws WorkflowException {
903                         return KEWServiceLocator.getWorkflowDocumentService().superUserReturnDocumentToPreviousNode(
904                                 principalId, documentBo, returnPoint.getNodeName(), annotation, executePostProcessor);
905                     }
906 
907                     public String getLogMessage(String documentId, String principalId, String annotation) {
908                         return "SU Return to Previous Node [principalId=" + principalId + ", documentId=" + documentId
909                                 + ", annotation=" + annotation + ", returnPoint=" + returnPoint + "]";
910                     }
911                 });
912 
913     }
914 
915     @Override
916     public DocumentActionResult placeInExceptionRouting(DocumentActionParameters parameters) {
917         incomingParamCheck(parameters, "parameters");
918         return executeActionInternal(parameters, PLACE_IN_EXCEPTION_CALLBACK);
919     }
920 
921     @Override
922     public boolean documentWillHaveAtLeastOneActionRequest(RoutingReportCriteria reportCriteria, List<String> actionRequestedCodes, boolean ignoreCurrentActionRequests) {
923         incomingParamCheck(reportCriteria, "reportCriteria");
924         incomingParamCheck(actionRequestedCodes, "actionRequestedCodes");
925         try {
926 	        SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
927 	        SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
928 	        // set activate requests to true by default so force action works correctly
929 	        criteria.setActivateRequests(Boolean.TRUE);
930 	        SimulationResults results = simulationEngine.runSimulation(criteria);
931             List<ActionRequestValue> actionRequestsToProcess = results.getSimulatedActionRequests();
932             if (!ignoreCurrentActionRequests) {
933                 actionRequestsToProcess.addAll(results.getDocument().getActionRequests());
934             }
935             for (ActionRequestValue actionRequest : actionRequestsToProcess) {
936                 if (actionRequest.isDone()) {
937                     // an action taken has eliminated this request from being active
938                     continue;
939                 }
940 				// if no action request codes are passed in.... assume any request found is
941 		    	if (CollectionUtils.isEmpty(actionRequestedCodes) ) {
942 		    		// we found an action request
943 		    		return true;
944 		    	}
945 		    	// check the action requested codes passed in
946 		    	for (String requestedActionRequestCode : actionRequestedCodes) {
947 					if (requestedActionRequestCode.equals(actionRequest.getActionRequested())) {
948 					    boolean satisfiesDestinationUserCriteria = (criteria.getDestinationRecipients().isEmpty()) || (isRecipientRoutedRequest(actionRequest,criteria.getDestinationRecipients()));
949 					    if (satisfiesDestinationUserCriteria) {
950 					        if (StringUtils.isBlank(criteria.getDestinationNodeName())) {
951 					            return true;
952 					        } else if (StringUtils.equals(criteria.getDestinationNodeName(),actionRequest.getNodeInstance().getName())) {
953 					            return true;
954 					        }
955 					    }
956 					}
957 				}
958 			}
959 	        return false;
960         } catch (Exception ex) {
961         	String error = "Problems evaluating documentWillHaveAtLeastOneActionRequest: " + ex.getMessage();
962             LOG.error(error,ex);
963             if (ex instanceof RuntimeException) {
964             	throw (RuntimeException)ex;
965             }
966             throw new RuntimeException(error, ex);
967         }
968     }
969 
970     private boolean isRecipientRoutedRequest(ActionRequestValue actionRequest, List<Recipient> recipients) throws WorkflowException {
971         for (Recipient recipient : recipients) {
972             if (actionRequest.isRecipientRoutedRequest(recipient)) {
973                 return true;
974             }
975         }
976         return false;
977     }
978 
979     @Override
980     public void reResolveRoleByDocTypeName(String documentTypeName, String roleName, String qualifiedRoleNameLabel) {
981         incomingParamCheck(documentTypeName, "documentTypeName");
982         incomingParamCheck(roleName, "roleName");
983         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
984         if ( LOG.isDebugEnabled() ) {
985         	LOG.debug("Re-resolving Role [docTypeName=" + documentTypeName + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
986         }
987     	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
988     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
989     		KEWServiceLocator.getRoleService().reResolveRole(documentType, roleName);
990     	} else {
991     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(documentType, roleName, qualifiedRoleNameLabel);
992     	}
993     }
994 
995     public void reResolveRoleByDocumentId(String documentId, String roleName, String qualifiedRoleNameLabel) {
996         incomingParamCheck(documentId, "documentId");
997         incomingParamCheck(roleName, "roleName");
998         incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
999         if ( LOG.isDebugEnabled() ) {
1000         	LOG.debug("Re-resolving Role [documentId=" + documentId + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
1001         }
1002         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1003     	if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
1004     		KEWServiceLocator.getRoleService().reResolveRole(routeHeader, roleName);
1005     	} else {
1006     		KEWServiceLocator.getRoleService().reResolveQualifiedRole(routeHeader, roleName, qualifiedRoleNameLabel);
1007     	}
1008     }
1009 
1010     @Override
1011     public List<RemotableAttributeError> validateWorkflowAttributeDefinition(
1012             WorkflowAttributeDefinition definition) {
1013         if (definition == null) {
1014             throw new RiceIllegalArgumentException("definition was null");
1015         }
1016         if ( LOG.isDebugEnabled() ) {
1017             LOG.debug("Validating WorkflowAttributeDefinition [attributeName="+definition.getAttributeName()+"]");
1018         }
1019         AttributeDefinition attributeDefinition = DTOConverter.convertWorkflowAttributeDefinition(definition);
1020         WorkflowRuleAttribute attribute = null;
1021         if (attributeDefinition != null) {
1022             attribute = (WorkflowRuleAttribute) GlobalResourceLoader.getObject(attributeDefinition.getObjectDefinition());
1023         }
1024         if (attribute instanceof GenericXMLRuleAttribute) {
1025             Map<String, String> attributePropMap = new HashMap<String, String>();
1026             GenericXMLRuleAttribute xmlAttribute = (GenericXMLRuleAttribute)attribute;
1027             xmlAttribute.setExtensionDefinition(attributeDefinition.getExtensionDefinition());
1028             for (PropertyDefinition propertyDefinition : definition.getPropertyDefinitions()) {
1029                 attributePropMap.put(propertyDefinition.getName(), propertyDefinition.getValue());
1030             }
1031             xmlAttribute.setParamMap(attributePropMap);
1032     }
1033         List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
1034         //validate inputs from client application if the attribute is capable
1035         if (attribute instanceof WorkflowAttributeXmlValidator) {
1036             List<? extends RemotableAttributeErrorContract> validationErrors = ((WorkflowAttributeXmlValidator)attribute).validateClientRoutingData();
1037             if (validationErrors != null) {
1038                 for (RemotableAttributeErrorContract validationError : validationErrors) {
1039                     errors.add(RemotableAttributeError.Builder.create(validationError).build());
1040                 }
1041             }
1042         }
1043         return errors;
1044     }
1045 
1046     @Override
1047     public boolean isFinalApprover(String documentId, String principalId) {
1048         incomingParamCheck(documentId, "documentId");
1049         incomingParamCheck(principalId, "principalId");
1050         if ( LOG.isDebugEnabled() ) {
1051         	LOG.debug("Evaluating isFinalApprover [docId=" + documentId + ", principalId=" + principalId + "]");
1052         }
1053         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1054         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(documentId);
1055         List<RouteNode> finalApproverNodes = KEWServiceLocator.getRouteNodeService().findFinalApprovalRouteNodes(routeHeader.getDocumentType().getDocumentTypeId());
1056         if (finalApproverNodes.isEmpty()) {
1057         	if ( LOG.isDebugEnabled() ) {
1058         		LOG.debug("Could not locate final approval nodes for document " + documentId);
1059         	}
1060             return false;
1061         }
1062         Set<String> finalApproverNodeNames = new HashSet<String>();
1063         for (RouteNode node : finalApproverNodes) {
1064             finalApproverNodeNames.add(node.getRouteNodeName());
1065         }
1066 
1067         int approveRequest = 0;
1068         for (ActionRequestValue request : requests) {
1069             RouteNodeInstance nodeInstance = request.getNodeInstance();
1070             if (nodeInstance == null) {
1071             	if ( LOG.isDebugEnabled() ) {
1072             		LOG.debug("Found an action request on the document with a null node instance, indicating EXCEPTION routing.");
1073             	}
1074                 return false;
1075             }
1076             if (finalApproverNodeNames.contains(nodeInstance.getRouteNode().getRouteNodeName())) {
1077                 if (request.isApproveOrCompleteRequest()) {
1078                     approveRequest++;
1079                     if ( LOG.isDebugEnabled() ) {
1080                     	LOG.debug("Found request is approver " + request.getActionRequestId());
1081                     }
1082                     if (! request.isRecipientRoutedRequest(principalId)) {
1083                     	if ( LOG.isDebugEnabled() ) {
1084                     		LOG.debug("Action Request not for user " + principalId);
1085                     	}
1086                         return false;
1087                     }
1088                 }
1089             }
1090         }
1091 
1092         if (approveRequest == 0) {
1093             return false;
1094         }
1095         if ( LOG.isDebugEnabled() ) {
1096         	LOG.debug("Principal "+principalId+" is final approver for document " + documentId);
1097         }
1098         return true;
1099     }
1100 
1101     @Override
1102     public boolean routeNodeHasApproverActionRequest(String documentTypeName, String docContent, String nodeName) {
1103         incomingParamCheck(documentTypeName, "documentTypeName");
1104         incomingParamCheck(docContent, "docContent");
1105         incomingParamCheck(nodeName, "nodeName");
1106         if ( LOG.isDebugEnabled() ) {
1107         	LOG.debug("Evaluating routeNodeHasApproverActionRequest [docTypeName=" + documentTypeName + ", nodeName=" + nodeName + "]");
1108         }
1109         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
1110         RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(documentType.getDocumentTypeId(), nodeName);
1111         return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, new Integer(KewApiConstants.INVALID_ROUTE_LEVEL));
1112     }
1113 
1114     /**
1115      * Really this method needs to be implemented using the executeSimulation functionality (the SimulationEngine).
1116      * This would get rid of the needs for us to call to FlexRM directly.
1117      */
1118     private boolean routeNodeHasApproverActionRequest(DocumentType documentType, String docContent, RouteNode node, Integer routeLevel) {
1119         incomingParamCheck(documentType, "documentType");
1120         incomingParamCheck(docContent, "docContent");
1121         incomingParamCheck(node, "node");
1122         incomingParamCheck(routeLevel, "routeLevel");
1123 
1124 /*        DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
1125         routeHeader.setDocumentId("");
1126         routeHeader.setDocumentTypeId(documentType.getDocumentTypeId());
1127         routeHeader.setDocRouteLevel(routeLevel);
1128         routeHeader.setDocVersion(new Integer(KewApiConstants.DocumentContentVersions.CURRENT));*/
1129 
1130         RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(documentType.getName());
1131         builder.setNodeNames(Collections.singletonList(node.getName()));
1132         builder.setXmlContent(docContent);
1133         DocumentDetail docDetail = executeSimulation(builder.build());
1134         if (docDetail != null) {
1135             for (ActionRequest actionRequest : docDetail.getActionRequests()) {
1136                 if (actionRequest.isApprovalRequest()) {
1137                     return true;
1138                 }
1139             }
1140         }
1141         /*if (node.getRuleTemplate() != null && node.isFlexRM()) {
1142             String ruleTemplateName = node.getRuleTemplate().getName();
1143             builder.setXmlContent(docContent);
1144             routeHeader.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
1145             FlexRM flexRM = new FlexRM();
1146     		RouteContext context = RouteContext.getCurrentRouteContext();
1147     		context.setDocument(routeHeader);
1148     		try {
1149     			List actionRequests = flexRM.getActionRequests(routeHeader, node, null, ruleTemplateName);
1150     			for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
1151     				ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
1152     				if (actionRequest.isApproveOrCompleteRequest()) {
1153     					return true;
1154     				}
1155     			}
1156     		} finally {
1157     			RouteContext.clearCurrentRouteContext();
1158     		}
1159         }*/
1160         return false;
1161     }
1162 
1163     @Override
1164     public boolean isLastApproverAtNode(String documentId, String principalId, String nodeName)  {
1165         incomingParamCheck(documentId, "documentId");
1166         incomingParamCheck(principalId, "principalId");
1167         incomingParamCheck(nodeName, "nodeName");
1168         if ( LOG.isDebugEnabled() ) {
1169         	LOG.debug("Evaluating isLastApproverAtNode [docId=" + documentId + ", principalId=" + principalId + ", nodeName=" + nodeName + "]");
1170         }
1171         loadDocument(documentId);
1172         // If this app constant is set to true, then we will attempt to simulate activation of non-active requests before
1173         // attempting to deactivate them, this is in order to address the force action issue reported by EPIC in issue
1174         // http://fms.dfa.cornell.edu:8080/browse/KULWF-366
1175         Boolean activateFirst = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
1176                 KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.FEATURE_DETAIL_TYPE, KewApiConstants.IS_LAST_APPROVER_ACTIVATE_FIRST_IND);
1177         if (activateFirst == null) {
1178             activateFirst = Boolean.FALSE;
1179         }
1180 
1181         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDocRequestCdNodeName(documentId, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, nodeName);
1182         if (requests == null || requests.isEmpty()) {
1183             return false;
1184         }
1185 
1186         // Deep-copy the action requests for the simulation.
1187         List<ActionRequestValue> copiedRequests = new ArrayList<ActionRequestValue>();
1188         for (ActionRequestValue request : requests) {
1189         	ActionRequestValue actionRequest = (ActionRequestValue) SerializationUtils.deepCopy(
1190                     (ActionRequestValue) request);
1191         	// Deep-copy the action items as well, since they are indirectly retrieved from the action request via service calls.
1192         	for (ActionItem actionItem : actionRequest.getActionItems()) {
1193         		actionRequest.getSimulatedActionItems().add((ActionItem) SerializationUtils.deepCopy(actionItem));
1194         	}
1195         	copiedRequests.add(actionRequest);
1196         }
1197 
1198         List<ActionRequestValue> savedRequests = new ArrayList<ActionRequestValue>();
1199         ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
1200         for (ActionRequestValue request : copiedRequests) {
1201             if (activateFirst.booleanValue() && !request.isActive()) {
1202                 request = KEWServiceLocator.getActionRequestService().activateRequest(request, activationContext);
1203             }
1204             if (request.isUserRequest() && request.getPrincipalId().equals(principalId)) {
1205                 request = KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1206             } else if (request.isGroupRequest() && KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, request.getGroup().getId())) {
1207                 request = KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1208             }
1209             savedRequests.add(request);
1210         }
1211         boolean allDeactivated = true;
1212         for (ActionRequestValue actionRequest: savedRequests) {
1213             allDeactivated = allDeactivated && actionRequest.isDeactivated();
1214         }
1215         return allDeactivated;
1216     }
1217 
1218     @Override
1219     public boolean isUserInRouteLog(String documentId, String principalId, boolean lookFuture) {
1220     	incomingParamCheck(documentId, "documentId");
1221         incomingParamCheck(principalId, "principalId");
1222         return isUserInRouteLogWithOptionalFlattening(documentId, principalId, lookFuture, false);
1223     }
1224 
1225     @Override
1226     public boolean isUserInRouteLogWithOptionalFlattening(String documentId, String principalId, boolean lookFuture, boolean flattenNodes) {
1227         incomingParamCheck(documentId, "documentId");
1228         incomingParamCheck(principalId, "principalId");
1229         boolean authorized = false;
1230         if ( LOG.isDebugEnabled() ) {
1231         	LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", principalId=" + principalId + ", lookFuture=" + lookFuture + "]");
1232         }
1233         DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1234         if (routeHeader == null) {
1235             throw new IllegalArgumentException("Document for documentId: " + documentId + " does not exist");
1236         }
1237         Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
1238         if (principal == null) {
1239             throw new IllegalArgumentException("Principal for principalId: " + principalId + " does not exist");
1240         }
1241         List<ActionTakenValue> actionsTaken = KEWServiceLocator.getActionTakenService().findByDocumentIdPrincipalId(documentId, principal.getPrincipalId());
1242 
1243         if(routeHeader.getInitiatorWorkflowId().equals(principal.getPrincipalId())){
1244         	return true;
1245         }
1246 
1247         if (!actionsTaken.isEmpty()) {
1248         	LOG.debug("found action taken by user");
1249         	authorized = true;
1250         }
1251 
1252         List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1253         if (actionRequestListHasPrincipal(principal, actionRequests)) {
1254         	authorized = true;
1255         }
1256 
1257         if (!lookFuture || authorized) {
1258         	return authorized;
1259         }
1260 
1261 
1262         SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1263         SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1264         criteria.setDestinationNodeName(null); // process entire document to conclusion
1265         criteria.getDestinationRecipients().add(new KimPrincipalRecipient(principal));
1266         criteria.setFlattenNodes(flattenNodes);
1267 
1268         try {
1269         	SimulationResults results = simulationEngine.runSimulation(criteria);
1270         	if (actionRequestListHasPrincipal(principal, results.getSimulatedActionRequests())) {
1271         		authorized = true;
1272         	}
1273         } catch (Exception e) {
1274         	throw new RiceRuntimeException(e);
1275         }
1276 
1277         return authorized;
1278     }
1279 
1280     private boolean actionRequestListHasPrincipal(Principal principal, List<ActionRequestValue> actionRequests) {
1281         for (ActionRequestValue actionRequest : actionRequests) {
1282             if (actionRequest.isRecipientRoutedRequest(new KimPrincipalRecipient(principal))) {
1283                 return true;
1284             }
1285         }
1286         return false;
1287     }
1288 
1289     public List<String> getPrincipalIdsInRouteLog(String documentId, boolean lookFuture) {
1290         if (StringUtils.isEmpty(documentId)) {
1291             throw new IllegalArgumentException("documentId passed in is null or blank");
1292         }
1293     	Set<String> principalIds = new HashSet<String>();
1294         try {
1295         	if ( LOG.isDebugEnabled() ) {
1296         		LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", lookFuture=" + lookFuture + "]");
1297         	}
1298             DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1299             List<ActionTakenValue> actionsTakens =
1300             	(List<ActionTakenValue>)KEWServiceLocator.getActionTakenService().findByDocumentId(documentId);
1301             //TODO: confirm that the initiator is not already there in the actionstaken
1302             principalIds.add(routeHeader.getInitiatorWorkflowId());
1303             for(ActionTakenValue actionTaken: actionsTakens){
1304             	principalIds.add(actionTaken.getPrincipalId());
1305             }
1306             List<ActionRequestValue> actionRequests =
1307             	KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1308             for(ActionRequestValue actionRequest: actionRequests){
1309             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1310             }
1311             if (!lookFuture) {
1312             	return new ArrayList<String>(principalIds);
1313             }
1314             SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1315             SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1316             criteria.setDestinationNodeName(null); // process entire document to conclusion
1317             SimulationResults results = simulationEngine.runSimulation(criteria);
1318             actionRequests = (List<ActionRequestValue>)results.getSimulatedActionRequests();
1319             for(ActionRequestValue actionRequest: actionRequests){
1320             	principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1321             }
1322         } catch (Exception ex) {
1323             LOG.warn("Problems getting principalIds in Route Log for documentId: "+documentId+". Exception:"+ex.getMessage(),ex);
1324         }
1325     	return new ArrayList<String>(principalIds);
1326     }
1327 
1328     private DocumentRouteHeaderValue loadDocument(String documentId) {
1329         return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
1330     }
1331 
1332     /**
1333 	 * This method gets all of the principalIds for the given ActionRequestValue.  It drills down into
1334 	 * groups if need be.
1335 	 *
1336 	 * @param actionRequest
1337 	 */
1338 	private List<String> getPrincipalIdsForActionRequest(ActionRequestValue actionRequest) {
1339 		List<String> results = Collections.emptyList();
1340 		if (actionRequest.getPrincipalId() != null) {
1341 			results = Collections.singletonList(actionRequest.getPrincipalId());
1342 		} else if (actionRequest.getGroupId() != null) {
1343 			List<String> principalIdsForGroup =
1344 				KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
1345 			if (principalIdsForGroup != null) {
1346 				results = principalIdsForGroup;
1347 			}
1348 		}
1349 		return results;
1350 	}
1351 
1352     private void incomingParamCheck(Object object, String name) {
1353         if (object == null) {
1354             throw new RiceIllegalArgumentException(name + " was null");
1355         } else if (object instanceof String
1356                 && StringUtils.isBlank((String) object)) {
1357             throw new RiceIllegalArgumentException(name + " was blank");
1358         }
1359     }
1360 
1361     public void setDocumentTypeService(DocumentTypeService documentTypeService) {
1362         this.documentTypeService = documentTypeService;
1363     }
1364 
1365     /**
1366      * TODO - this code is temporary until we get rid of all the crazy throwing of
1367      * "WorkflowException"
1368      */
1369     private void translateException(WorkflowException e) {
1370         if (e instanceof org.kuali.rice.kew.api.exception.InvalidActionTakenException) {
1371             throw new InvalidActionTakenException(e.getMessage(), e);
1372         }
1373         throw new WorkflowRuntimeException(e.getMessage(), e);
1374     }
1375 
1376     protected DocumentActionResult executeActionInternal(DocumentActionParameters parameters,
1377             DocumentActionCallback callback) {
1378         if (parameters == null) {
1379             throw new RiceIllegalArgumentException("Document action parameters was null.");
1380         }
1381         if (LOG.isDebugEnabled()) {
1382             LOG.debug(callback.getLogMessage(parameters.getDocumentId(), parameters.getPrincipalId(),
1383                     parameters.getAnnotation()));
1384         }
1385         DocumentRouteHeaderValue documentBo = init(parameters);
1386         try {
1387             documentBo = callback.doInDocumentBo(documentBo, parameters.getPrincipalId(), parameters.getAnnotation());
1388         } catch (WorkflowException e) {
1389             // TODO fix this up once the checked exception goes away
1390             translateException(e);
1391         }
1392         return constructDocumentActionResult(documentBo, parameters.getPrincipalId());
1393     }
1394 
1395     protected static interface DocumentActionCallback {
1396 
1397         DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
1398                 String annotation) throws WorkflowException;
1399 
1400         String getLogMessage(String documentId, String principalId, String annotation);
1401 
1402     }
1403 
1404     protected static abstract class StandardDocumentActionCallback implements DocumentActionCallback {
1405 
1406         public final String getLogMessage(String documentId, String principalId, String annotation) {
1407             return getActionName() + " [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
1408                     + annotation + "]";
1409         }
1410 
1411         protected abstract String getActionName();
1412 
1413     }
1414 
1415 }