001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.actions;
017
018import java.util.Collections;
019import java.util.HashSet;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Set;
023
024import org.apache.log4j.MDC;
025import org.kuali.rice.kew.actionrequest.ActionRequestValue;
026import org.kuali.rice.kew.actionrequest.Recipient;
027import org.kuali.rice.kew.actions.ActionTakenEvent;
028import org.kuali.rice.kew.api.KewApiServiceLocator;
029import org.kuali.rice.kew.api.document.DocumentOrchestrationQueue;
030import org.kuali.rice.kew.actiontaken.ActionTakenValue;
031import org.kuali.rice.kew.api.WorkflowRuntimeException;
032import org.kuali.rice.kew.api.document.DocumentProcessingOptions;
033import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
034import org.kuali.rice.kew.doctype.bo.DocumentType;
035import org.kuali.rice.kew.engine.BlanketApproveEngine;
036import org.kuali.rice.kew.engine.CompatUtils;
037import org.kuali.rice.kew.engine.OrchestrationConfig;
038import org.kuali.rice.kew.engine.OrchestrationConfig.EngineCapability;
039import org.kuali.rice.kew.engine.RouteContext;
040import org.kuali.rice.kew.engine.node.RouteNode;
041import org.kuali.rice.kew.engine.node.service.RouteNodeService;
042import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
043import org.kuali.rice.kew.service.KEWServiceLocator;
044import org.kuali.rice.kew.api.KewApiConstants;
045import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
046
047
048/**
049 * Does the sync work for blanket approves requested by client apps.
050 *
051 * @author Kuali Rice Team (rice.collab@kuali.org)
052 */
053public class BlanketApproveAction extends ActionTakenEvent {
054
055    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BlanketApproveAction.class);
056    private Set<String> nodeNames;
057
058    public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal) {
059        this(rh, principal, DEFAULT_ANNOTATION, (Set<String>) null);
060    }
061
062    public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation, Integer routeLevel) {
063        this(rh, principal, annotation, convertRouteLevel(rh.getDocumentType(), routeLevel));
064    }
065
066    public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation, String nodeName) {
067        this(rh, principal, annotation, Collections.singleton(nodeName));
068    }
069
070    public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation, Set<String> nodeNames) {
071        super(KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD, rh, principal, annotation, DEFAULT_RUN_POSTPROCESSOR_LOGIC, false);
072        this.nodeNames = (nodeNames == null ? new HashSet<String>() : nodeNames);
073    }
074
075    private static Set<String> convertRouteLevel(DocumentType documentType, Integer routeLevel) {
076        Set<String> nodeNames = new HashSet<String>();
077        if (routeLevel == null) {
078            return nodeNames;
079        }
080        RouteNode node = CompatUtils.getNodeForLevel(documentType, routeLevel);
081        if (node == null) {
082            throw new WorkflowRuntimeException("Could not locate a valid node for the given route level: " + routeLevel);
083        }
084        nodeNames.add(node.getRouteNodeName());
085        return nodeNames;
086    }
087
088    /* (non-Javadoc)
089     * @see org.kuali.rice.kew.actions.ActionTakenEvent#validateActionRules()
090     */
091    @Override
092    public String validateActionRules() {
093        return validateActionRules(getActionRequestService().findAllPendingRequests(routeHeader.getDocumentId()));
094    }
095
096    public String validateActionRules(List<ActionRequestValue> actionRequests) {
097        if ( (nodeNames != null) && (!nodeNames.isEmpty()) ) {
098            String nodeName = isGivenNodeListValid();
099            if (!org.apache.commons.lang.StringUtils.isEmpty(nodeName)) {
100                return "Document already at or beyond route node " + nodeName;
101            }
102        }
103        if (!getRouteHeader().isValidActionToTake(getActionPerformedCode())) {
104            return "Document is not in a state to be approved";
105        }
106        List<ActionRequestValue> filteredActionRequests = filterActionRequestsByCode(actionRequests, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ);
107        if (!isActionCompatibleRequest(filteredActionRequests)) {
108            return "No request for the user is compatible with the BlanketApprove Action";
109        }
110        // check state before checking kim
111        if (! KEWServiceLocator.getDocumentTypePermissionService().canBlanketApprove(getPrincipal().getPrincipalId(), getRouteHeader())) {
112            return "User is not authorized to BlanketApprove document";
113        }
114        return "";
115    }
116
117    private String isGivenNodeListValid() {
118        for (Iterator<String> iterator = nodeNames.iterator(); iterator.hasNext();) {
119            String nodeName = (String) iterator.next();
120            if (nodeName == null) {
121                iterator.remove();
122                continue;
123            }
124            if (!getRouteNodeService().isNodeInPath(getRouteHeader(), nodeName)) {
125                return nodeName;
126            }
127        }
128        return "";
129    }
130
131    public void recordAction() throws InvalidActionTakenException {
132        MDC.put("docId", getRouteHeader().getDocumentId());
133        updateSearchableAttributesIfPossible();
134
135        List<ActionRequestValue> actionRequests = getActionRequestService().findAllValidRequests(getPrincipal().getPrincipalId(), getDocumentId(), KewApiConstants.ACTION_REQUEST_COMPLETE_REQ);
136        String errorMessage = validateActionRules(actionRequests);
137        if (!org.apache.commons.lang.StringUtils.isEmpty(errorMessage)) {
138            throw new InvalidActionTakenException(errorMessage);
139        }
140
141        LOG.debug("Checking to see if the action is legal");
142
143            LOG.debug("Blanket approving document : " + annotation);
144
145            if (getRouteHeader().isStateInitiated() || getRouteHeader().isStateSaved()) {
146                markDocumentEnroute(getRouteHeader());
147                getRouteHeader().setRoutedByUserWorkflowId(getPrincipal().getPrincipalId());
148            }
149
150            LOG.debug("Record the blanket approval action");
151            Recipient delegator = findDelegatorForActionRequests(actionRequests);
152            ActionTakenValue actionTaken = saveActionTaken(delegator);
153
154            LOG.debug("Deactivate pending action requests for user");
155            getActionRequestService().deactivateRequests(actionTaken, actionRequests);
156            notifyActionTaken(actionTaken);
157
158            KEWServiceLocator.getRouteHeaderService().saveRouteHeader(getRouteHeader());
159
160//        } else {
161//            LOG.warn("Document not in state to be approved.");
162//            throw new InvalidActionTakenException("Document is not in a state to be approved");
163//        }
164            
165          queueDeferredWork(actionTaken);
166    }
167
168    protected void queueDeferredWork(ActionTakenValue actionTaken) {
169        try {
170                final boolean shouldIndex = getRouteHeader().getDocumentType().hasSearchableAttributes() && RouteContext.getCurrentRouteContext().isSearchIndexingRequestedForContext();
171
172            String applicationId = routeHeader.getDocumentType().getApplicationId();
173            DocumentOrchestrationQueue blanketApprove = KewApiServiceLocator.getDocumentOrchestrationQueue(
174                    routeHeader.getDocumentId(), applicationId);
175            org.kuali.rice.kew.api.document.OrchestrationConfig orchestrationConfig =
176                    org.kuali.rice.kew.api.document.OrchestrationConfig.create(actionTaken.getActionTakenId(), nodeNames);
177            DocumentProcessingOptions options = DocumentProcessingOptions.create(true, shouldIndex);
178            blanketApprove.orchestrateDocument(routeHeader.getDocumentId(), getPrincipal().getPrincipalId(),
179                    orchestrationConfig, options);
180        } catch (Exception e) {
181            LOG.error(e);
182            throw new WorkflowRuntimeException(e);
183        }
184    }
185    
186    public void performDeferredBlanketApproveWork(ActionTakenValue actionTaken, DocumentProcessingOptions processingOptions) throws Exception {
187
188        if (getRouteHeader().isInException()) {
189            LOG.debug("Moving document back to Enroute from Exception");
190
191            markDocumentEnroute(getRouteHeader());
192
193        }
194        OrchestrationConfig config = new OrchestrationConfig(EngineCapability.BLANKET_APPROVAL, nodeNames, actionTaken, processingOptions.isSendNotifications(), processingOptions.isRunPostProcessor());
195        BlanketApproveEngine blanketApproveEngine = KEWServiceLocator.getWorkflowEngineFactory().newEngine(config);
196        blanketApproveEngine.process(getRouteHeader().getDocumentId(), null);
197   
198        queueDocumentProcessing();
199   }
200
201    protected void markDocumentEnroute(DocumentRouteHeaderValue routeHeader) throws InvalidActionTakenException {
202        String oldStatus = routeHeader.getDocRouteStatus();
203        routeHeader.markDocumentEnroute();
204
205        String newStatus = routeHeader.getDocRouteStatus();
206        notifyStatusChange(newStatus, oldStatus);
207        KEWServiceLocator.getRouteHeaderService().saveRouteHeader(routeHeader);
208    }
209
210    private RouteNodeService getRouteNodeService() {
211        return KEWServiceLocator.getRouteNodeService();
212    }
213}