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