001/** 002 * Copyright 2005-2017 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 DocumentRouteHeaderValue routeHeaderValue = KEWServiceLocator.getRouteHeaderService(). 159 saveRouteHeader(getRouteHeader()); 160 setRouteHeader(routeHeaderValue); 161 162// } else { 163// LOG.warn("Document not in state to be approved."); 164// throw new InvalidActionTakenException("Document is not in a state to be approved"); 165// } 166 167 queueDeferredWork(actionTaken); 168 } 169 170 protected void queueDeferredWork(ActionTakenValue actionTaken) { 171 try { 172 final boolean shouldIndex = getRouteHeader().getDocumentType().hasSearchableAttributes() && RouteContext.getCurrentRouteContext().isSearchIndexingRequestedForContext(); 173 174 String applicationId = routeHeader.getDocumentType().getApplicationId(); 175 DocumentOrchestrationQueue blanketApprove = KewApiServiceLocator.getDocumentOrchestrationQueue( 176 routeHeader.getDocumentId(), applicationId); 177 org.kuali.rice.kew.api.document.OrchestrationConfig orchestrationConfig = 178 org.kuali.rice.kew.api.document.OrchestrationConfig.create(actionTaken.getActionTakenId(), nodeNames); 179 DocumentProcessingOptions options = DocumentProcessingOptions.create(true, shouldIndex); 180 blanketApprove.orchestrateDocument(routeHeader.getDocumentId(), getPrincipal().getPrincipalId(), 181 orchestrationConfig, options); 182 } catch (Exception e) { 183 LOG.error(e); 184 throw new WorkflowRuntimeException(e); 185 } 186 } 187 188 public void performDeferredBlanketApproveWork(ActionTakenValue actionTaken, DocumentProcessingOptions processingOptions) throws Exception { 189 190 if (getRouteHeader().isInException()) { 191 LOG.debug("Moving document back to Enroute from Exception"); 192 193 markDocumentEnroute(getRouteHeader()); 194 195 } 196 //KULRICE-12283 Modified this code to pass along parameters which configures if acks and FYIs are deactivated during the blanket approval 197 OrchestrationConfig config = new OrchestrationConfig(EngineCapability.BLANKET_APPROVAL, nodeNames, actionTaken, 198 processingOptions.isSendNotifications(), processingOptions.isRunPostProcessor(), 199 processingOptions.isDeactivateAcknowledgements(), processingOptions.isDeactivateFYIs(), true); 200 BlanketApproveEngine blanketApproveEngine = KEWServiceLocator.getWorkflowEngineFactory().newEngine(config); 201 blanketApproveEngine.process(getRouteHeader().getDocumentId(), null); 202 203 queueDocumentProcessing(); 204 } 205 206 protected void markDocumentEnroute(DocumentRouteHeaderValue routeHeader) throws InvalidActionTakenException { 207 String oldStatus = routeHeader.getDocRouteStatus(); 208 routeHeader.markDocumentEnroute(); 209 210 String newStatus = routeHeader.getDocRouteStatus(); 211 notifyStatusChange(newStatus, oldStatus); 212 DocumentRouteHeaderValue routeHeaderValue = KEWServiceLocator.getRouteHeaderService(). 213 saveRouteHeader(routeHeader); 214 setRouteHeader(routeHeaderValue); 215 } 216 217 private RouteNodeService getRouteNodeService() { 218 return KEWServiceLocator.getRouteNodeService(); 219 } 220}