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