1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.engine;
17
18 import org.apache.log4j.MDC;
19 import org.kuali.rice.coreservice.framework.parameter.ParameterService;
20 import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
21 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
22 import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
23 import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
24 import org.kuali.rice.kew.actions.NotificationContext;
25 import org.kuali.rice.kew.actiontaken.ActionTakenValue;
26 import org.kuali.rice.kew.api.WorkflowRuntimeException;
27 import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
28 import org.kuali.rice.kew.api.exception.WorkflowException;
29 import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
30 import org.kuali.rice.kew.engine.node.RouteNode;
31 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
32 import org.kuali.rice.kew.engine.node.service.RouteNodeService;
33 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
34 import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
35 import org.kuali.rice.kew.service.KEWServiceLocator;
36 import org.kuali.rice.kew.api.KewApiConstants;
37
38 import java.util.ArrayList;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.LinkedList;
42 import java.util.List;
43 import java.util.Set;
44
45
46
47
48
49
50
51 public class BlanketApproveEngine extends StandardWorkflowEngine {
52
53 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BlanketApproveEngine.class);
54
55
56 BlanketApproveEngine(RouteNodeService routeNodeService, RouteHeaderService routeHeaderService,
57 ParameterService parameterService, OrchestrationConfig config) {
58 super(routeNodeService, routeHeaderService, parameterService, config);
59 }
60
61
62
63
64 public void process(String documentId, String nodeInstanceId) throws Exception {
65 if (documentId == null) {
66 throw new IllegalArgumentException("Cannot process a null document id.");
67 }
68 MDC.put("docId", documentId);
69
70 try {
71 RouteContext context = RouteContext.getCurrentRouteContext();
72 KEWServiceLocator.getRouteHeaderService().lockRouteHeader(documentId);
73 if ( LOG.isInfoEnabled() ) {
74 LOG.info("Processing document for Blanket Approval: " + documentId + " : " + nodeInstanceId);
75 }
76 DocumentRouteHeaderValue document = getRouteHeaderService().getRouteHeader(documentId, true);
77 if (!document.isRoutable()) {
78
79 LOG.warn("Document not routable so returning with doing no action");
80 return;
81 }
82 List<RouteNodeInstance> activeNodeInstances = new ArrayList<RouteNodeInstance>();
83 if (nodeInstanceId == null) {
84 activeNodeInstances.addAll(getRouteNodeService().getActiveNodeInstances(documentId));
85 } else {
86 RouteNodeInstance instanceNode = getRouteNodeService().findRouteNodeInstanceById(nodeInstanceId);
87 if (instanceNode == null) {
88 throw new IllegalArgumentException("Invalid node instance id: " + nodeInstanceId);
89 }
90 activeNodeInstances.add(instanceNode);
91 }
92 List<RouteNodeInstance> nodeInstancesToProcess = determineNodeInstancesToProcess(activeNodeInstances, config.getDestinationNodeNames());
93
94
95 context.setDoNotSendApproveNotificationEmails(true);
96 context.setDocument(document);
97 context.setEngineState(new EngineState());
98 NotificationContext notifyContext = null;
99 if (config.isSendNotifications()) {
100 notifyContext = new NotificationContext(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, config.getCause().getPrincipal(), config.getCause().getActionTaken());
101 }
102 lockAdditionalDocuments(document);
103 try {
104 List<ProcessEntry> processingQueue = new LinkedList<ProcessEntry>();
105 for (RouteNodeInstance nodeInstancesToProcesses : nodeInstancesToProcess)
106 {
107 processingQueue.add(new ProcessEntry((RouteNodeInstance) nodeInstancesToProcesses));
108 }
109 Set<String> nodesCompleted = new HashSet<String>();
110
111
112 while (!processingQueue.isEmpty() && !isReachedDestinationNodes(config.getDestinationNodeNames(), nodesCompleted)) {
113 ProcessEntry entry = processingQueue.remove(0);
114
115
116
117 if (entry.getTimesProcessed() > 20) {
118 throw new WorkflowException("Could not process document through to blanket approval." + " Document failed to progress past node " + entry.getNodeInstance().getRouteNode().getRouteNodeName());
119 }
120 RouteNodeInstance nodeInstance = entry.getNodeInstance();
121 context.setNodeInstance(nodeInstance);
122 if (config.getDestinationNodeNames().contains(nodeInstance.getName())) {
123 nodesCompleted.add(nodeInstance.getName());
124 continue;
125 }
126 ProcessContext resultProcessContext = processNodeInstance(context, helper);
127 invokeBlanketApproval(config.getCause(), nodeInstance, notifyContext);
128 if (!resultProcessContext.getNextNodeInstances().isEmpty() || resultProcessContext.isComplete()) {
129 for (Iterator nodeIt = resultProcessContext.getNextNodeInstances().iterator(); nodeIt.hasNext();) {
130 addToProcessingQueue(processingQueue, (RouteNodeInstance) nodeIt.next());
131 }
132 } else {
133 entry.increment();
134 processingQueue.add(processingQueue.size(), entry);
135 }
136 }
137
138 RouteContext.clearCurrentRouteContext();
139
140
141 super.process(documentId, null);
142 } catch (Exception e) {
143 if (e instanceof RuntimeException) {
144 throw (RuntimeException)e;
145 } else {
146 throw new WorkflowRuntimeException(e.toString(), e);
147 }
148 }
149 } finally {
150 RouteContext.clearCurrentRouteContext();
151 MDC.remove("docId");
152 }
153 }
154
155
156
157
158 private boolean isReachedDestinationNodes(Set destinationNodesNames, Set<String> nodeNamesCompleted) {
159 return !destinationNodesNames.isEmpty() && nodeNamesCompleted.equals(destinationNodesNames);
160 }
161
162 private void addToProcessingQueue(List<ProcessEntry> processingQueue, RouteNodeInstance nodeInstance) {
163
164 for (ProcessEntry entry : processingQueue)
165 {
166 if (entry.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId()))
167 {
168 entry.setNodeInstance(nodeInstance);
169 return;
170 }
171 }
172 processingQueue.add(processingQueue.size(), new ProcessEntry(nodeInstance));
173 }
174
175
176
177
178 private List<RouteNodeInstance> determineNodeInstancesToProcess(List<RouteNodeInstance> activeNodeInstances, Set nodeNames) throws Exception {
179 if (nodeNames.isEmpty()) {
180 return activeNodeInstances;
181 }
182 List<RouteNodeInstance> nodeInstancesToProcess = new ArrayList<RouteNodeInstance>();
183 for (Iterator<RouteNodeInstance> iterator = activeNodeInstances.iterator(); iterator.hasNext();) {
184 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
185 if (isNodeNameInPath(nodeNames, nodeInstance)) {
186 nodeInstancesToProcess.add(nodeInstance);
187 }
188 }
189 if (nodeInstancesToProcess.size() == 0) {
190 throw new InvalidActionTakenException("Could not locate nodes with the given names in the blanket approval path '" + printNodeNames(nodeNames) + "'. " + "The document is probably already passed the specified nodes or does not contain the nodes.");
191 }
192 return nodeInstancesToProcess;
193 }
194
195 private boolean isNodeNameInPath(Set nodeNames, RouteNodeInstance nodeInstance) throws Exception {
196 boolean isInPath = false;
197 for (Object nodeName1 : nodeNames)
198 {
199 String nodeName = (String) nodeName1;
200 for (RouteNode nextNode : nodeInstance.getRouteNode().getNextNodes())
201 {
202 isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, new HashSet<String>());
203 }
204 }
205 return isInPath;
206 }
207
208 private boolean isNodeNameInPath(String nodeName, RouteNode node, Set<String> inspected) throws Exception {
209 boolean isInPath = !inspected.contains(node.getRouteNodeId()) && node.getRouteNodeName().equals(nodeName);
210 inspected.add(node.getRouteNodeId());
211 if (helper.isSubProcessNode(node)) {
212 ProcessDefinitionBo subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName());
213 RouteNode subNode = subProcess.getInitialRouteNode();
214 if (subNode != null) {
215 isInPath = isInPath || isNodeNameInPath(nodeName, subNode, inspected);
216 }
217 }
218 for (RouteNode nextNode : node.getNextNodes())
219 {
220 isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, inspected);
221 }
222 return isInPath;
223 }
224
225 private String printNodeNames(Set nodesNames) {
226 StringBuffer buffer = new StringBuffer();
227 for (Iterator iterator = nodesNames.iterator(); iterator.hasNext();) {
228 String nodeName = (String) iterator.next();
229 buffer.append(nodeName);
230 buffer.append((iterator.hasNext() ? ", " : ""));
231 }
232 return buffer.toString();
233 }
234
235
236
237
238 private void invokeBlanketApproval(ActionTakenValue actionTaken, RouteNodeInstance nodeInstance, NotificationContext notifyContext) {
239 List actionRequests = getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(nodeInstance.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
240 actionRequests = getActionRequestService().getRootRequests(actionRequests);
241 List<ActionRequestValue> requestsToNotify = new ArrayList<ActionRequestValue>();
242 for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
243 ActionRequestValue request = (ActionRequestValue) iterator.next();
244 if (request.isApproveOrCompleteRequest()) {
245 requestsToNotify.add(getActionRequestService().deactivateRequest(actionTaken, request));
246 }
247
248 if(request.isAcknowledgeRequest() && config.isDeactivateAcknowledgements()) {
249 getActionRequestService().deactivateRequest(actionTaken, request);
250 }
251 if(request.isFYIRequest() && config.isDeactivateFYIs()) {
252 getActionRequestService().deactivateRequest(actionTaken, request);
253 }
254 }
255 if (notifyContext != null && !requestsToNotify.isEmpty()) {
256 ActionRequestFactory arFactory = new ActionRequestFactory(RouteContext.getCurrentRouteContext().getDocument(), nodeInstance);
257 KimPrincipalRecipient delegatorRecipient = null;
258 if (actionTaken.getDelegatorPrincipal() != null) {
259 delegatorRecipient = new KimPrincipalRecipient(actionTaken.getDelegatorPrincipal());
260 }
261 List<ActionRequestValue> notificationRequests = arFactory.generateNotifications(requestsToNotify, notifyContext.getPrincipalTakingAction(), delegatorRecipient, notifyContext.getNotificationRequestCode(), notifyContext.getActionTakenCode());
262 getActionRequestService().activateRequests(notificationRequests);
263 }
264 }
265
266 private ActionRequestService getActionRequestService() {
267 return KEWServiceLocator.getActionRequestService();
268 }
269
270 private class ProcessEntry {
271
272 private RouteNodeInstance nodeInstance;
273 private int timesProcessed = 0;
274
275 public ProcessEntry(RouteNodeInstance nodeInstance) {
276 this.nodeInstance = nodeInstance;
277 }
278
279 public RouteNodeInstance getNodeInstance() {
280 return nodeInstance;
281 }
282
283 public void setNodeInstance(RouteNodeInstance nodeInstance) {
284 this.nodeInstance = nodeInstance;
285 }
286
287 public void increment() {
288 timesProcessed++;
289 }
290
291 public int getTimesProcessed() {
292 return timesProcessed;
293 }
294
295 }
296
297 }