001: /*
002: * JBoss, Home of Professional Open Source
003: * Copyright 2005, JBoss Inc., and individual contributors as indicated
004: * by the @authors tag. See the copyright.txt in the distribution for a
005: * full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jbpm.graph.def;
023:
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.io.Reader;
027: import java.io.StringReader;
028: import java.util.ArrayList;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Properties;
034: import java.util.StringTokenizer;
035: import java.util.zip.ZipInputStream;
036:
037: import org.jbpm.JbpmConfiguration;
038: import org.jbpm.JbpmException;
039: import org.jbpm.context.def.ContextDefinition;
040: import org.jbpm.file.def.FileDefinition;
041: import org.jbpm.graph.exe.ProcessInstance;
042: import org.jbpm.graph.node.ProcessFactory;
043: import org.jbpm.graph.node.StartState;
044: import org.jbpm.jpdl.par.ProcessArchive;
045: import org.jbpm.jpdl.xml.JpdlXmlReader;
046: import org.jbpm.module.def.ModuleDefinition;
047: import org.jbpm.taskmgmt.def.TaskMgmtDefinition;
048: import org.jbpm.util.ClassLoaderUtil;
049: import org.xml.sax.InputSource;
050:
051: public class ProcessDefinition extends GraphElement implements
052: NodeCollection {
053:
054: private static final long serialVersionUID = 1L;
055:
056: protected int version = -1;
057: protected boolean isTerminationImplicit = false;
058: protected Node startState = null;
059: protected List nodes = null;
060: transient Map nodesMap = null;
061: protected Map actions = null;
062: protected Map definitions = null;
063:
064: // event types //////////////////////////////////////////////////////////////
065:
066: public static final String[] supportedEventTypes = new String[] {
067: Event.EVENTTYPE_PROCESS_START, Event.EVENTTYPE_PROCESS_END,
068: Event.EVENTTYPE_NODE_ENTER, Event.EVENTTYPE_NODE_LEAVE,
069: Event.EVENTTYPE_TASK_CREATE, Event.EVENTTYPE_TASK_ASSIGN,
070: Event.EVENTTYPE_TASK_START, Event.EVENTTYPE_TASK_END,
071: Event.EVENTTYPE_TRANSITION, Event.EVENTTYPE_BEFORE_SIGNAL,
072: Event.EVENTTYPE_AFTER_SIGNAL,
073: Event.EVENTTYPE_SUPERSTATE_ENTER,
074: Event.EVENTTYPE_SUPERSTATE_LEAVE,
075: Event.EVENTTYPE_SUBPROCESS_CREATED,
076: Event.EVENTTYPE_SUBPROCESS_END, Event.EVENTTYPE_TIMER };
077:
078: public String[] getSupportedEventTypes() {
079: return supportedEventTypes;
080: }
081:
082: // constructors /////////////////////////////////////////////////////////////
083:
084: public ProcessDefinition() {
085: this .processDefinition = this ;
086: }
087:
088: public static ProcessDefinition createNewProcessDefinition() {
089: ProcessDefinition processDefinition = new ProcessDefinition();
090:
091: // now add all the default modules that are configured in the file jbpm.default.modules
092: String resource = JbpmConfiguration.Configs
093: .getString("resource.default.modules");
094: Properties defaultModulesProperties = ClassLoaderUtil
095: .getProperties(resource);
096: Iterator iter = defaultModulesProperties.keySet().iterator();
097: while (iter.hasNext()) {
098: String moduleClassName = (String) iter.next();
099: try {
100: ModuleDefinition moduleDefinition = (ModuleDefinition) ClassLoaderUtil
101: .loadClass(moduleClassName).newInstance();
102: processDefinition.addDefinition(moduleDefinition);
103:
104: } catch (Exception e) {
105: e.printStackTrace();
106: throw new JbpmException(
107: "couldn't instantiate default module '"
108: + moduleClassName + "'", e);
109: }
110: }
111: return processDefinition;
112: }
113:
114: public ProcessDefinition(String name) {
115: this .processDefinition = this ;
116: this .name = name;
117: }
118:
119: public ProcessDefinition(String[] nodes, String[] transitions) {
120: this .processDefinition = this ;
121: ProcessFactory.addNodesAndTransitions(this , nodes, transitions);
122: }
123:
124: public ProcessInstance createProcessInstance() {
125: return new ProcessInstance(this );
126: }
127:
128: public ProcessInstance createProcessInstance(Map variables) {
129: return new ProcessInstance(this , variables, null);
130: }
131:
132: public ProcessInstance createProcessInstance(Map variables,
133: String businessKey) {
134: return new ProcessInstance(this , variables, businessKey);
135: }
136:
137: public void setProcessDefinition(ProcessDefinition processDefinition) {
138: if (!this .equals(processDefinition)) {
139: throw new JbpmException(
140: "can't set the process-definition-property of a process defition to something else then a self-reference");
141: }
142: }
143:
144: // parsing //////////////////////////////////////////////////////////////////
145:
146: /**
147: * parse a process definition from an xml string.
148: * @throws org.jbpm.jpdl.JpdlException if parsing reported an error.
149: */
150: public static ProcessDefinition parseXmlString(String xml) {
151: StringReader stringReader = new StringReader(xml);
152: JpdlXmlReader jpdlReader = new JpdlXmlReader(new InputSource(
153: stringReader));
154: return jpdlReader.readProcessDefinition();
155: }
156:
157: /**
158: * parse a process definition from an xml resource file.
159: * @throws org.jbpm.jpdl.JpdlException if parsing reported an error.
160: */
161: public static ProcessDefinition parseXmlResource(String xmlResource) {
162: InputStream resourceStream = ClassLoaderUtil
163: .getStream(xmlResource);
164: try {
165: return parseXmlInputStream(resourceStream);
166: } finally {
167: if (resourceStream != null) {
168: try {
169: resourceStream.close();
170: } catch (IOException e) {
171: }
172: }
173: }
174: }
175:
176: /**
177: * parse a process definition from an xml input stream.
178: * @throws org.jbpm.jpdl.JpdlException if parsing reported an error.
179: */
180: public static ProcessDefinition parseXmlInputStream(
181: InputStream inputStream) {
182: JpdlXmlReader jpdlReader = new JpdlXmlReader(new InputSource(
183: inputStream));
184: return jpdlReader.readProcessDefinition();
185: }
186:
187: /**
188: * parse a process definition from an xml reader.
189: * @throws org.jbpm.jpdl.JpdlException if parsing reported an error.
190: */
191: public static ProcessDefinition parseXmlReader(Reader reader) {
192: JpdlXmlReader jpdlReader = new JpdlXmlReader(new InputSource(
193: reader));
194: return jpdlReader.readProcessDefinition();
195: }
196:
197: /**
198: * parse a process definition from a process archive zip-stream.
199: * @throws org.jbpm.jpdl.JpdlException if parsing reported an error.
200: */
201: public static ProcessDefinition parseParZipInputStream(
202: ZipInputStream zipInputStream) {
203: try {
204: return new ProcessArchive(zipInputStream)
205: .parseProcessDefinition();
206: } catch (IOException e) {
207: throw new JbpmException(
208: "couldn't parse process zip file zipInputStream", e);
209: }
210: }
211:
212: /**
213: * parse a process definition from a process archive resource.
214: * @throws org.jbpm.jpdl.JpdlException if parsing reported an error.
215: */
216: public static ProcessDefinition parseParResource(String parResource) {
217: return parseParZipInputStream(new ZipInputStream(
218: ClassLoaderUtil.getStream(parResource)));
219: }
220:
221: // nodes ////////////////////////////////////////////////////////////////////
222:
223: // javadoc description in NodeCollection
224: public List getNodes() {
225: return nodes;
226: }
227:
228: // javadoc description in NodeCollection
229: public Map getNodesMap() {
230: if (nodesMap == null) {
231: nodesMap = new HashMap();
232: if (nodes != null) {
233: Iterator iter = nodes.iterator();
234: while (iter.hasNext()) {
235: Node node = (Node) iter.next();
236: nodesMap.put(node.getName(), node);
237: }
238: }
239: }
240: return nodesMap;
241: }
242:
243: // javadoc description in NodeCollection
244: public Node getNode(String name) {
245: if (nodes == null)
246: return null;
247: return (Node) getNodesMap().get(name);
248: }
249:
250: // javadoc description in NodeCollection
251: public boolean hasNode(String name) {
252: if (nodes == null)
253: return false;
254: return getNodesMap().containsKey(name);
255: }
256:
257: // javadoc description in NodeCollection
258: public Node addNode(Node node) {
259: if (node == null)
260: throw new IllegalArgumentException(
261: "can't add a null node to a processdefinition");
262: if (nodes == null)
263: nodes = new ArrayList();
264: nodes.add(node);
265: node.processDefinition = this ;
266: nodesMap = null;
267:
268: if ((node instanceof StartState) && (this .startState == null)) {
269: this .startState = node;
270: }
271: return node;
272: }
273:
274: // javadoc description in NodeCollection
275: public Node removeNode(Node node) {
276: Node removedNode = null;
277: if (node == null)
278: throw new IllegalArgumentException(
279: "can't remove a null node from a process definition");
280: if (nodes != null) {
281: if (nodes.remove(node)) {
282: removedNode = node;
283: removedNode.processDefinition = null;
284: nodesMap = null;
285: }
286: }
287:
288: if (startState == removedNode) {
289: startState = null;
290: }
291: return removedNode;
292: }
293:
294: // javadoc description in NodeCollection
295: public void reorderNode(int oldIndex, int newIndex) {
296: if ((nodes != null) && (Math.min(oldIndex, newIndex) >= 0)
297: && (Math.max(oldIndex, newIndex) < nodes.size())) {
298: Object o = nodes.remove(oldIndex);
299: nodes.add(newIndex, o);
300: } else {
301: throw new IndexOutOfBoundsException(
302: "couldn't reorder element from index '" + oldIndex
303: + "' to index '" + newIndex
304: + "' in nodeList '" + nodes + "'");
305: }
306: }
307:
308: // javadoc description in NodeCollection
309: public String generateNodeName() {
310: return generateNodeName(nodes);
311: }
312:
313: // javadoc description in NodeCollection
314: public Node findNode(String hierarchicalName) {
315: return findNode(this , hierarchicalName);
316: }
317:
318: public static String generateNodeName(List nodes) {
319: String name = null;
320: if (nodes == null) {
321: name = "1";
322: } else {
323: int n = 1;
324: while (containsName(nodes, Integer.toString(n)))
325: n++;
326: name = Integer.toString(n);
327: }
328: return name;
329: }
330:
331: static boolean containsName(List nodes, String name) {
332: Iterator iter = nodes.iterator();
333: while (iter.hasNext()) {
334: Node node = (Node) iter.next();
335: if (name.equals(node.getName())) {
336: return true;
337: }
338: }
339: return false;
340: }
341:
342: public static Node findNode(NodeCollection nodeCollection,
343: String hierarchicalName) {
344: Node node = null;
345: if ((hierarchicalName != null)
346: && (!"".equals(hierarchicalName.trim()))) {
347:
348: if ((hierarchicalName.startsWith("/"))
349: && (nodeCollection instanceof SuperState)) {
350: nodeCollection = ((SuperState) nodeCollection)
351: .getProcessDefinition();
352: }
353:
354: StringTokenizer tokenizer = new StringTokenizer(
355: hierarchicalName, "/");
356: while (tokenizer.hasMoreElements()) {
357: String namePart = tokenizer.nextToken();
358: if ("..".equals(namePart)) {
359: if (nodeCollection instanceof ProcessDefinition) {
360: throw new JbpmException(
361: "couldn't find node '"
362: + hierarchicalName
363: + "' because of a '..' on the process definition.");
364: }
365: nodeCollection = (NodeCollection) ((GraphElement) nodeCollection)
366: .getParent();
367: } else if (tokenizer.hasMoreElements()) {
368: nodeCollection = (NodeCollection) nodeCollection
369: .getNode(namePart);
370: } else {
371: node = nodeCollection.getNode(namePart);
372: }
373: }
374: }
375: return node;
376: }
377:
378: public void setStartState(StartState startState) {
379: if ((this .startState != startState)
380: && (this .startState != null)) {
381: removeNode(this .startState);
382: }
383: this .startState = startState;
384: if (startState != null) {
385: addNode(startState);
386: }
387: }
388:
389: public GraphElement getParent() {
390: return null;
391: }
392:
393: // actions //////////////////////////////////////////////////////////////////
394:
395: /**
396: * creates a bidirectional relation between this process definition and the given action.
397: * @throws IllegalArgumentException if action is null or if action.getName() is null.
398: */
399: public Action addAction(Action action) {
400: if (action == null)
401: throw new IllegalArgumentException(
402: "can't add a null action to an process definition");
403: if (action.getName() == null)
404: throw new IllegalArgumentException(
405: "can't add an unnamed action to an process definition");
406: if (actions == null)
407: actions = new HashMap();
408: actions.put(action.getName(), action);
409: action.processDefinition = this ;
410: return action;
411: }
412:
413: /**
414: * removes the bidirectional relation between this process definition and the given action.
415: * @throws IllegalArgumentException if action is null or if the action was not present in the actions of this process definition.
416: */
417: public void removeAction(Action action) {
418: if (action == null)
419: throw new IllegalArgumentException(
420: "can't remove a null action from an process definition");
421: if (actions != null) {
422: if (!actions.containsValue(action)) {
423: throw new IllegalArgumentException(
424: "can't remove an action that is not part of this process definition");
425: }
426: actions.remove(action.getName());
427: action.processDefinition = null;
428: }
429: }
430:
431: public Action getAction(String name) {
432: if (actions == null)
433: return null;
434: return (Action) actions.get(name);
435: }
436:
437: public Map getActions() {
438: return actions;
439: }
440:
441: public boolean hasActions() {
442: return ((actions != null) && (actions.size() > 0));
443: }
444:
445: // module definitions ///////////////////////////////////////////////////////
446:
447: public Object createInstance() {
448: return new ProcessInstance(this );
449: }
450:
451: public ModuleDefinition addDefinition(
452: ModuleDefinition moduleDefinition) {
453: if (moduleDefinition == null)
454: throw new IllegalArgumentException(
455: "can't add a null moduleDefinition to a process definition");
456: if (definitions == null)
457: definitions = new HashMap();
458: definitions.put(moduleDefinition.getClass().getName(),
459: moduleDefinition);
460: moduleDefinition.setProcessDefinition(this );
461: return moduleDefinition;
462: }
463:
464: public ModuleDefinition removeDefinition(
465: ModuleDefinition moduleDefinition) {
466: ModuleDefinition removedDefinition = null;
467: if (moduleDefinition == null)
468: throw new IllegalArgumentException(
469: "can't remove a null moduleDefinition from a process definition");
470: if (definitions != null) {
471: removedDefinition = (ModuleDefinition) definitions
472: .remove(moduleDefinition.getClass().getName());
473: if (removedDefinition != null) {
474: moduleDefinition.setProcessDefinition(null);
475: }
476: }
477: return removedDefinition;
478: }
479:
480: public ModuleDefinition getDefinition(Class clazz) {
481: ModuleDefinition moduleDefinition = null;
482: if (definitions != null) {
483: moduleDefinition = (ModuleDefinition) definitions.get(clazz
484: .getName());
485: }
486: return moduleDefinition;
487: }
488:
489: public ContextDefinition getContextDefinition() {
490: return (ContextDefinition) getDefinition(ContextDefinition.class);
491: }
492:
493: public FileDefinition getFileDefinition() {
494: return (FileDefinition) getDefinition(FileDefinition.class);
495: }
496:
497: public TaskMgmtDefinition getTaskMgmtDefinition() {
498: return (TaskMgmtDefinition) getDefinition(TaskMgmtDefinition.class);
499: }
500:
501: public Map getDefinitions() {
502: return definitions;
503: }
504:
505: public void setDefinitions(Map definitions) {
506: this .definitions = definitions;
507: }
508:
509: // getters and setters //////////////////////////////////////////////////////
510:
511: public int getVersion() {
512: return version;
513: }
514:
515: public void setVersion(int version) {
516: this .version = version;
517: }
518:
519: public Node getStartState() {
520: return startState;
521: }
522:
523: public void setStartState(Node startState) {
524: this .startState = startState;
525: }
526:
527: public boolean isTerminationImplicit() {
528: return isTerminationImplicit;
529: }
530:
531: public void setTerminationImplicit(boolean isTerminationImplicit) {
532: this.isTerminationImplicit = isTerminationImplicit;
533: }
534: }
|