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.RemotableAttributeError;
025import org.kuali.rice.core.api.uif.RemotableAttributeErrorContract;
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.KewApiConstants;
033import org.kuali.rice.kew.api.KewApiServiceLocator;
034import org.kuali.rice.kew.api.WorkflowRuntimeException;
035import org.kuali.rice.kew.api.action.ActionRequest;
036import org.kuali.rice.kew.api.action.ActionRequestType;
037import org.kuali.rice.kew.api.action.ActionType;
038import org.kuali.rice.kew.api.action.AdHocRevoke;
039import org.kuali.rice.kew.api.action.AdHocToGroup;
040import org.kuali.rice.kew.api.action.AdHocToGroup_v2_1_2;
041import org.kuali.rice.kew.api.action.AdHocToPrincipal;
042import org.kuali.rice.kew.api.action.AdHocToPrincipal_v2_1_2;
043import org.kuali.rice.kew.api.action.DocumentActionParameters;
044import org.kuali.rice.kew.api.action.DocumentActionResult;
045import org.kuali.rice.kew.api.action.InvalidActionTakenException;
046import org.kuali.rice.kew.api.action.MovePoint;
047import org.kuali.rice.kew.api.action.RequestedActions;
048import org.kuali.rice.kew.api.action.ReturnPoint;
049import org.kuali.rice.kew.api.action.RoutingReportCriteria;
050import org.kuali.rice.kew.api.action.ValidActions;
051import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
052import org.kuali.rice.kew.api.doctype.DocumentTypeService;
053import org.kuali.rice.kew.api.doctype.IllegalDocumentTypeException;
054import org.kuali.rice.kew.api.document.Document;
055import org.kuali.rice.kew.api.document.DocumentContentUpdate;
056import org.kuali.rice.kew.api.document.DocumentDetail;
057import org.kuali.rice.kew.api.document.DocumentUpdate;
058import org.kuali.rice.kew.api.document.PropertyDefinition;
059import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
060import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
061import org.kuali.rice.kew.api.exception.WorkflowException;
062import org.kuali.rice.kew.definition.AttributeDefinition;
063import org.kuali.rice.kew.doctype.bo.DocumentType;
064import org.kuali.rice.kew.dto.DTOConverter;
065import org.kuali.rice.kew.engine.ActivationContext;
066import org.kuali.rice.kew.engine.node.RouteNode;
067import org.kuali.rice.kew.engine.node.RouteNodeInstance;
068import org.kuali.rice.kew.engine.simulation.SimulationCriteria;
069import org.kuali.rice.kew.engine.simulation.SimulationResults;
070import org.kuali.rice.kew.engine.simulation.SimulationWorkflowEngine;
071import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
072import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
073import org.kuali.rice.kew.rule.WorkflowRuleAttribute;
074import org.kuali.rice.kew.rule.xmlrouting.GenericXMLRuleAttribute;
075import org.kuali.rice.kew.service.KEWServiceLocator;
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        DocumentRouteHeaderValue documentBo = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
766        if (documentBo.getDocumentType().hasSearchableAttributes()) {
767            DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentBo.getDocumentType().getApplicationId());
768            queue.indexDocument(documentId);
769        }
770    }
771
772    @Override
773    public DocumentActionResult superUserBlanketApprove(DocumentActionParameters parameters,
774            final boolean executePostProcessor) {
775        incomingParamCheck(parameters, "parameters");
776        return executeActionInternal(parameters,
777                new DocumentActionCallback() {
778                    public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
779                            String principalId, String annotation) throws WorkflowException {
780                        return KEWServiceLocator.getWorkflowDocumentService().superUserApprove(principalId, documentBo,
781                                annotation, executePostProcessor);
782                    }
783
784                    public String getLogMessage(String documentId, String principalId, String annotation) {
785                        return "SU Blanket Approve [principalId=" + principalId + ", documentId=" + documentId
786                                + ", annotation=" + annotation + "]";
787                    }
788                });
789    }
790
791    @Override
792    public DocumentActionResult superUserNodeApprove(DocumentActionParameters parameters,
793            final boolean executePostProcessor, final String nodeName) {
794                incomingParamCheck(parameters, "parameters");
795                incomingParamCheck(nodeName, "nodeName");
796        return executeActionInternal(parameters,
797                new DocumentActionCallback() {
798                    public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
799                            String principalId, String annotation) throws WorkflowException {
800                        return KEWServiceLocator.getWorkflowDocumentService().superUserNodeApproveAction(principalId,
801                                documentBo, nodeName, annotation, executePostProcessor);
802                    }
803
804                    public String getLogMessage(String documentId, String principalId, String annotation) {
805                        return "SU Node Approve Action [principalId=" + principalId + ", documentId=" + documentId
806                                + ", nodeName=" + nodeName + ", annotation=" + annotation + "]";
807                    }
808                });
809
810    }
811
812    @Override
813    public DocumentActionResult superUserTakeRequestedAction(DocumentActionParameters parameters,
814            final boolean executePostProcessor, final String actionRequestId) {
815                incomingParamCheck(parameters, "parameters");
816                incomingParamCheck(actionRequestId, "actionRequestId");
817        return executeActionInternal(parameters,
818                new DocumentActionCallback() {
819                    public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
820                            String principalId, String annotation) throws WorkflowException {
821                        return KEWServiceLocator.getWorkflowDocumentService().superUserActionRequestApproveAction(
822                                principalId, documentBo, actionRequestId, annotation,
823                                executePostProcessor);
824                    }
825
826                    public String getLogMessage(String documentId, String principalId, String annotation) {
827                        return "SU Take Requested Action [principalId=" + principalId + ", docume tId=" + documentId
828                                + ", actionRequestId=" + actionRequestId + ", annotation=" + annotation + "]";
829                    }
830                });
831    }
832
833    @Override
834    public DocumentActionResult superUserDisapprove(DocumentActionParameters parameters,
835            final boolean executePostProcessor) {
836                        incomingParamCheck(parameters, "parameters");
837        return executeActionInternal(parameters,
838                new DocumentActionCallback() {
839                    public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
840                            String principalId, String annotation) throws WorkflowException {
841                        return KEWServiceLocator.getWorkflowDocumentService().superUserDisapproveAction(principalId,
842                                documentBo, annotation, executePostProcessor);
843                    }
844
845                    public String getLogMessage(String documentId, String principalId, String annotation) {
846                        return "SU Disapprove [principalId=" + principalId + ", documentId=" + documentId
847                                + ", annotation=" + annotation + "]";
848                    }
849                });
850    }
851
852    @Override
853    public DocumentActionResult superUserCancel(DocumentActionParameters parameters, final boolean executePostProcessor) {
854                        incomingParamCheck(parameters, "parameters");
855        return executeActionInternal(parameters,
856                new DocumentActionCallback() {
857                    public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
858                            String principalId, String annotation) throws WorkflowException {
859                        return KEWServiceLocator.getWorkflowDocumentService().superUserCancelAction(principalId,
860                                documentBo, annotation, executePostProcessor);
861                    }
862
863                    public String getLogMessage(String documentId, String principalId, String annotation) {
864                        return "SU Cancel [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
865                                + annotation + "]";
866                    }
867                });
868    }
869
870    @Override
871    public DocumentActionResult superUserReturnToPreviousNode(DocumentActionParameters parameters,
872            final boolean executePostProcessor, final ReturnPoint returnPoint) {
873            incomingParamCheck(parameters, "parameters");
874            incomingParamCheck(returnPoint, "returnPoint");
875        return executeActionInternal(parameters,
876                new DocumentActionCallback() {
877                    public DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo,
878                            String principalId, String annotation) throws WorkflowException {
879                        return KEWServiceLocator.getWorkflowDocumentService().superUserReturnDocumentToPreviousNode(
880                                principalId, documentBo, returnPoint.getNodeName(), annotation, executePostProcessor);
881                    }
882
883                    public String getLogMessage(String documentId, String principalId, String annotation) {
884                        return "SU Return to Previous Node [principalId=" + principalId + ", documentId=" + documentId
885                                + ", annotation=" + annotation + ", returnPoint=" + returnPoint + "]";
886                    }
887                });
888
889    }
890
891    @Override
892    public DocumentActionResult placeInExceptionRouting(DocumentActionParameters parameters) {
893        incomingParamCheck(parameters, "parameters");
894        return executeActionInternal(parameters, PLACE_IN_EXCEPTION_CALLBACK);
895    }
896
897    @Override
898    public boolean documentWillHaveAtLeastOneActionRequest(RoutingReportCriteria reportCriteria, List<String> actionRequestedCodes, boolean ignoreCurrentActionRequests) {
899        incomingParamCheck(reportCriteria, "reportCriteria");
900        incomingParamCheck(actionRequestedCodes, "actionRequestedCodes");
901        try {
902                SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
903                SimulationCriteria criteria = SimulationCriteria.from(reportCriteria);
904                // set activate requests to true by default so force action works correctly
905                criteria.setActivateRequests(Boolean.TRUE);
906                SimulationResults results = simulationEngine.runSimulation(criteria);
907            List<ActionRequestValue> actionRequestsToProcess = results.getSimulatedActionRequests();
908            if (!ignoreCurrentActionRequests) {
909                actionRequestsToProcess.addAll(results.getDocument().getActionRequests());
910            }
911            for (ActionRequestValue actionRequest : actionRequestsToProcess) {
912                if (actionRequest.isDone()) {
913                    // an action taken has eliminated this request from being active
914                    continue;
915                }
916                                // if no action request codes are passed in.... assume any request found is
917                        if (CollectionUtils.isEmpty(actionRequestedCodes) ) {
918                                // we found an action request
919                                return true;
920                        }
921                        // check the action requested codes passed in
922                        for (String requestedActionRequestCode : actionRequestedCodes) {
923                                        if (requestedActionRequestCode.equals(actionRequest.getActionRequested())) {
924                                            boolean satisfiesDestinationUserCriteria = (criteria.getDestinationRecipients().isEmpty()) || (isRecipientRoutedRequest(actionRequest,criteria.getDestinationRecipients()));
925                                            if (satisfiesDestinationUserCriteria) {
926                                                if (StringUtils.isBlank(criteria.getDestinationNodeName())) {
927                                                    return true;
928                                                } else if (StringUtils.equals(criteria.getDestinationNodeName(),actionRequest.getNodeInstance().getName())) {
929                                                    return true;
930                                                }
931                                            }
932                                        }
933                                }
934                        }
935                return false;
936        } catch (Exception ex) {
937                String error = "Problems evaluating documentWillHaveAtLeastOneActionRequest: " + ex.getMessage();
938            LOG.error(error,ex);
939            if (ex instanceof RuntimeException) {
940                throw (RuntimeException)ex;
941            }
942            throw new RuntimeException(error, ex);
943        }
944    }
945
946    private boolean isRecipientRoutedRequest(ActionRequestValue actionRequest, List<Recipient> recipients) throws WorkflowException {
947        for (Recipient recipient : recipients) {
948            if (actionRequest.isRecipientRoutedRequest(recipient)) {
949                return true;
950            }
951        }
952        return false;
953    }
954
955    @Override
956    public void reResolveRoleByDocTypeName(String documentTypeName, String roleName, String qualifiedRoleNameLabel) {
957        incomingParamCheck(documentTypeName, "documentTypeName");
958        incomingParamCheck(roleName, "roleName");
959        incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
960        if ( LOG.isDebugEnabled() ) {
961                LOG.debug("Re-resolving Role [docTypeName=" + documentTypeName + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
962        }
963        DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
964        if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
965                KEWServiceLocator.getRoleService().reResolveRole(documentType, roleName);
966        } else {
967                KEWServiceLocator.getRoleService().reResolveQualifiedRole(documentType, roleName, qualifiedRoleNameLabel);
968        }
969    }
970
971    public void reResolveRoleByDocumentId(String documentId, String roleName, String qualifiedRoleNameLabel) {
972        incomingParamCheck(documentId, "documentId");
973        incomingParamCheck(roleName, "roleName");
974        incomingParamCheck(qualifiedRoleNameLabel, "qualifiedRoleNameLabel");
975        if ( LOG.isDebugEnabled() ) {
976                LOG.debug("Re-resolving Role [documentId=" + documentId + ", roleName=" + roleName + ", qualifiedRoleNameLabel=" + qualifiedRoleNameLabel + "]");
977        }
978        DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
979        if (org.apache.commons.lang.StringUtils.isEmpty(qualifiedRoleNameLabel)) {
980                KEWServiceLocator.getRoleService().reResolveRole(routeHeader, roleName);
981        } else {
982                KEWServiceLocator.getRoleService().reResolveQualifiedRole(routeHeader, roleName, qualifiedRoleNameLabel);
983        }
984    }
985
986    @Override
987    public List<RemotableAttributeError> validateWorkflowAttributeDefinition(
988            WorkflowAttributeDefinition definition) {
989        if (definition == null) {
990            throw new RiceIllegalArgumentException("definition was null");
991        }
992        if ( LOG.isDebugEnabled() ) {
993            LOG.debug("Validating WorkflowAttributeDefinition [attributeName="+definition.getAttributeName()+"]");
994        }
995        AttributeDefinition attributeDefinition = DTOConverter.convertWorkflowAttributeDefinition(definition);
996        WorkflowRuleAttribute attribute = null;
997        if (attributeDefinition != null) {
998            attribute = (WorkflowRuleAttribute) GlobalResourceLoader.getObject(attributeDefinition.getObjectDefinition());
999        }
1000        if (attribute instanceof GenericXMLRuleAttribute) {
1001            Map<String, String> attributePropMap = new HashMap<String, String>();
1002            GenericXMLRuleAttribute xmlAttribute = (GenericXMLRuleAttribute)attribute;
1003            xmlAttribute.setExtensionDefinition(attributeDefinition.getExtensionDefinition());
1004            for (PropertyDefinition propertyDefinition : definition.getPropertyDefinitions()) {
1005                attributePropMap.put(propertyDefinition.getName(), propertyDefinition.getValue());
1006            }
1007            xmlAttribute.setParamMap(attributePropMap);
1008    }
1009        List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
1010        //validate inputs from client application if the attribute is capable
1011        if (attribute instanceof WorkflowAttributeXmlValidator) {
1012            List<? extends RemotableAttributeErrorContract> validationErrors = ((WorkflowAttributeXmlValidator)attribute).validateClientRoutingData();
1013            if (validationErrors != null) {
1014                for (RemotableAttributeErrorContract validationError : validationErrors) {
1015                    errors.add(RemotableAttributeError.Builder.create(validationError).build());
1016                }
1017            }
1018        }
1019        return errors;
1020    }
1021
1022    @Override
1023    public boolean isFinalApprover(String documentId, String principalId) {
1024        incomingParamCheck(documentId, "documentId");
1025        incomingParamCheck(principalId, "principalId");
1026        if ( LOG.isDebugEnabled() ) {
1027                LOG.debug("Evaluating isFinalApprover [docId=" + documentId + ", principalId=" + principalId + "]");
1028        }
1029        DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1030        List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(documentId);
1031        List<RouteNode> finalApproverNodes = KEWServiceLocator.getRouteNodeService().findFinalApprovalRouteNodes(routeHeader.getDocumentType().getDocumentTypeId());
1032        if (finalApproverNodes.isEmpty()) {
1033                if ( LOG.isDebugEnabled() ) {
1034                        LOG.debug("Could not locate final approval nodes for document " + documentId);
1035                }
1036            return false;
1037        }
1038        Set<String> finalApproverNodeNames = new HashSet<String>();
1039        for (RouteNode node : finalApproverNodes) {
1040            finalApproverNodeNames.add(node.getRouteNodeName());
1041        }
1042
1043        int approveRequest = 0;
1044        for (ActionRequestValue request : requests) {
1045            RouteNodeInstance nodeInstance = request.getNodeInstance();
1046            if (nodeInstance == null) {
1047                if ( LOG.isDebugEnabled() ) {
1048                        LOG.debug("Found an action request on the document with a null node instance, indicating EXCEPTION routing.");
1049                }
1050                return false;
1051            }
1052            if (finalApproverNodeNames.contains(nodeInstance.getRouteNode().getRouteNodeName())) {
1053                if (request.isApproveOrCompleteRequest()) {
1054                    approveRequest++;
1055                    if ( LOG.isDebugEnabled() ) {
1056                        LOG.debug("Found request is approver " + request.getActionRequestId());
1057                    }
1058                    if (! request.isRecipientRoutedRequest(principalId)) {
1059                        if ( LOG.isDebugEnabled() ) {
1060                                LOG.debug("Action Request not for user " + principalId);
1061                        }
1062                        return false;
1063                    }
1064                }
1065            }
1066        }
1067
1068        if (approveRequest == 0) {
1069            return false;
1070        }
1071        if ( LOG.isDebugEnabled() ) {
1072                LOG.debug("Principal "+principalId+" is final approver for document " + documentId);
1073        }
1074        return true;
1075    }
1076
1077    @Override
1078    public boolean routeNodeHasApproverActionRequest(String documentTypeName, String docContent, String nodeName) {
1079        incomingParamCheck(documentTypeName, "documentTypeName");
1080        incomingParamCheck(docContent, "docContent");
1081        incomingParamCheck(nodeName, "nodeName");
1082        if ( LOG.isDebugEnabled() ) {
1083                LOG.debug("Evaluating routeNodeHasApproverActionRequest [docTypeName=" + documentTypeName + ", nodeName=" + nodeName + "]");
1084        }
1085        DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
1086        RouteNode routeNode = KEWServiceLocator.getRouteNodeService().findRouteNodeByName(documentType.getDocumentTypeId(), nodeName);
1087        return routeNodeHasApproverActionRequest(documentType, docContent, routeNode, new Integer(KewApiConstants.INVALID_ROUTE_LEVEL));
1088    }
1089
1090    /**
1091     * Really this method needs to be implemented using the executeSimulation functionality (the SimulationEngine).
1092     * This would get rid of the needs for us to call to FlexRM directly.
1093     */
1094    private boolean routeNodeHasApproverActionRequest(DocumentType documentType, String docContent, RouteNode node, Integer routeLevel) {
1095        incomingParamCheck(documentType, "documentType");
1096        incomingParamCheck(docContent, "docContent");
1097        incomingParamCheck(node, "node");
1098        incomingParamCheck(routeLevel, "routeLevel");
1099
1100/*        DocumentRouteHeaderValue routeHeader = new DocumentRouteHeaderValue();
1101        routeHeader.setDocumentId("");
1102        routeHeader.setDocumentTypeId(documentType.getDocumentTypeId());
1103        routeHeader.setDocRouteLevel(routeLevel);
1104        routeHeader.setDocVersion(new Integer(KewApiConstants.DocumentContentVersions.CURRENT));*/
1105
1106        RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(documentType.getName());
1107        builder.setNodeNames(Collections.singletonList(node.getName()));
1108        builder.setXmlContent(docContent);
1109        DocumentDetail docDetail = executeSimulation(builder.build());
1110        if (docDetail != null) {
1111            for (ActionRequest actionRequest : docDetail.getActionRequests()) {
1112                if (actionRequest.isApprovalRequest()) {
1113                    return true;
1114                }
1115            }
1116        }
1117        /*if (node.getRuleTemplate() != null && node.isFlexRM()) {
1118            String ruleTemplateName = node.getRuleTemplate().getName();
1119            builder.setXmlContent(docContent);
1120            routeHeader.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
1121            FlexRM flexRM = new FlexRM();
1122                RouteContext context = RouteContext.getCurrentRouteContext();
1123                context.setDocument(routeHeader);
1124                try {
1125                        List actionRequests = flexRM.getActionRequests(routeHeader, node, null, ruleTemplateName);
1126                        for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
1127                                ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
1128                                if (actionRequest.isApproveOrCompleteRequest()) {
1129                                        return true;
1130                                }
1131                        }
1132                } finally {
1133                        RouteContext.clearCurrentRouteContext();
1134                }
1135        }*/
1136        return false;
1137    }
1138
1139    @Override
1140    public boolean isLastApproverAtNode(String documentId, String principalId, String nodeName)  {
1141        incomingParamCheck(documentId, "documentId");
1142        incomingParamCheck(principalId, "principalId");
1143        incomingParamCheck(nodeName, "nodeName");
1144        if ( LOG.isDebugEnabled() ) {
1145                LOG.debug("Evaluating isLastApproverAtNode [docId=" + documentId + ", principalId=" + principalId + ", nodeName=" + nodeName + "]");
1146        }
1147        loadDocument(documentId);
1148        // If this app constant is set to true, then we will attempt to simulate activation of non-active requests before
1149        // attempting to deactivate them, this is in order to address the force action issue reported by EPIC in issue
1150        // http://fms.dfa.cornell.edu:8080/browse/KULWF-366
1151        Boolean activateFirst = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
1152                KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.FEATURE_DETAIL_TYPE, KewApiConstants.IS_LAST_APPROVER_ACTIVATE_FIRST_IND);
1153        if (activateFirst == null) {
1154            activateFirst = Boolean.FALSE;
1155        }
1156
1157        List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingByDocRequestCdNodeName(documentId, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, nodeName);
1158        if (requests == null || requests.isEmpty()) {
1159            return false;
1160        }
1161
1162        // Deep-copy the action requests for the simulation.
1163        List<ActionRequestValue> copiedRequests = new ArrayList<ActionRequestValue>();
1164        for (ActionRequestValue request : requests) {
1165                ActionRequestValue actionRequest = (ActionRequestValue) ObjectUtils.deepCopy(
1166                    (ActionRequestValue) request);
1167                // Deep-copy the action items as well, since they are indirectly retrieved from the action request via service calls.
1168                for (ActionItem actionItem : actionRequest.getActionItems()) {
1169                        actionRequest.getSimulatedActionItems().add((ActionItem) ObjectUtils.deepCopy(actionItem));
1170                }
1171                copiedRequests.add(actionRequest);
1172        }
1173
1174        ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
1175        for (ActionRequestValue request : copiedRequests) {
1176            if (activateFirst.booleanValue() && !request.isActive()) {
1177                KEWServiceLocator.getActionRequestService().activateRequest(request, activationContext);
1178            }
1179            if (request.isUserRequest() && request.getPrincipalId().equals(principalId)) {
1180                KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1181            } else if (request.isGroupRequest() && KimApiServiceLocator.getGroupService().isMemberOfGroup(principalId, request.getGroup().getId())) {
1182                KEWServiceLocator.getActionRequestService().deactivateRequest(null, request, activationContext);
1183            }
1184        }
1185        boolean allDeactivated = true;
1186        for (ActionRequestValue actionRequest: copiedRequests) {
1187            allDeactivated = allDeactivated && actionRequest.isDeactivated();
1188        }
1189        return allDeactivated;
1190    }
1191
1192    @Override
1193    public boolean isUserInRouteLog(String documentId, String principalId, boolean lookFuture) {
1194        incomingParamCheck(documentId, "documentId");
1195        incomingParamCheck(principalId, "principalId");
1196        return isUserInRouteLogWithOptionalFlattening(documentId, principalId, lookFuture, false);
1197    }
1198
1199    @Override
1200    public boolean isUserInRouteLogWithOptionalFlattening(String documentId, String principalId, boolean lookFuture, boolean flattenNodes) {
1201        incomingParamCheck(documentId, "documentId");
1202        incomingParamCheck(principalId, "principalId");
1203        boolean authorized = false;
1204        if ( LOG.isDebugEnabled() ) {
1205                LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", principalId=" + principalId + ", lookFuture=" + lookFuture + "]");
1206        }
1207        DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1208        if (routeHeader == null) {
1209            throw new IllegalArgumentException("Document for documentId: " + documentId + " does not exist");
1210        }
1211        Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipal(principalId);
1212        if (principal == null) {
1213            throw new IllegalArgumentException("Principal for principalId: " + principalId + " does not exist");
1214        }
1215        List<ActionTakenValue> actionsTaken = KEWServiceLocator.getActionTakenService().findByDocumentIdWorkflowId(documentId, principal.getPrincipalId());
1216
1217        if(routeHeader.getInitiatorWorkflowId().equals(principal.getPrincipalId())){
1218                return true;
1219        }
1220
1221        if (!actionsTaken.isEmpty()) {
1222                LOG.debug("found action taken by user");
1223                authorized = true;
1224        }
1225
1226        List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1227        if (actionRequestListHasPrincipal(principal, actionRequests)) {
1228                authorized = true;
1229        }
1230
1231        if (!lookFuture || authorized) {
1232                return authorized;
1233        }
1234
1235
1236        SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1237        SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1238        criteria.setDestinationNodeName(null); // process entire document to conclusion
1239        criteria.getDestinationRecipients().add(new KimPrincipalRecipient(principal));
1240        criteria.setFlattenNodes(flattenNodes);
1241
1242        try {
1243                SimulationResults results = simulationEngine.runSimulation(criteria);
1244                if (actionRequestListHasPrincipal(principal, results.getSimulatedActionRequests())) {
1245                        authorized = true;
1246                }
1247        } catch (Exception e) {
1248                throw new RiceRuntimeException(e);
1249        }
1250
1251        return authorized;
1252    }
1253
1254    private boolean actionRequestListHasPrincipal(Principal principal, List<ActionRequestValue> actionRequests) {
1255        for (ActionRequestValue actionRequest : actionRequests) {
1256            if (actionRequest.isRecipientRoutedRequest(new KimPrincipalRecipient(principal))) {
1257                return true;
1258            }
1259        }
1260        return false;
1261    }
1262
1263    public List<String> getPrincipalIdsInRouteLog(String documentId, boolean lookFuture) {
1264        if (StringUtils.isEmpty(documentId)) {
1265            throw new IllegalArgumentException("documentId passed in is null or blank");
1266        }
1267        Set<String> principalIds = new HashSet<String>();
1268        try {
1269                if ( LOG.isDebugEnabled() ) {
1270                        LOG.debug("Evaluating isUserInRouteLog [docId=" + documentId + ", lookFuture=" + lookFuture + "]");
1271                }
1272            DocumentRouteHeaderValue routeHeader = loadDocument(documentId);
1273            List<ActionTakenValue> actionsTakens =
1274                (List<ActionTakenValue>)KEWServiceLocator.getActionTakenService().findByDocumentId(documentId);
1275            //TODO: confirm that the initiator is not already there in the actionstaken
1276            principalIds.add(routeHeader.getInitiatorWorkflowId());
1277            for(ActionTakenValue actionTaken: actionsTakens){
1278                principalIds.add(actionTaken.getPrincipalId());
1279            }
1280            List<ActionRequestValue> actionRequests =
1281                KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(documentId);
1282            for(ActionRequestValue actionRequest: actionRequests){
1283                principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1284            }
1285            if (!lookFuture) {
1286                return new ArrayList<String>(principalIds);
1287            }
1288            SimulationWorkflowEngine simulationEngine = KEWServiceLocator.getSimulationEngine();
1289            SimulationCriteria criteria = SimulationCriteria.createSimulationCritUsingDocumentId(documentId);
1290            criteria.setDestinationNodeName(null); // process entire document to conclusion
1291            SimulationResults results = simulationEngine.runSimulation(criteria);
1292            actionRequests = (List<ActionRequestValue>)results.getSimulatedActionRequests();
1293            for(ActionRequestValue actionRequest: actionRequests){
1294                principalIds.addAll(getPrincipalIdsForActionRequest(actionRequest));
1295            }
1296        } catch (Exception ex) {
1297            LOG.warn("Problems getting principalIds in Route Log for documentId: "+documentId+". Exception:"+ex.getMessage(),ex);
1298        }
1299        return new ArrayList<String>(principalIds);
1300    }
1301
1302    private DocumentRouteHeaderValue loadDocument(String documentId) {
1303        return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
1304    }
1305
1306    /**
1307         * This method gets all of the principalIds for the given ActionRequestValue.  It drills down into
1308         * groups if need be.
1309         *
1310         * @param actionRequest
1311         */
1312        private List<String> getPrincipalIdsForActionRequest(ActionRequestValue actionRequest) {
1313                List<String> results = Collections.emptyList();
1314                if (actionRequest.getPrincipalId() != null) {
1315                        results = Collections.singletonList(actionRequest.getPrincipalId());
1316                } else if (actionRequest.getGroupId() != null) {
1317                        List<String> principalIdsForGroup =
1318                                KimApiServiceLocator.getGroupService().getMemberPrincipalIds(actionRequest.getGroupId());
1319                        if (principalIdsForGroup != null) {
1320                                results = principalIdsForGroup;
1321                        }
1322                }
1323                return results;
1324        }
1325
1326    private void incomingParamCheck(Object object, String name) {
1327        if (object == null) {
1328            throw new RiceIllegalArgumentException(name + " was null");
1329        } else if (object instanceof String
1330                && StringUtils.isBlank((String) object)) {
1331            throw new RiceIllegalArgumentException(name + " was blank");
1332        }
1333    }
1334
1335    public void setDocumentTypeService(DocumentTypeService documentTypeService) {
1336        this.documentTypeService = documentTypeService;
1337    }
1338
1339    /**
1340     * TODO - this code is temporary until we get rid of all the crazy throwing of
1341     * "WorkflowException"
1342     */
1343    private void translateException(WorkflowException e) {
1344        if (e instanceof org.kuali.rice.kew.api.exception.InvalidActionTakenException) {
1345            throw new InvalidActionTakenException(e.getMessage(), e);
1346        }
1347        throw new WorkflowRuntimeException(e.getMessage(), e);
1348    }
1349
1350    protected DocumentActionResult executeActionInternal(DocumentActionParameters parameters,
1351            DocumentActionCallback callback) {
1352        if (parameters == null) {
1353            throw new RiceIllegalArgumentException("Document action parameters was null.");
1354        }
1355        if (LOG.isDebugEnabled()) {
1356            LOG.debug(callback.getLogMessage(parameters.getDocumentId(), parameters.getPrincipalId(),
1357                    parameters.getAnnotation()));
1358        }
1359        DocumentRouteHeaderValue documentBo = init(parameters);
1360        try {
1361            documentBo = callback.doInDocumentBo(documentBo, parameters.getPrincipalId(), parameters.getAnnotation());
1362        } catch (WorkflowException e) {
1363            // TODO fix this up once the checked exception goes away
1364            translateException(e);
1365        }
1366        return constructDocumentActionResult(documentBo, parameters.getPrincipalId());
1367    }
1368
1369    protected static interface DocumentActionCallback {
1370
1371        DocumentRouteHeaderValue doInDocumentBo(DocumentRouteHeaderValue documentBo, String principalId,
1372                String annotation) throws WorkflowException;
1373
1374        String getLogMessage(String documentId, String principalId, String annotation);
1375
1376    }
1377
1378    protected static abstract class StandardDocumentActionCallback implements DocumentActionCallback {
1379
1380        public final String getLogMessage(String documentId, String principalId, String annotation) {
1381            return getActionName() + " [principalId=" + principalId + ", documentId=" + documentId + ", annotation="
1382                    + annotation + "]";
1383        }
1384
1385        protected abstract String getActionName();
1386
1387    }
1388
1389}