001: /*
002: * ====================================================================
003: *
004: * XFLOW - Process Management System
005: * Copyright (C) 2003 Rob Tan
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions, and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions, and the disclaimer that follows
017: * these conditions in the documentation and/or other materials
018: * provided with the distribution.
019: *
020: * 3. The name "XFlow" must not be used to endorse or promote products
021: * derived from this software without prior written permission. For
022: * written permission, please contact rcktan@yahoo.com
023: *
024: * 4. Products derived from this software may not be called "XFlow", nor
025: * may "XFlow" appear in their name, without prior written permission
026: * from the XFlow Project Management (rcktan@yahoo.com)
027: *
028: * In addition, we request (but do not require) that you include in the
029: * end-user documentation provided with the redistribution and/or in the
030: * software itself an acknowledgement equivalent to the following:
031: * "This product includes software developed by the
032: * XFlow Project (http://xflow.sourceforge.net/)."
033: * Alternatively, the acknowledgment may be graphical using the logos
034: * available at http://xflow.sourceforge.net/
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE XFLOW AUTHORS OR THE PROJECT
040: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: *
049: * ====================================================================
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the XFlow Project and was originally
052: * created by Rob Tan (rcktan@yahoo.com)
053: * For more information on the XFlow Project, please see:
054: * <http://xflow.sourceforge.net/>.
055: * ====================================================================
056: */
057: package xflow.common;
058:
059: import java.sql.*;
060: import java.util.*;
061: import java.io.*;
062: import xflow.util.*;
063: import org.apache.log4j.Logger;
064:
065: /**
066: * The Node class represents a node in a directed graph.
067: * A directed graph is used to represent a workflow model.
068: */
069: public class Node implements Serializable {
070:
071: private static Logger log = Logger.getLogger(Node.class);
072:
073: public static final String PROCESS = "Process";
074: public static final String AND = "And";
075: public static final String OR = "Or";
076: public static final String START = "Start";
077: public static final String END = "End";
078: public static final String CONTAINER = "Container";
079:
080: private int nodeId;
081: private String nodeType;
082: private String name;
083: private String description;
084: private Vector destinations;
085: private Vector fromNodes;
086: private HashMap properties;
087:
088: static int count = 0;
089:
090: /**
091: * Constructs a new node
092: * @param String nodeName
093: */
094: public Node(String nodeName, String nodeType) {
095: this .name = nodeName;
096: this .nodeType = nodeType;
097: destinations = new Vector();
098: fromNodes = new Vector();
099: description = nodeName;
100: properties = new HashMap();
101: }
102:
103: /**
104: * Constructs a new node
105: * @param int nodeId
106: */
107: public Node(int nodeId) {
108: this .nodeId = nodeId;
109: destinations = new Vector();
110: fromNodes = new Vector();
111: properties = new HashMap();
112: }
113:
114: /**
115: * Returns the node ID
116: * @return int nodeId
117: */
118: public int getNodeId() {
119: return nodeId;
120: }
121:
122: /**
123: * Returns the node ID
124: * @return Integer nodeId
125: */
126: public Integer getNodeIdAsInteger() {
127: return new Integer(nodeId);
128: }
129:
130: /**
131: * Returns the node name
132: * @return String node name
133: */
134: public String getNodeName() {
135: return name;
136: }
137:
138: /**
139: * Returns the node type
140: * @return String node type
141: */
142: public String getNodeType() {
143: return nodeType;
144: }
145:
146: /**
147: * Returns node description
148: * @return description
149: */
150: public String getDescription() {
151: return description;
152: }
153:
154: /**
155: * Sets the node description
156: * @param String d
157: */
158: public void setDescription(String d) {
159: description = d;
160: }
161:
162: /**
163: * Returns the node's containee graph name. Only valid if node's type is CONTAINER
164: * @return containee
165: */
166: public String getContainee() {
167: String containee = null;
168: if (nodeType.equals(Node.CONTAINER)) {
169: containee = (String) properties.get("containee");
170: }
171: return containee;
172: }
173:
174: /**
175: * Sets the node's containee graph name. Only valid if node's type is CONTAINER
176: * @param String grapghName
177: */
178: public void setContainee(String graphName) {
179: if (nodeType.equals(Node.CONTAINER)) {
180: properties.put("containee", graphName);
181: }
182: }
183:
184: /**
185: * Returns the node's containee graph version. Only valid if node's type is CONTAINER
186: * @return containeeVersion
187: */
188: public int getContaineeVersion() {
189: int version = -1;
190: if (nodeType.equals(Node.CONTAINER)) {
191: Integer iObj = (Integer) properties.get("containeeVersion");
192: if (iObj != null) {
193: version = iObj.intValue();
194: }
195: }
196: return version;
197: }
198:
199: /**
200: * Sets the node's containee graph version. Only valid if node's type is CONTAINER
201: * @param int version
202: */
203: public void setContaineeVersion(int i) {
204: if (nodeType.equals(Node.CONTAINER)) {
205: properties.put("containeeVersion", new Integer(i));
206: }
207: }
208:
209: /**
210: * Recursively loads this node and all nodes reachable from this node
211: * from database.
212: *
213: * @param int gid The graph ID
214: * @param HashMap hashTable The hash table of all processed nodes so far
215: */
216: private void expand(int gid, HashMap hashTable, Connection con) {
217:
218: //System.out.println ("Expanding " + nodeId);
219:
220: Statement s = null;
221:
222: try {
223: s = con.createStatement();
224:
225: // Get the attributes of this node from node table
226: ResultSet rs = s
227: .executeQuery("select * from node where nid = "
228: + nodeId);
229: if (rs.next()) {
230: name = rs.getString("name");
231: description = rs.getString("description");
232: nodeType = rs.getString("nodetype");
233: // If nodeType is Start or End, force node name to be fixed
234: if (nodeType.equals(Node.START)) {
235: name = Node.START;
236: } else if (nodeType.equals(Node.END)) {
237: name = Node.END;
238: }
239: }
240: s.close();
241:
242: // Get all properties for this node
243: s = con.createStatement();
244: rs = s
245: .executeQuery("select name, value from nodeprops where nid = "
246: + nodeId);
247: while (rs.next()) {
248: String name = rs.getString("name");
249: String vstr = rs.getString("value");
250: Object obj = HexUtil.hexDecodeObject(vstr);
251: properties.put(name, obj);
252: }
253: s.close();
254:
255: // Get all the destinations for this node from database
256: s = con.createStatement();
257: rs = s
258: .executeQuery("select destnid, rule from destination where nid = "
259: + nodeId);
260: log
261: .info("select destnid, rule from destination where nid = "
262: + nodeId);
263:
264: // For each destination:
265: // 1. create a node for it and expand from this node
266: // 2. create a Destination object and add to this node's
267: // list of destinations.
268: while (rs.next()) {
269: String destNid = rs.getString("destnid");
270: String rule = rs.getString("rule");
271: Integer intObj = new Integer(destNid);
272: int destNodeId = intObj.intValue();
273: Node destNode = (Node) hashTable.get(intObj);
274: if (destNode == null) {
275: // 1. Create the node
276: destNode = new Node(destNodeId);
277: hashTable.put(intObj, destNode);
278: destNode.expand(gid, hashTable, con);
279: } else {
280: // Don't process this node - we have already processed it
281: System.out.println("Already exists in hash: "
282: + intObj);
283: }
284:
285: // 2. Add the destination to this node
286: addDestination(destNode, rule);
287: }
288: } catch (Exception e) {
289: e.printStackTrace();
290: } finally {
291: if (s != null) {
292: try {
293: s.close();
294: } catch (Exception e) {
295: e.printStackTrace();
296: }
297: }
298: }
299: }
300:
301: /**
302: * Recursively loads this node and all nodes reachable from this node
303: * from database.
304: *
305: * @param int gid The graph ID
306: */
307: public void expand(int gid, Connection con) {
308:
309: HashMap nodeHash = new HashMap();
310: expand(gid, nodeHash, con);
311: }
312:
313: /**
314: * Recursively saves the links between a node and its destinations.
315: *
316: * @param HashMap hashTable of already processed nodes
317: */
318: private void saveLink(HashMap hash, Connection con) {
319:
320: // Already saved links for this node
321: Node n = (Node) hash.get(name);
322: if (n != null)
323: return;
324:
325: // Put this node in hash table - this marks the node as "processed"
326: hash.put(name, this );
327:
328: Statement s = null;
329:
330: // For each destination....
331: for (int i = 0; i < destinations.size(); i++) {
332: Destination d = (Destination) destinations.elementAt(i);
333: Node destNode = d.node;
334: int destNodeId = destNode.getNodeId();
335: String rule = d.rule;
336:
337: try {
338: s = con.createStatement();
339:
340: // Insert a row in the destination table
341: String sql = "";
342: if (rule != null) {
343: sql = "insert into destination values (" + nodeId
344: + "," + destNodeId + ",'" + rule + "')";
345: } else {
346: sql = "insert into destination values (" + nodeId
347: + "," + destNodeId + ",null)";
348: }
349: log.info(sql);
350: s.executeUpdate(sql);
351: } catch (Exception e) {
352: e.printStackTrace();
353: } finally {
354: if (s != null) {
355: try {
356: s.close();
357: } catch (Exception e) {
358: e.printStackTrace();
359: }
360: }
361: }
362: }
363:
364: // Now recurse and save links of destination nodes
365: for (int i = 0; i < destinations.size(); i++) {
366: Destination d = (Destination) destinations.elementAt(i);
367: Node destNode = d.node;
368: destNode.saveLink(hash, con);
369: }
370: }
371:
372: /**
373: * Recursively saves the links between a node and its destinations.
374: */
375: private void saveLink(Connection con) {
376:
377: HashMap hash = new HashMap();
378: saveLink(hash, con);
379: }
380:
381: /**
382: * Recursively saves a node, its destinations and all links
383: * between nodes to the database.
384: *
385: * @param int gid The graph ID
386: */
387: public void saveDB(int gid, Connection con) {
388:
389: // First save the node and all nodes reachable by it
390: HashMap hash = new HashMap();
391: saveDB(gid, hash, con);
392:
393: // Now save the links between nodes
394: saveLink(con);
395: }
396:
397: /**
398: * Recursively saves a node, its destinations and all links
399: * between nodes to the database.
400: *
401: * @param int gid The graph ID
402: * @param HashMap hash The hash table of all processed nodes
403: */
404: private void saveDB(int gid, HashMap hash, Connection con) {
405:
406: // Already saved this node
407: Node n = (Node) hash.get(name);
408: if (n != null)
409: return;
410:
411: Statement s = null;
412:
413: try {
414: s = con.createStatement();
415:
416: // Insert a row in the node table
417: String sql = "insert into node values (" + gid
418: + ", null, '" + name + "','" + nodeType + "','"
419: + description + "');";
420:
421: log.info(sql);
422: hash.put(name, this );
423: s.execute(sql);
424:
425: sql = "select max(nid), nid from node";
426: ResultSet rs = s.executeQuery(sql);
427: if (rs.next()) {
428: nodeId = rs.getInt("nid");
429: }
430:
431: // Insert the node's properties
432: Iterator itr = properties.keySet().iterator();
433: while (itr.hasNext()) {
434: String key = (String) itr.next();
435: Object value = properties.get(key);
436: String valueStr = HexUtil.hexEncodeObject(value);
437: if (valueStr == null) {
438: continue;
439: }
440:
441: sql = "insert into nodeprops values (" + nodeId + ",'"
442: + key + "','" + valueStr + "')";
443:
444: log.info(sql);
445: s.execute(sql);
446: }
447:
448: // Recursively call saveDB for each destination
449: for (int i = 0; i < destinations.size(); i++) {
450: Destination d = (Destination) destinations.elementAt(i);
451: Node destNode = d.node;
452: destNode.saveDB(gid, hash, con);
453: }
454: } catch (Exception e) {
455: e.printStackTrace();
456: } finally {
457: if (s != null) {
458: try {
459: s.close();
460: } catch (Exception e) {
461: e.printStackTrace();
462: }
463: }
464: }
465: }
466:
467: /**
468: * Detects if the graph contains cycles.
469: *
470: * @param HashMap hashTable - contains the nodes already visited
471: * @param cycleDetected - true if cycle has been detected.
472: *
473: * @return boolean true if cycle detected.
474: *
475: */
476: private boolean detectCycle(HashMap hashTable, boolean cycleDetected) {
477:
478: // We have found a cycle - rewind the recursion
479: if (cycleDetected) {
480: return true;
481: }
482:
483: for (int i = 0; i < destinations.size(); i++) {
484: Destination d = (Destination) destinations.elementAt(i);
485: Node destNode = d.node;
486: Integer destNodeId = destNode.getNodeIdAsInteger();
487:
488: // Is destination node already in the list of nodes we came from?
489: // If yes, we have a cycle.
490: Node findNode = (Node) hashTable.get(destNodeId);
491: if (findNode != null) {
492: // We've got a cycle. Unwind
493: System.out.println("Cycle detected. From Node: "
494: + nodeId + " To Node: " + destNodeId);
495: cycleDetected = true;
496: break; // Get out
497: } else {
498: // No cycle detected - continue the graph traversal
499: hashTable.put(destNodeId, destNode);
500: cycleDetected = destNode.detectCycle(hashTable,
501: cycleDetected);
502: hashTable.remove(destNodeId);
503: }
504: }
505:
506: return cycleDetected;
507: }
508:
509: /**
510: * Detects if the graph contains cycles.
511: */
512: public boolean detectCycle() {
513: HashMap hashTable = new HashMap();
514:
515: Integer objKey = new Integer(nodeId);
516: hashTable.put(objKey, this );
517: boolean result = detectCycle(hashTable, false);
518: return result;
519: }
520:
521: /**
522: * Recursively traverses all the nodes of a graph.
523: * Useful for debugging.
524: */
525: public void traverse() {
526: this .print();
527: if (destinations.size() == 0) {
528: System.out.println("No more destinations for " + nodeId);
529: }
530:
531: for (int i = 0; i < destinations.size(); i++) {
532: Destination d = (Destination) destinations.elementAt(i);
533: d.node.traverse();
534: }
535: }
536:
537: /**
538: * Prints out node id and description of node.
539: * Useful for debugging.
540: */
541: public void print() {
542: System.out.println("Node Id: " + nodeId + "\n" + "Node Name: "
543: + name + "\n" + "Description: " + description);
544: Iterator itr = properties.keySet().iterator();
545: while (itr.hasNext()) {
546: String key = (String) itr.next();
547: Object value = properties.get(key);
548: System.out.println(key + " = " + value);
549: }
550:
551: }
552:
553: /**
554: * Finds a node within a graph
555: *
556: * @param int nodeId The node ID for finding
557: * @param Node result The result
558: *
559: * @return Node the result, null if not found
560: */
561: private Node getNode(int nodeId, Node result) {
562: if (result != null) {
563: return result;
564: }
565:
566: if (this .nodeId == nodeId) {
567: System.out.println("Found " + nodeId);
568: result = this ;
569: } else {
570: for (int i = 0; i < destinations.size(); i++) {
571: Destination d = (Destination) destinations.elementAt(i);
572: result = d.node.getNode(nodeId, result);
573: if (result != null)
574: break;
575: }
576: }
577:
578: return result;
579: }
580:
581: /**
582: * Finds and returns a node within a graph given a node ID
583: *
584: * @param int nodeId The node ID for finding
585: * @return Node the result, null if not found
586: */
587: public Node getNode(int nodeId) {
588: return getNode(nodeId, null);
589: }
590:
591: /**
592: * Finds a node within a graph
593: *
594: * @param String name The node name for finding
595: * @param Node result The result
596: *
597: * @return Node the result, null if not found
598: */
599: private Node getNode(String name, Node result) {
600: if (result != null) {
601: return result;
602: }
603:
604: if (this .name.equals(name)) {
605: System.out.println("Found " + name);
606: result = this ;
607: } else {
608: for (int i = 0; i < destinations.size(); i++) {
609: Destination d = (Destination) destinations.elementAt(i);
610: result = d.node.getNode(name, result);
611: if (result != null)
612: break;
613: }
614: }
615:
616: return result;
617: }
618:
619: /**
620: * Finds and returns a node within a graph given a node name
621: *
622: * @param String name The node name for finding
623: * @return Node the result, null if not found
624: */
625: public Node getNode(String name) {
626: return getNode(name, null);
627: }
628:
629: /**
630: * Adds a destination and a rule to evaluate a workflowobject's
631: * transition to this destination.
632: *
633: * @param Node node The destination node
634: * @param String rule The rule for reaching this destination
635: *
636: */
637: public void addDestination(Node node, String rule) {
638: Destination d = new Destination(node, rule);
639: destinations.addElement(d);
640: node.addFromNode(this );
641: }
642:
643: /**
644: * @return Vector - this node's list of destinations
645: */
646: public Vector getDestinations() {
647: return destinations;
648: }
649:
650: /**
651: * Adds a fromNode to this node
652: *
653: * @param Node node The from node
654: *
655: */
656: public void addFromNode(Node n) {
657: fromNodes.addElement(n);
658: }
659:
660: /**
661: * @return Vector - this node's list of from nodes
662: */
663: public Vector getFromNodes() {
664: return fromNodes;
665: }
666:
667: /**
668: * @return Vector - all descendant nodes of specified type
669: */
670: public Vector getNodes(String nodeType) {
671: Vector v = new Vector();
672: HashMap map = new HashMap();
673: getNodes(nodeType, map);
674:
675: Iterator itr = map.values().iterator();
676: while (itr.hasNext()) {
677: v.addElement(itr.next());
678: }
679: return v;
680: }
681:
682: private void getNodes(String nType, HashMap map) {
683: if (nodeType.equals(nType)) {
684: map.put(name, this );
685: }
686: for (int i = 0; i < destinations.size(); i++) {
687: Destination d = (Destination) destinations.elementAt(i);
688: Node dnode = d.node;
689: dnode.getNodes(nType, map);
690: }
691: }
692:
693: /**
694: * @return Vector - all descendant nodes
695: */
696: public Vector getNodes() {
697: Vector v = new Vector();
698: HashMap map = new HashMap();
699: getNodes(map);
700:
701: Iterator itr = map.values().iterator();
702: while (itr.hasNext()) {
703: v.addElement(itr.next());
704: }
705: return v;
706: }
707:
708: private void getNodes(HashMap map) {
709: map.put(name, this );
710: for (int i = 0; i < destinations.size(); i++) {
711: Destination d = (Destination) destinations.elementAt(i);
712: Node dnode = d.node;
713: dnode.getNodes(map);
714: }
715: }
716:
717: /**
718: * Sets a property on a node
719: *
720: * @param key the property name
721: * @param value the property value - must be serializable
722: *
723: */
724: public void setProperty(String key, Object value) {
725: properties.put(key, value);
726: }
727:
728: /**
729: * Gets a node's property
730: *
731: * @param key the property name
732: * @return the property value
733: */
734: public Object getProperty(String key) {
735: return properties.get(key);
736: }
737:
738: /**
739: * Sets the timeout value for a Process node
740: *
741: * @param timeoutMinutes the timeout in minutes
742: *
743: */
744: public void setTimeoutMinutes(int tout) {
745: if (!nodeType.equals(PROCESS)) {
746: return;
747: }
748: properties.put("timeoutMinutes", new Integer(tout));
749: }
750:
751: /**
752: * Gets the timeout for a Process node
753: *
754: * @return the timeout in minutes
755: */
756: public int getTimeoutMinutes() {
757: if (!nodeType.equals(PROCESS)) {
758: return -1;
759: }
760: Integer tout = (Integer) properties.get("timeoutMinutes");
761: if (tout != null) {
762: return tout.intValue();
763: } else {
764: return -1;
765: }
766: }
767:
768: /**
769: * Sets the timeout handler for a Process node
770: *
771: * @param timeoutHandler the name of the timeout handler (a workflow name)
772: *
773: */
774: public void setTimeoutHandler(String handler) {
775: if (!nodeType.equals(PROCESS)) {
776: return;
777: }
778: properties.put("timeoutHandler", handler);
779: }
780:
781: /**
782: * Gets the timeout handler for a Process node
783: *
784: * @return the timeout handler name
785: */
786: public String getTimeoutHandler() {
787: if (!nodeType.equals(PROCESS)) {
788: return null;
789: }
790: String handler = (String) properties.get("timeoutHandler");
791: return handler;
792: }
793: }
|