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:
064: import org.apache.log4j.Logger;
065:
066: public class DirectedGraph implements Serializable {
067:
068: private static Logger log = Logger.getLogger(DirectedGraph.class);
069:
070: private Node rootNode;
071: private int graphId;
072: private String name;
073: private String description;
074: private int version;
075:
076: private HashMap startNode = new HashMap();
077: private HashMap endNode = new HashMap();
078:
079: /**
080: * Constructor
081: */
082: public DirectedGraph() {
083: }
084:
085: /**
086: * Creates a new instance of DirectedGraph with name
087: * @param name The name of the graph
088: */
089: public DirectedGraph(String name) {
090: this .name = name;
091: description = name;
092: version = -1;
093: }
094:
095: /**
096: * Creates a new instance of DirectedGraph with name and version
097: * @param name The name of the graph
098: * @param version The version of the graph
099: */
100: public DirectedGraph(String name, int vers) {
101: this .name = name;
102: description = name;
103: version = vers;
104: }
105:
106: // Accessor methods
107:
108: public String getName() {
109: return name;
110: }
111:
112: public void setName(String n) {
113: name = n;
114: }
115:
116: public String getDescription() {
117: return description;
118: }
119:
120: public void setDescription(String d) {
121: description = d;
122: }
123:
124: public int getVersion() {
125: return version;
126: }
127:
128: public void setVersion(int v) {
129: version = v;
130: }
131:
132: public int getGraphId() {
133: return graphId;
134: }
135:
136: public void setGraphId(int i) {
137: graphId = i;
138: }
139:
140: /**
141: * Sets the root node of this graph.
142: * @param Node, the root node
143: */
144: public void setRootNode(Node node) {
145: rootNode = node;
146: }
147:
148: /**
149: * Gets the root node of this graph
150: * @returns Node, the root node
151: *
152: */
153: public Node getRootNode() {
154: return rootNode;
155: }
156:
157: /**
158: * Retrieves a graph from the Database, including all of its nodes.
159: */
160: public void loadDB() throws XflowException {
161:
162: Connection con = null;
163: Statement s = null;
164:
165: try {
166: con = Persistence.getConnection();
167: s = con.createStatement();
168:
169: // Get the graph Id and its root node ID
170: String sql = null;
171:
172: // Load the latest version
173: if (version == -1) {
174: sql = "select max(version), * from graph where name='"
175: + name + "'";
176: } else {
177: sql = "select * from graph where name='" + name
178: + "' and version = " + version;
179: }
180: log.info(sql);
181: ResultSet rs = s.executeQuery(sql);
182: if (rs.next()) {
183: String graphIdStr = rs.getString("gid");
184: String rootNodeIdStr = rs.getString("nid");
185: version = rs.getInt("version");
186: description = rs.getString("description");
187: Integer iobj = new Integer(rootNodeIdStr);
188: int rootNodeId = iobj.intValue();
189: iobj = new Integer(graphIdStr);
190: graphId = iobj.intValue();
191: rootNode = new Node(rootNodeId);
192:
193: // Expands the rootNode - this recursively loads all nodes
194: // of the graph
195: rootNode.expand(graphId, con);
196: } else {
197: throw new XflowException(
198: "Failed to load graph from database");
199: }
200: } catch (Exception e) {
201: throw new XflowException(e.getMessage());
202: } finally {
203: if (s != null) {
204: try {
205: s.close();
206: } catch (Exception e) {
207: e.printStackTrace();
208: }
209: }
210: if (con != null) {
211: try {
212: con.close();
213: } catch (Exception e) {
214: }
215: }
216: }
217: }
218:
219: public static int getGraphId(String graphName, int version)
220: throws XflowException {
221:
222: int gid = -1;
223: Connection con = null;
224: Statement s = null;
225:
226: try {
227: con = Persistence.getConnection();
228: s = con.createStatement();
229:
230: String sql = null;
231:
232: // Load the latest version
233: if (version == -1) {
234: sql = "select max(version), * from graph where name='"
235: + graphName + "'";
236: } else {
237: sql = "select * from graph where name='" + graphName
238: + "' and version = " + version;
239: }
240: log.info(sql);
241: ResultSet rs = s.executeQuery(sql);
242: if (rs.next()) {
243: gid = rs.getInt("gid");
244: }
245: } catch (Exception e) {
246: throw new XflowException(e.getMessage());
247: } finally {
248: if (s != null) {
249: try {
250: s.close();
251: } catch (Exception e) {
252: e.printStackTrace();
253: }
254: }
255: if (con != null) {
256: try {
257: con.close();
258: } catch (Exception e) {
259: }
260: }
261: }
262:
263: return gid;
264: }
265:
266: public static int getLatestVersionNumber(String graphName)
267: throws XflowException {
268:
269: int version = -1;
270: Connection con = null;
271: Statement s = null;
272:
273: try {
274: con = Persistence.getConnection();
275: s = con.createStatement();
276:
277: String sql = "select max(version), * from graph where name='"
278: + graphName + "'";
279: log.info(sql);
280: ResultSet rs = s.executeQuery(sql);
281: if (rs.next()) {
282: version = rs.getInt("version");
283: }
284: } catch (Exception e) {
285: throw new XflowException(e.getMessage());
286: } finally {
287: if (s != null) {
288: try {
289: s.close();
290: } catch (Exception e) {
291: e.printStackTrace();
292: }
293: }
294: if (con != null) {
295: try {
296: con.close();
297: } catch (Exception e) {
298: }
299: }
300: }
301:
302: return version;
303: }
304:
305: /**
306: * Retrieves a graph from the Database using graph ID, including all of its nodes.
307: */
308: public static DirectedGraph loadByGraphId(int gid)
309: throws XflowException {
310:
311: Connection con = null;
312: Statement s = null;
313: DirectedGraph dg = null;
314:
315: try {
316: con = Persistence.getConnection();
317: s = con.createStatement();
318:
319: // Get the graph Id and its root node ID
320: String sql = null;
321: sql = "select * from graph g where g.gid = " + gid;
322: log.info(sql);
323: ResultSet rs = s.executeQuery(sql);
324: if (rs.next()) {
325: String name = rs.getString("name");
326: int version = rs.getInt("version");
327: String description = rs.getString("description");
328:
329: dg = new DirectedGraph(name, version);
330: dg.setGraphId(gid);
331: dg.setDescription(description);
332:
333: String rootNodeIdStr = rs.getString("nid");
334: Integer iobj = new Integer(rootNodeIdStr);
335: int rootNodeId = iobj.intValue();
336: Node rootNode = new Node(rootNodeId);
337: dg.setRootNode(rootNode);
338:
339: // Expands the rootNode - this recursively loads all nodes
340: // of the graph
341: rootNode.expand(gid, con);
342: }
343: } catch (Exception e) {
344: throw new XflowException(e.getMessage());
345: } finally {
346: if (s != null) {
347: try {
348: s.close();
349: } catch (Exception e) {
350: e.printStackTrace();
351: }
352: }
353: if (con != null) {
354: try {
355: con.close();
356: } catch (Exception e) {
357: }
358: }
359: }
360: return dg;
361: }
362:
363: /**
364: * Retrieves a graph from the Database using workflow ID, including all of its nodes.
365: */
366: public static DirectedGraph loadDB(int wfid) throws XflowException {
367:
368: Connection con = null;
369: Statement s = null;
370: DirectedGraph dg = null;
371:
372: try {
373: con = Persistence.getConnection();
374: s = con.createStatement();
375:
376: // Get the graph Id and its root node ID
377: String sql = null;
378: sql = "select * from graph g, workflow w where w.workflowId = "
379: + wfid + "and g.gid = w.gid";
380: log.info(sql);
381: ResultSet rs = s.executeQuery(sql);
382: if (rs.next()) {
383: int gid = rs.getInt("gid");
384: String name = rs.getString("name");
385: int version = rs.getInt("version");
386: String description = rs.getString("description");
387:
388: dg = new DirectedGraph(name, version);
389: dg.setGraphId(gid);
390: dg.setDescription(description);
391:
392: String rootNodeIdStr = rs.getString("nid");
393: Integer iobj = new Integer(rootNodeIdStr);
394: int rootNodeId = iobj.intValue();
395: Node rootNode = new Node(rootNodeId);
396: dg.setRootNode(rootNode);
397:
398: // Expands the rootNode - this recursively loads all nodes
399: // of the graph
400: rootNode.expand(gid, con);
401: }
402: } catch (Exception e) {
403: throw new XflowException(e.getMessage());
404: } finally {
405: if (s != null) {
406: try {
407: s.close();
408: } catch (Exception e) {
409: e.printStackTrace();
410: }
411: }
412: if (con != null) {
413: try {
414: con.close();
415: } catch (Exception e) {
416: }
417: }
418: }
419: return dg;
420: }
421:
422: public boolean graphExistsInDB() throws XflowException {
423:
424: boolean result = false;
425: Connection con = null;
426: Statement s = null;
427:
428: try {
429: if (version != -1) {
430: con = Persistence.getConnection();
431: s = con.createStatement();
432: ResultSet rs;
433: String sql = "select * from graph where name = '"
434: + name + "' and version = " + version;
435: log.info(sql);
436: rs = s.executeQuery(sql);
437: if (rs.next()) {
438: result = true;
439: }
440: }
441: } catch (Exception e) {
442: throw new XflowException(e.getMessage());
443: } finally {
444: if (s != null) {
445: try {
446: s.close();
447: } catch (Exception e) {
448: e.printStackTrace();
449: }
450: }
451: if (con != null) {
452: try {
453: con.close();
454: } catch (Exception e) {
455: }
456: }
457: }
458: return result;
459: }
460:
461: /**
462: * Save the graph and all its nodes to the Database.
463: */
464: public void saveDB() throws XflowException {
465:
466: Connection con = null;
467: Statement s = null;
468:
469: // Make sure that this graph does not already exist in the database
470: if (graphExistsInDB()) {
471: throw new XflowException("There is already a graph called "
472: + name + " in the database");
473: }
474:
475: // Save the graph
476: try {
477:
478: con = Persistence.getConnection();
479: s = con.createStatement();
480:
481: graphId = Util.generateUniqueIntId();
482:
483: // Save the nodes - starting from rootNode - this
484: // recursively saves all of the nodes reachable from the root node
485: rootNode.saveDB(graphId, con);
486:
487: // Get the last version number and bump it up by 1
488: int nextVersion = 0;
489: ResultSet rs = s
490: .executeQuery("select max(version), version from graph where name = '"
491: + name + "'");
492: if (rs.next()) {
493: nextVersion = rs.getInt("version") + 1;
494: }
495: version = nextVersion;
496:
497: // Now save the graph table
498: int rootNodeId = rootNode.getNodeId();
499: String sql = "insert into graph values (" + graphId + ","
500: + rootNodeId + ",'" + name + "','" + description
501: + "'," + version + ")";
502: log.info(sql);
503: s.executeUpdate(sql);
504: } catch (Exception e) {
505: throw new XflowException(e.getMessage());
506: } finally {
507: if (s != null) {
508: try {
509: s.close();
510: } catch (Exception e) {
511: e.printStackTrace();
512: }
513: }
514: if (con != null) {
515: try {
516: con.close();
517: } catch (Exception e) {
518: }
519: }
520: }
521: }
522:
523: /**
524: * Gets a node given the node Id
525: * @returns Node
526: *
527: */
528: public Node getNode(int nodeId) {
529: return rootNode.getNode(nodeId);
530: }
531:
532: /**
533: * Validate a DirectedGraph
534: * @throws XflowException
535: */
536: public void validate() throws XflowException {
537: validate(rootNode);
538: }
539:
540: /**
541: * Helper function of validate()
542: * @param node
543: * @throws XflowException
544: */
545: private void validate(Node node) throws XflowException {
546: String type = node.getNodeType();
547: if (type.equals(Node.START)) {
548: validateStart(node);
549: } else if (type.equals(Node.END)) {
550: validateEnd(node);
551: } else if (type.equals(Node.AND)) {
552: validateAND(node);
553: } else if (type.equals(Node.PROCESS)) {
554: validateProcess(node);
555: } else if (type.equals(Node.CONTAINER)) {
556: validateContainer(node);
557: }
558: if (startNode.size() == 0) { // rule#1
559: throw new XflowException(
560: "there is no Start node in the graph");
561: }
562: if (endNode.size() == 0) { //rule #3
563: throw new XflowException(
564: "there is no End node in the graph");
565: }
566: }
567:
568: private void validateStart(Node node) throws XflowException {
569: startNode.put(node.getNodeName(), node);
570: if (startNode.size() != 1) { // rule #1
571: throw new XflowException(
572: "More than one Start node in the graph");
573: } else { //only one start node
574: if (node.getFromNodes().size() != 0) { //rule #2
575: throw new XflowException(
576: "No nodes should go into Start node");
577: } else {
578: Vector destinations = node.getDestinations();
579: if (destinations.size() == 0) { // rule #2
580: throw new XflowException(
581: "Start node should has at lease one node out");
582: } else {
583: for (int i = 0; i < destinations.size(); i++) {
584: Destination d = (Destination) destinations
585: .elementAt(i);
586: String ntype = d.node.getNodeType();
587: if (ntype.equals(Node.CONTAINER)
588: || ntype.equals(Node.PROCESS)) {
589: validate(d.node);
590: } else { // rule #2
591: throw new XflowException(
592: "Start node should go into Container"
593: + " or Process node.");
594: }
595: }
596: }
597: }
598: }
599:
600: }
601:
602: private void validateEnd(Node node) throws XflowException {
603: endNode.put(node.getNodeName(), node);
604: if (endNode.size() != 1) { // rule #3
605: throw new XflowException(
606: "More than one End node in the graph");
607: } else {
608: if (node.getDestinations().size() != 0) { //rule #4
609: throw new XflowException(
610: "No nodes should go out from End node");
611: }
612: }
613:
614: }
615:
616: private void validateAND(Node node) throws XflowException {
617: if (node.getFromNodes().size() < 2) { //rule #5
618: throw new XflowException(
619: "AND node should have at least 2 nodes in");
620: } else {
621: Vector destinations = node.getDestinations();
622: if (destinations.size() == 0) { // rule #6
623: throw new XflowException(
624: "AND node should has at lease one node out");
625: } else {
626: for (int i = 0; i < destinations.size(); i++) {
627: Destination d = (Destination) destinations
628: .elementAt(i);
629: String ntype = d.node.getNodeType();
630: if (ntype.equals(Node.CONTAINER)
631: || ntype.equals(Node.PROCESS)
632: || ntype.equals(Node.END)
633: || ntype.equals(Node.AND)) {
634: validate(d.node);
635: } else { // rule #7
636: throw new XflowException(
637: "AND node should go into a Container,"
638: + " a Process, or End node.");
639: }
640: }
641: }
642: }
643: }
644:
645: private void validateProcess(Node node) throws XflowException {
646: Vector destinations = node.getDestinations();
647: if (destinations.size() == 0) { // rule #8
648: throw new XflowException(
649: "Process node should has at lease one node out");
650: } else {
651: for (int i = 0; i < destinations.size(); i++) {
652: Destination d = (Destination) destinations.elementAt(i);
653: String ntype = d.node.getNodeType();
654: if (ntype.equals(Node.CONTAINER)
655: || ntype.equals(Node.PROCESS)
656: || ntype.equals(Node.END)
657: || ntype.equals(Node.AND)) {
658: validate(d.node);
659: } else { // rule #9
660: throw new XflowException(
661: "Process node should go into a Container,"
662: + " a Process, or End node.");
663: }
664: }
665: }
666: }
667:
668: private void validateContainer(Node node) throws XflowException {
669: Vector destinations = node.getDestinations();
670: for (int i = 0; i < destinations.size(); i++) {
671: Destination d = (Destination) destinations.elementAt(i);
672: String ntype = d.node.getNodeType();
673: if (ntype.equals(Node.CONTAINER)
674: || ntype.equals(Node.PROCESS)
675: || ntype.equals(Node.END) || ntype.equals(Node.AND)) {
676: validate(d.node);
677: } else { // rule #11
678: throw new XflowException(
679: "Container node should go into a Container,"
680: + " a Process, or End node.");
681: }
682: }
683: }
684:
685: public String toXML() throws XflowException {
686: return XflowGraphSerializer.serialize(this );
687: }
688:
689: /**
690: * Gets a node given the node name
691: * @returns Node
692: *
693: */
694: public Node getNode(String nodeName) {
695: return rootNode.getNode(nodeName);
696: }
697:
698: public Node getEndNode() {
699: return rootNode.getNode(Node.END);
700: // NB. End nodes always have the name "End"
701: }
702:
703: public Vector getNodes(String nodeType) {
704: return rootNode.getNodes(nodeType);
705: }
706:
707: public Vector getAllNodes() {
708: return rootNode.getNodes();
709: }
710: }
|