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}