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