001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.engine.node;
018:
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.Comparator;
023: import java.util.HashMap;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029:
030: import org.apache.commons.collections.ComparatorUtils;
031:
032: import edu.iu.uis.eden.KEWServiceLocator;
033: import edu.iu.uis.eden.doctype.DocumentType;
034: import edu.iu.uis.eden.engine.RouteHelper;
035: import edu.iu.uis.eden.engine.node.dao.RouteNodeDAO;
036: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
037: import edu.iu.uis.eden.util.Utilities;
038:
039: public class RouteNodeServiceImpl implements RouteNodeService {
040:
041: protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
042: .getLogger(getClass());
043:
044: public static final String REVOKED_NODE_INSTANCES_STATE_KEY = "NodeInstances.Revoked";
045:
046: private static final Comparator NODE_INSTANCE_FORWARD_SORT = new NodeInstanceIdSorter();
047: private static final Comparator NODE_INSTANCE_BACKWARD_SORT = ComparatorUtils
048: .reversedComparator(NODE_INSTANCE_FORWARD_SORT);
049: private RouteHelper helper = new RouteHelper();
050: private RouteNodeDAO routeNodeDAO;
051:
052: public void save(RouteNode node) {
053: routeNodeDAO.save(node);
054: }
055:
056: public void save(RouteNodeInstance nodeInstance) {
057: routeNodeDAO.save(nodeInstance);
058: }
059:
060: public void save(NodeState nodeState) {
061: routeNodeDAO.save(nodeState);
062: }
063:
064: public void save(Branch branch) {
065: routeNodeDAO.save(branch);
066: }
067:
068: public RouteNode findRouteNodeById(Long nodeId) {
069: return routeNodeDAO.findRouteNodeById(nodeId);
070: }
071:
072: public RouteNodeInstance findRouteNodeInstanceById(
073: Long nodeInstanceId) {
074: return routeNodeDAO.findRouteNodeInstanceById(nodeInstanceId);
075: }
076:
077: public List getCurrentNodeInstances(Long documentId) {
078: List currentNodeInstances = getActiveNodeInstances(documentId);
079: if (currentNodeInstances.isEmpty()) {
080: currentNodeInstances = getTerminalNodeInstances(documentId);
081: }
082: return currentNodeInstances;
083: }
084:
085: public List getActiveNodeInstances(Long documentId) {
086: return routeNodeDAO.getActiveNodeInstances(documentId);
087: }
088:
089: public List getActiveNodeInstances(DocumentRouteHeaderValue document) {
090: List flattenedNodeInstances = getFlattenedNodeInstances(
091: document, true);
092: List activeNodeInstances = new ArrayList();
093: for (Iterator iterator = flattenedNodeInstances.iterator(); iterator
094: .hasNext();) {
095: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
096: .next();
097: if (nodeInstance.isActive()) {
098: activeNodeInstances.add(nodeInstance);
099: }
100: }
101: return activeNodeInstances;
102: }
103:
104: public List getTerminalNodeInstances(Long documentId) {
105: return routeNodeDAO.getTerminalNodeInstances(documentId);
106: }
107:
108: public List getInitialNodeInstances(Long documentId) {
109: return routeNodeDAO.getInitialNodeInstances(documentId);
110: }
111:
112: public NodeState findNodeState(Long nodeInstanceId, String key) {
113: return routeNodeDAO.findNodeState(nodeInstanceId, key);
114: }
115:
116: public RouteNode findRouteNodeByName(Long documentTypeId,
117: String name) {
118: return routeNodeDAO.findRouteNodeByName(documentTypeId, name);
119: }
120:
121: public List findFinalApprovalRouteNodes(Long documentTypeId) {
122: DocumentType documentType = KEWServiceLocator
123: .getDocumentTypeService().findById(documentTypeId);
124: documentType = documentType.getRouteDefiningDocumentType();
125: return routeNodeDAO.findFinalApprovalRouteNodes(documentType
126: .getDocumentTypeId());
127: }
128:
129: public List findNextRouteNodesInPath(
130: RouteNodeInstance nodeInstance, String nodeName) {
131: List nodesInPath = new ArrayList();
132: for (Iterator iterator = nodeInstance.getRouteNode()
133: .getNextNodes().iterator(); iterator.hasNext();) {
134: RouteNode nextNode = (RouteNode) iterator.next();
135: nodesInPath.addAll(findNextRouteNodesInPath(nodeName,
136: nextNode, new HashSet()));
137: }
138: return nodesInPath;
139: }
140:
141: private List findNextRouteNodesInPath(String nodeName,
142: RouteNode node, Set inspected) {
143: List nextNodesInPath = new ArrayList();
144: if (inspected.contains(node.getRouteNodeId())) {
145: return nextNodesInPath;
146: }
147: inspected.add(node.getRouteNodeId());
148: if (node.getRouteNodeName().equals(nodeName)) {
149: nextNodesInPath.add(node);
150: } else {
151: if (helper.isSubProcessNode(node)) {
152: Process subProcess = node.getDocumentType()
153: .getNamedProcess(node.getRouteNodeName());
154: RouteNode subNode = subProcess.getInitialRouteNode();
155: nextNodesInPath.addAll(findNextRouteNodesInPath(
156: nodeName, subNode, inspected));
157: }
158: for (Iterator iterator = node.getNextNodes().iterator(); iterator
159: .hasNext();) {
160: RouteNode nextNode = (RouteNode) iterator.next();
161: nextNodesInPath.addAll(findNextRouteNodesInPath(
162: nodeName, nextNode, inspected));
163: }
164: }
165: return nextNodesInPath;
166: }
167:
168: public boolean isNodeInPath(DocumentRouteHeaderValue document,
169: String nodeName) {
170: boolean isInPath = false;
171: Collection activeNodes = getActiveNodeInstances(document
172: .getRouteHeaderId());
173: for (Iterator iterator = activeNodes.iterator(); iterator
174: .hasNext();) {
175: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
176: .next();
177: List nextNodesInPath = findNextRouteNodesInPath(
178: nodeInstance, nodeName);
179: isInPath = isInPath || !nextNodesInPath.isEmpty();
180: }
181: return isInPath;
182: }
183:
184: public List findRouteNodeInstances(Long documentId) {
185: return this .routeNodeDAO.findRouteNodeInstances(documentId);
186: }
187:
188: public void setRouteNodeDAO(RouteNodeDAO dao) {
189: this .routeNodeDAO = dao;
190: }
191:
192: public List findProcessNodeInstances(RouteNodeInstance process) {
193: return this .routeNodeDAO.findProcessNodeInstances(process);
194: }
195:
196: public Set findPreviousNodeNames(Long documentId) {
197: List currentNodeInstances = KEWServiceLocator
198: .getRouteNodeService().getCurrentNodeInstances(
199: documentId);
200: List nodeInstances = new ArrayList();
201: for (Iterator iterator = currentNodeInstances.iterator(); iterator
202: .hasNext();) {
203: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
204: .next();
205: nodeInstances.addAll(nodeInstance
206: .getPreviousNodeInstances());
207: }
208: Set nodeNames = new HashSet();
209: while (!nodeInstances.isEmpty()) {
210: RouteNodeInstance nodeInstance = (RouteNodeInstance) nodeInstances
211: .remove(0);
212: nodeNames.add(nodeInstance.getName());
213: nodeInstances.addAll(nodeInstance
214: .getPreviousNodeInstances());
215: }
216: return nodeNames;
217: }
218:
219: public Set findFutureNodeNames(Long documentId) {
220: List currentNodeInstances = KEWServiceLocator
221: .getRouteNodeService().getCurrentNodeInstances(
222: documentId);
223: List nodes = new ArrayList();
224: for (Iterator iterator = currentNodeInstances.iterator(); iterator
225: .hasNext();) {
226: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
227: .next();
228: nodes.addAll(nodeInstance.getRouteNode().getNextNodes());
229: }
230: Set nodeNames = new HashSet();
231: while (!nodes.isEmpty()) {
232: RouteNode node = (RouteNode) nodes.remove(0);
233: nodeNames.add(node.getRouteNodeName());
234: nodes.addAll(node.getNextNodes());
235: }
236: return nodeNames;
237: }
238:
239: public List getFlattenedNodes(DocumentType documentType,
240: boolean climbHierarchy) {
241: List nodes = new ArrayList();
242: if (!documentType.isRouteInherited() || climbHierarchy) {
243: for (Iterator iterator = documentType.getProcesses()
244: .iterator(); iterator.hasNext();) {
245: Process process = (Process) iterator.next();
246: nodes.addAll(getFlattenedNodes(process));
247: }
248: }
249: Collections.sort(nodes, new RouteNodeSorter());
250: return nodes;
251: }
252:
253: public List getFlattenedNodes(Process process) {
254: Map nodesMap = new HashMap();
255: flattenNodeGraph(nodesMap, process.getInitialRouteNode());
256: List nodes = new ArrayList(nodesMap.values());
257: Collections.sort(nodes, new RouteNodeSorter());
258: return nodes;
259: }
260:
261: /**
262: * Recursively walks the node graph and builds up the map. Uses a map because we will
263: * end up walking through duplicates, as is the case with Join nodes.
264: */
265: private void flattenNodeGraph(Map nodes, RouteNode node) {
266: if (nodes.containsKey(node.getRouteNodeName())) {
267: return;
268: }
269: nodes.put(node.getRouteNodeName(), node);
270: for (Iterator iterator = node.getNextNodes().iterator(); iterator
271: .hasNext();) {
272: RouteNode nextNode = (RouteNode) iterator.next();
273: flattenNodeGraph(nodes, nextNode);
274: }
275: }
276:
277: public List getFlattenedNodeInstances(
278: DocumentRouteHeaderValue document, boolean includeProcesses) {
279: List nodeInstances = new ArrayList();
280: Set visitedNodeInstanceIds = new HashSet();
281: for (Iterator iterator = document
282: .getInitialRouteNodeInstances().iterator(); iterator
283: .hasNext();) {
284: RouteNodeInstance initialNodeInstance = (RouteNodeInstance) iterator
285: .next();
286: flattenNodeInstanceGraph(nodeInstances,
287: visitedNodeInstanceIds, initialNodeInstance,
288: includeProcesses);
289: }
290: return nodeInstances;
291: }
292:
293: private void flattenNodeInstanceGraph(List nodeInstances,
294: Set visitedNodeInstanceIds, RouteNodeInstance nodeInstance,
295: boolean includeProcesses) {
296: if (visitedNodeInstanceIds.contains(nodeInstance
297: .getRouteNodeInstanceId())) {
298: return;
299: }
300: if (includeProcesses && nodeInstance.getProcess() != null) {
301: flattenNodeInstanceGraph(nodeInstances,
302: visitedNodeInstanceIds, nodeInstance.getProcess(),
303: includeProcesses);
304: }
305: visitedNodeInstanceIds.add(nodeInstance
306: .getRouteNodeInstanceId());
307: nodeInstances.add(nodeInstance);
308: for (Iterator iterator = nodeInstance.getNextNodeInstances()
309: .iterator(); iterator.hasNext();) {
310: RouteNodeInstance nextNodeInstance = (RouteNodeInstance) iterator
311: .next();
312: flattenNodeInstanceGraph(nodeInstances,
313: visitedNodeInstanceIds, nextNodeInstance,
314: includeProcesses);
315: }
316: }
317:
318: public NodeGraphSearchResult searchNodeGraph(
319: NodeGraphSearchCriteria criteria) {
320: NodeGraphContext context = new NodeGraphContext();
321: if (criteria.getSearchDirection() == NodeGraphSearchCriteria.SEARCH_DIRECTION_BACKWARD) {
322: searchNodeGraphBackward(context, criteria.getMatcher(),
323: null, criteria.getStartingNodeInstances());
324: } else {
325: throw new UnsupportedOperationException(
326: "Search feature can only search backward currently.");
327: }
328: List exactPath = determineExactPath(context, criteria
329: .getSearchDirection(), criteria
330: .getStartingNodeInstances());
331: return new NodeGraphSearchResult(context
332: .getCurrentNodeInstance(), exactPath);
333: }
334:
335: private void searchNodeGraphBackward(NodeGraphContext context,
336: NodeMatcher matcher,
337: RouteNodeInstance previousNodeInstance,
338: Collection nodeInstances) {
339: if (nodeInstances == null) {
340: return;
341: }
342: for (Iterator iterator = nodeInstances.iterator(); iterator
343: .hasNext();) {
344: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
345: .next();
346: context.setPreviousNodeInstance(previousNodeInstance);
347: context.setCurrentNodeInstance(nodeInstance);
348: searchNodeGraphBackward(context, matcher);
349: if (context.getResultNodeInstance() != null) {
350: // we've located the node instance we're searching for, we're done
351: break;
352: }
353: }
354: }
355:
356: private void searchNodeGraphBackward(NodeGraphContext context,
357: NodeMatcher matcher) {
358: RouteNodeInstance current = context.getCurrentNodeInstance();
359: int numBranches = current.getNextNodeInstances().size();
360: // if this is a split node, we want to wait here, until all branches join back to us
361: if (numBranches > 1) {
362: // determine the number of branches that have joined back to the split thus far
363: Integer joinCount = (Integer) context.getSplitState().get(
364: current.getRouteNodeInstanceId());
365: if (joinCount == null) {
366: joinCount = new Integer(0);
367: }
368: // if this split is not a leaf node we increment the count
369: if (context.getPreviousNodeInstance() != null) {
370: joinCount = new Integer(joinCount.intValue() + 1);
371: }
372: context.getSplitState().put(
373: current.getRouteNodeInstanceId(), joinCount);
374: // if not all branches have joined, stop and wait for other branches to join
375: if (joinCount.intValue() != numBranches) {
376: return;
377: }
378: }
379: if (matcher.isMatch(context)) {
380: context.setResultNodeInstance(current);
381: } else {
382: context.getVisited().put(current.getRouteNodeInstanceId(),
383: current);
384: searchNodeGraphBackward(context, matcher, current, current
385: .getPreviousNodeInstances());
386: }
387: }
388:
389: public List getActiveNodeInstances(
390: DocumentRouteHeaderValue document, String nodeName) {
391: Collection activeNodes = getActiveNodeInstances(document
392: .getRouteHeaderId());
393: List foundNodes = new ArrayList();
394: for (Iterator iterator = activeNodes.iterator(); iterator
395: .hasNext();) {
396: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
397: .next();
398: if (nodeInstance.getName().equals(nodeName)) {
399: foundNodes.add(nodeInstance);
400: }
401: }
402: return foundNodes;
403: }
404:
405: private List determineExactPath(NodeGraphContext context,
406: int searchDirection, Collection startingNodeInstances) {
407: List exactPath = new ArrayList();
408: if (context.getResultNodeInstance() == null) {
409: exactPath.addAll(context.getVisited().values());
410: } else {
411: determineExactPath(exactPath, new HashMap(),
412: startingNodeInstances, context
413: .getResultNodeInstance());
414: }
415: if (NodeGraphSearchCriteria.SEARCH_DIRECTION_FORWARD == searchDirection) {
416: Collections.sort(exactPath, NODE_INSTANCE_BACKWARD_SORT);
417: } else {
418: Collections.sort(exactPath, NODE_INSTANCE_FORWARD_SORT);
419: }
420: return exactPath;
421: }
422:
423: private void determineExactPath(List exactPath, Map visited,
424: Collection startingNodeInstances,
425: RouteNodeInstance nodeInstance) {
426: if (nodeInstance == null) {
427: return;
428: }
429: if (visited.containsKey(nodeInstance.getRouteNodeInstanceId())) {
430: return;
431: }
432: visited
433: .put(nodeInstance.getRouteNodeInstanceId(),
434: nodeInstance);
435: exactPath.add(nodeInstance);
436: for (Iterator iterator = startingNodeInstances.iterator(); iterator
437: .hasNext();) {
438: RouteNodeInstance startingNode = (RouteNodeInstance) iterator
439: .next();
440: if (startingNode.getRouteNodeInstanceId().equals(
441: nodeInstance.getRouteNodeInstanceId())) {
442: return;
443: }
444: }
445: for (Iterator iterator = nodeInstance.getNextNodeInstances()
446: .iterator(); iterator.hasNext();) {
447: RouteNodeInstance nextNodeInstance = (RouteNodeInstance) iterator
448: .next();
449: determineExactPath(exactPath, visited,
450: startingNodeInstances, nextNodeInstance);
451: }
452: }
453:
454: /**
455: * Sorts by RouteNodeId or the order the nodes will be evaluated in *roughly*. This is
456: * for display purposes when rendering a flattened list of nodes.
457: *
458: * @author rkirkend
459: */
460: private static class RouteNodeSorter implements Comparator {
461: public int compare(Object arg0, Object arg1) {
462: RouteNode rn1 = (RouteNode) arg0;
463: RouteNode rn2 = (RouteNode) arg1;
464: return rn1.getRouteNodeId().compareTo(rn2.getRouteNodeId());
465: }
466: }
467:
468: private static class NodeInstanceIdSorter implements Comparator {
469: public int compare(Object arg0, Object arg1) {
470: RouteNodeInstance nodeInstance1 = (RouteNodeInstance) arg0;
471: RouteNodeInstance nodeInstance2 = (RouteNodeInstance) arg1;
472: return nodeInstance1.getRouteNodeInstanceId().compareTo(
473: nodeInstance2.getRouteNodeInstanceId());
474: }
475: }
476:
477: public void deleteByRouteNodeInstance(
478: RouteNodeInstance routeNodeInstance) {
479: //update the route node instance link table to cancel the relationship between the to-be-deleted instance and the previous node instances
480: routeNodeDAO.deleteLinksToPreNodeInstances(routeNodeInstance);
481: //delete the routeNodeInstance and its next node instances
482: routeNodeDAO
483: .deleteRouteNodeInstancesHereAfter(routeNodeInstance);
484: }
485:
486: public void deleteNodeStateById(Long nodeStateId) {
487: routeNodeDAO.deleteNodeStateById(nodeStateId);
488: }
489:
490: public void deleteNodeStates(List statesToBeDeleted) {
491: routeNodeDAO.deleteNodeStates(statesToBeDeleted);
492: }
493:
494: /**
495: * Records the revocation in the root BranchState of the document.
496: */
497: public void revokeNodeInstance(DocumentRouteHeaderValue document,
498: RouteNodeInstance nodeInstance) {
499: if (document == null) {
500: throw new IllegalArgumentException(
501: "Document must not be null.");
502: }
503: if (nodeInstance == null
504: || nodeInstance.getRouteNodeInstanceId() == null) {
505: throw new IllegalArgumentException(
506: "In order to revoke a final approval node the node instance must be persisent and have an id.");
507: }
508: // get the initial node instance, the root branch is where we will store the state
509: RouteNodeInstance initialInstance = (RouteNodeInstance) document
510: .getInitialRouteNodeInstance(0);
511: Branch rootBranch = initialInstance.getBranch();
512: BranchState state = rootBranch
513: .getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
514: if (state == null) {
515: state = new BranchState(REVOKED_NODE_INSTANCES_STATE_KEY,
516: "");
517: rootBranch.addBranchState(state);
518: }
519: if (state.getValue() == null) {
520: state.setValue("");
521: }
522: state.setValue(state.getValue()
523: + nodeInstance.getRouteNodeInstanceId() + ",");
524: save(rootBranch);
525: }
526:
527: /**
528: * Queries the list of revoked node instances from the root BranchState of the Document
529: * and returns a List of revoked RouteNodeInstances.
530: */
531: public List getRevokedNodeInstances(
532: DocumentRouteHeaderValue document) {
533: if (document == null) {
534: throw new IllegalArgumentException(
535: "Document must not be null.");
536: }
537: List revokedNodeInstances = new ArrayList();
538: RouteNodeInstance initialInstance = (RouteNodeInstance) document
539: .getInitialRouteNodeInstance(0);
540: Branch rootBranch = initialInstance.getBranch();
541: BranchState state = rootBranch
542: .getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
543: if (state == null || Utilities.isEmpty(state.getValue())) {
544: return revokedNodeInstances;
545: }
546: String[] revokedNodes = state.getValue().split(",");
547: for (int index = 0; index < revokedNodes.length; index++) {
548: String revokedNodeInstanceIdValue = revokedNodes[index];
549: Long revokedNodeInstanceId = Long
550: .valueOf(revokedNodeInstanceIdValue);
551: RouteNodeInstance revokedNodeInstance = findRouteNodeInstanceById(revokedNodeInstanceId);
552: if (revokedNodeInstance == null) {
553: LOG
554: .warn("Could not locate revoked RouteNodeInstance with the given id: "
555: + revokedNodeInstanceId);
556: } else {
557: revokedNodeInstances.add(revokedNodeInstance);
558: }
559: }
560: return revokedNodeInstances;
561: }
562:
563: }
|