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 this(rh, principal, DEFAULT_ANNOTATION, (Set<String>) null); 059 } 060 061 public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation, Integer routeLevel) { 062 this(rh, principal, annotation, convertRouteLevel(rh.getDocumentType(), routeLevel)); 063 } 064 065 public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation, String nodeName) { 066 this(rh, principal, annotation, Collections.singleton(nodeName)); 067 } 068 069 public BlanketApproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation, Set<String> nodeNames) { 070 super(KewApiConstants.ACTION_TAKEN_BLANKET_APPROVE_CD, rh, principal, annotation, DEFAULT_RUN_POSTPROCESSOR_LOGIC, false); 071 this.nodeNames = (nodeNames == null ? new HashSet<String>() : nodeNames); 072 } 073 074 private static Set<String> convertRouteLevel(DocumentType documentType, Integer routeLevel) { 075 Set<String> nodeNames = new HashSet<String>(); 076 if (routeLevel == null) { 077 return nodeNames; 078 } 079 RouteNode node = CompatUtils.getNodeForLevel(documentType, routeLevel); 080 if (node == null) { 081 throw new WorkflowRuntimeException("Could not locate a valid node for the given route level: " + routeLevel); 082 } 083 nodeNames.add(node.getRouteNodeName()); 084 return nodeNames; 085 } 086 087 /* (non-Javadoc) 088 * @see org.kuali.rice.kew.actions.ActionTakenEvent#validateActionRules() 089 */ 090 @Override 091 public String validateActionRules() { 092 return validateActionRules(getActionRequestService().findAllPendingRequests(routeHeader.getDocumentId())); 093 } 094 095 public String validateActionRules(List<ActionRequestValue> actionRequests) { 096 if ( (nodeNames != null) && (!nodeNames.isEmpty()) ) { 097 String nodeName = isGivenNodeListValid(); 098 if (!org.apache.commons.lang.StringUtils.isEmpty(nodeName)) { 099 return "Document already at or beyond route node " + nodeName; 100 } 101 } 102 if (!getRouteHeader().isValidActionToTake(getActionPerformedCode())) { 103 return "Document is not in a state to be approved"; 104 } 105 List<ActionRequestValue> filteredActionRequests = filterActionRequestsByCode(actionRequests, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ); 106 if (!isActionCompatibleRequest(filteredActionRequests)) { 107 return "No request for the user is compatible with the BlanketApprove Action"; 108 } 109 // check state before checking kim 110 if (! KEWServiceLocator.getDocumentTypePermissionService().canBlanketApprove(getPrincipal().getPrincipalId(), getRouteHeader().getDocumentType(), getRouteHeader().getDocRouteStatus(), getRouteHeader().getInitiatorWorkflowId())) { 111 return "User is not authorized to BlanketApprove document"; 112 } 113 return ""; 114 } 115 116 private String isGivenNodeListValid() { 117 for (Iterator<String> iterator = nodeNames.iterator(); iterator.hasNext();) { 118 String nodeName = (String) iterator.next(); 119 if (nodeName == null) { 120 iterator.remove(); 121 continue; 122 } 123 if (!getRouteNodeService().isNodeInPath(getRouteHeader(), nodeName)) { 124 return nodeName; 125 } 126 } 127 return ""; 128 } 129 130 public void recordAction() throws InvalidActionTakenException { 131 MDC.put("docId", getRouteHeader().getDocumentId()); 132 updateSearchableAttributesIfPossible(); 133 134 List<ActionRequestValue> actionRequests = getActionRequestService().findAllValidRequests(getPrincipal().getPrincipalId(), getDocumentId(), KewApiConstants.ACTION_REQUEST_COMPLETE_REQ); 135 String errorMessage = validateActionRules(actionRequests); 136 if (!org.apache.commons.lang.StringUtils.isEmpty(errorMessage)) { 137 throw new InvalidActionTakenException(errorMessage); 138 } 139 140 LOG.debug("Checking to see if the action is legal"); 141 142 LOG.debug("Blanket approving document : " + annotation); 143 144 if (getRouteHeader().isStateInitiated() || getRouteHeader().isStateSaved()) { 145 markDocumentEnroute(getRouteHeader()); 146 getRouteHeader().setRoutedByUserWorkflowId(getPrincipal().getPrincipalId()); 147 } 148 149 LOG.debug("Record the blanket approval action"); 150 Recipient delegator = findDelegatorForActionRequests(actionRequests); 151 ActionTakenValue actionTaken = saveActionTaken(delegator); 152 153 LOG.debug("Deactivate pending action requests for user"); 154 getActionRequestService().deactivateRequests(actionTaken, actionRequests); 155 notifyActionTaken(actionTaken); 156 157 KEWServiceLocator.getRouteHeaderService().saveRouteHeader(getRouteHeader()); 158 159 // } else { 160 // LOG.warn("Document not in state to be approved."); 161 // throw new InvalidActionTakenException("Document is not in a state to be approved"); 162 // } 163 164 queueDeferredWork(actionTaken); 165 } 166 167 protected void queueDeferredWork(ActionTakenValue actionTaken) { 168 try { 169 final boolean shouldIndex = getRouteHeader().getDocumentType().hasSearchableAttributes() && RouteContext.getCurrentRouteContext().isSearchIndexingRequestedForContext(); 170 171 DocumentOrchestrationQueue blanketApprove = MessageServiceNames.getDocumentOrchestrationQueue(routeHeader); 172 org.kuali.rice.kew.api.document.OrchestrationConfig orchestrationConfig = 173 org.kuali.rice.kew.api.document.OrchestrationConfig.create(actionTaken.getActionTakenId(), nodeNames); 174 DocumentProcessingOptions options = DocumentProcessingOptions.create(true, shouldIndex); 175 blanketApprove.orchestrateDocument(routeHeader.getDocumentId(), getPrincipal().getPrincipalId(), 176 orchestrationConfig, options); 177 } catch (Exception e) { 178 LOG.error(e); 179 throw new WorkflowRuntimeException(e); 180 } 181 } 182 183 public void performDeferredBlanketApproveWork(ActionTakenValue actionTaken, DocumentProcessingOptions processingOptions) throws Exception { 184 185 if (getRouteHeader().isInException()) { 186 LOG.debug("Moving document back to Enroute from Exception"); 187 188 String oldStatus = getRouteHeader().getDocRouteStatus(); 189 getRouteHeader().markDocumentEnroute(); 190 191 String newStatus = getRouteHeader().getDocRouteStatus(); 192 notifyStatusChange(newStatus, oldStatus); 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 = getRouteHeader().getDocRouteStatus(); 203 getRouteHeader().markDocumentEnroute(); 204 205 String newStatus = getRouteHeader().getDocRouteStatus(); 206 notifyStatusChange(newStatus, oldStatus); 207 KEWServiceLocator.getRouteHeaderService().saveRouteHeader(getRouteHeader()); 208 } 209 210 private RouteNodeService getRouteNodeService() { 211 return KEWServiceLocator.getRouteNodeService(); 212 } 213 }