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 RouteContext context = RouteContext.getCurrentRouteContext();
70 try {
71 KEWServiceLocator.getRouteHeaderService().lockRouteHeader(documentId, true);
72 if ( LOG.isInfoEnabled() ) {
73 LOG.info("Processing document for Blanket Approval: " + documentId + " : " + nodeInstanceId);
74 }
75 DocumentRouteHeaderValue document = getRouteHeaderService().getRouteHeader(documentId, true);
76 if (!document.isRoutable()) {
77 LOG.debug("Document not routable so returning with doing no action");
78 return;
79 }
80 List<RouteNodeInstance> activeNodeInstances = new ArrayList<RouteNodeInstance>();
81 if (nodeInstanceId == null) {
82 activeNodeInstances.addAll(getRouteNodeService().getActiveNodeInstances(documentId));
83 } else {
84 RouteNodeInstance instanceNode = getRouteNodeService().findRouteNodeInstanceById(nodeInstanceId);
85 if (instanceNode == null) {
86 throw new IllegalArgumentException("Invalid node instance id: " + nodeInstanceId);
87 }
88 activeNodeInstances.add(instanceNode);
89 }
90 List<RouteNodeInstance> nodeInstancesToProcess = determineNodeInstancesToProcess(activeNodeInstances, config.getDestinationNodeNames());
91
92
93 context.setDoNotSendApproveNotificationEmails(true);
94 context.setDocument(document);
95 context.setEngineState(new EngineState());
96 NotificationContext notifyContext = null;
97 if (config.isSendNotifications()) {
98 notifyContext = new NotificationContext(KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, config.getCause().getPrincipal(), config.getCause().getActionTaken());
99 }
100 lockAdditionalDocuments(document);
101 try {
102 List<ProcessEntry> processingQueue = new LinkedList<ProcessEntry>();
103 for (RouteNodeInstance nodeInstancesToProcesses : nodeInstancesToProcess)
104 {
105 processingQueue.add(new ProcessEntry((RouteNodeInstance) nodeInstancesToProcesses));
106 }
107 Set<String> nodesCompleted = new HashSet<String>();
108
109
110 while (!processingQueue.isEmpty() && !isReachedDestinationNodes(config.getDestinationNodeNames(), nodesCompleted)) {
111 ProcessEntry entry = processingQueue.remove(0);
112
113
114
115 if (entry.getTimesProcessed() > 20) {
116 throw new WorkflowException("Could not process document through to blanket approval." + " Document failed to progress past node " + entry.getNodeInstance().getRouteNode().getRouteNodeName());
117 }
118 RouteNodeInstance nodeInstance = entry.getNodeInstance();
119 context.setNodeInstance(nodeInstance);
120 if (config.getDestinationNodeNames().contains(nodeInstance.getName())) {
121 nodesCompleted.add(nodeInstance.getName());
122 continue;
123 }
124 ProcessContext resultProcessContext = processNodeInstance(context, helper);
125 invokeBlanketApproval(config.getCause(), nodeInstance, notifyContext);
126 if (!resultProcessContext.getNextNodeInstances().isEmpty() || resultProcessContext.isComplete()) {
127 for (Iterator nodeIt = resultProcessContext.getNextNodeInstances().iterator(); nodeIt.hasNext();) {
128 addToProcessingQueue(processingQueue, (RouteNodeInstance) nodeIt.next());
129 }
130 } else {
131 entry.increment();
132 processingQueue.add(processingQueue.size(), entry);
133 }
134 }
135
136 RouteContext.clearCurrentRouteContext();
137
138
139 super.process(documentId, null);
140 } catch (Exception e) {
141 if (e instanceof RuntimeException) {
142 throw (RuntimeException)e;
143 } else {
144 throw new WorkflowRuntimeException(e.toString(), e);
145 }
146 }
147 } finally {
148 RouteContext.clearCurrentRouteContext();
149 MDC.remove("docId");
150 }
151 }
152
153
154
155
156 private boolean isReachedDestinationNodes(Set destinationNodesNames, Set<String> nodeNamesCompleted) {
157 return !destinationNodesNames.isEmpty() && nodeNamesCompleted.equals(destinationNodesNames);
158 }
159
160 private void addToProcessingQueue(List<ProcessEntry> processingQueue, RouteNodeInstance nodeInstance) {
161
162 for (ProcessEntry entry : processingQueue)
163 {
164 if (entry.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId()))
165 {
166 entry.setNodeInstance(nodeInstance);
167 return;
168 }
169 }
170 processingQueue.add(processingQueue.size(), new ProcessEntry(nodeInstance));
171 }
172
173
174
175
176 private List<RouteNodeInstance> determineNodeInstancesToProcess(List<RouteNodeInstance> activeNodeInstances, Set nodeNames) throws Exception {
177 if (nodeNames.isEmpty()) {
178 return activeNodeInstances;
179 }
180 List<RouteNodeInstance> nodeInstancesToProcess = new ArrayList<RouteNodeInstance>();
181 for (Iterator<RouteNodeInstance> iterator = activeNodeInstances.iterator(); iterator.hasNext();) {
182 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
183 if (isNodeNameInPath(nodeNames, nodeInstance)) {
184 nodeInstancesToProcess.add(nodeInstance);
185 }
186 }
187 if (nodeInstancesToProcess.size() == 0) {
188 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.");
189 }
190 return nodeInstancesToProcess;
191 }
192
193 private boolean isNodeNameInPath(Set nodeNames, RouteNodeInstance nodeInstance) throws Exception {
194 boolean isInPath = false;
195 for (Object nodeName1 : nodeNames)
196 {
197 String nodeName = (String) nodeName1;
198 for (RouteNode nextNode : nodeInstance.getRouteNode().getNextNodes())
199 {
200 isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, new HashSet<String>());
201 }
202 }
203 return isInPath;
204 }
205
206 private boolean isNodeNameInPath(String nodeName, RouteNode node, Set<String> inspected) throws Exception {
207 boolean isInPath = !inspected.contains(node.getRouteNodeId()) && node.getRouteNodeName().equals(nodeName);
208 inspected.add(node.getRouteNodeId());
209 if (helper.isSubProcessNode(node)) {
210 ProcessDefinitionBo subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName());
211 RouteNode subNode = subProcess.getInitialRouteNode();
212 if (subNode != null) {
213 isInPath = isInPath || isNodeNameInPath(nodeName, subNode, inspected);
214 }
215 }
216 for (RouteNode nextNode : node.getNextNodes())
217 {
218 isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, inspected);
219 }
220 return isInPath;
221 }
222
223 private String printNodeNames(Set nodesNames) {
224 StringBuffer buffer = new StringBuffer();
225 for (Iterator iterator = nodesNames.iterator(); iterator.hasNext();) {
226 String nodeName = (String) iterator.next();
227 buffer.append(nodeName);
228 buffer.append((iterator.hasNext() ? ", " : ""));
229 }
230 return buffer.toString();
231 }
232
233
234
235
236 private void invokeBlanketApproval(ActionTakenValue actionTaken, RouteNodeInstance nodeInstance, NotificationContext notifyContext) {
237 List actionRequests = getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(nodeInstance.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
238 actionRequests = getActionRequestService().getRootRequests(actionRequests);
239 List<ActionRequestValue> requestsToNotify = new ArrayList<ActionRequestValue>();
240 for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
241 ActionRequestValue request = (ActionRequestValue) iterator.next();
242 if (request.isApproveOrCompleteRequest()) {
243 getActionRequestService().deactivateRequest(actionTaken, request);
244 requestsToNotify.add(request);
245 }
246 }
247 if (notifyContext != null) {
248 ActionRequestFactory arFactory = new ActionRequestFactory(RouteContext.getCurrentRouteContext().getDocument(), nodeInstance);
249 KimPrincipalRecipient delegatorRecipient = null;
250 if (actionTaken.getDelegatorPrincipal() != null) {
251 delegatorRecipient = new KimPrincipalRecipient(actionTaken.getDelegatorPrincipal());
252 }
253 List<ActionRequestValue> notificationRequests = arFactory.generateNotifications(requestsToNotify, notifyContext.getPrincipalTakingAction(), delegatorRecipient, notifyContext.getNotificationRequestCode(), notifyContext.getActionTakenCode());
254 getActionRequestService().activateRequests(notificationRequests);
255 }
256 }
257
258 private ActionRequestService getActionRequestService() {
259 return KEWServiceLocator.getActionRequestService();
260 }
261
262 private class ProcessEntry {
263
264 private RouteNodeInstance nodeInstance;
265 private int timesProcessed = 0;
266
267 public ProcessEntry(RouteNodeInstance nodeInstance) {
268 this.nodeInstance = nodeInstance;
269 }
270
271 public RouteNodeInstance getNodeInstance() {
272 return nodeInstance;
273 }
274
275 public void setNodeInstance(RouteNodeInstance nodeInstance) {
276 this.nodeInstance = nodeInstance;
277 }
278
279 public void increment() {
280 timesProcessed++;
281 }
282
283 public int getTimesProcessed() {
284 return timesProcessed;
285 }
286
287 }
288
289 }