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