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