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.exe;
023:
024: import java.io.Serializable;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.Date;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032:
033: import org.jbpm.JbpmException;
034: import org.jbpm.context.exe.ContextInstance;
035: import org.jbpm.graph.def.Event;
036: import org.jbpm.graph.def.Node;
037: import org.jbpm.graph.def.ProcessDefinition;
038: import org.jbpm.graph.def.Transition;
039: import org.jbpm.graph.log.ProcessInstanceCreateLog;
040: import org.jbpm.graph.log.ProcessInstanceEndLog;
041: import org.jbpm.logging.exe.LoggingInstance;
042: import org.jbpm.logging.log.ProcessLog;
043: import org.jbpm.module.def.ModuleDefinition;
044: import org.jbpm.module.exe.ModuleInstance;
045: import org.jbpm.scheduler.SchedulerService;
046: import org.jbpm.svc.Services;
047: import org.jbpm.taskmgmt.exe.TaskMgmtInstance;
048: import org.jbpm.util.Clock;
049: import org.jbpm.util.EqualsUtil;
050:
051: /**
052: * is one execution of a {@link org.jbpm.graph.def.ProcessDefinition}.
053: * To create a new process execution of a process definition, just use the
054: * {@link #ProcessInstance(ProcessDefinition)}.
055: *
056: */
057: public class ProcessInstance implements Serializable {
058:
059: private static final long serialVersionUID = 1L;
060:
061: long id = 0;
062: int version = 0;
063: protected String key = null;
064: protected Date start = null;
065: protected Date end = null;
066: protected ProcessDefinition processDefinition = null;
067: protected Token rootToken = null;
068: protected Token super ProcessToken = null;
069: protected boolean isSuspended = false;
070: protected Map instances = null;
071: protected Map transientInstances = null;
072: protected List runtimeActions = null;
073: /** not persisted */
074: protected List cascadeProcessInstances = null;
075:
076: // constructors /////////////////////////////////////////////////////////////
077:
078: public ProcessInstance() {
079: }
080:
081: /**
082: * creates a new process instance for the given process definition,
083: * puts the root-token (=main path of execution) in the start state
084: * and executes the initial node. In case the initial node is a
085: * start-state, it will behave as a wait state.
086: * For each of the optional module definitions contained in the
087: * {@link ProcessDefinition}, the corresponding module instance
088: * will be created.
089: * @throws JbpmException if processDefinition is null.
090: */
091: public ProcessInstance(ProcessDefinition processDefinition) {
092: this (processDefinition, null, null);
093: }
094:
095: /**
096: * creates a new process instance for the given process definition,
097: * puts the root-token (=main path of execution) in the start state
098: * and executes the initial node. In case the initial node is a
099: * start-state, it will behave as a wait state.
100: * For each of the optional module definitions contained in the
101: * {@link ProcessDefinition}, the corresponding module instance
102: * will be created.
103: * @param variables will be inserted into the context variables
104: * after the context submodule has been created and before the
105: * process-start event is fired, which is also before the execution
106: * of the initial node.
107: * @throws JbpmException if processDefinition is null.
108: */
109: public ProcessInstance(ProcessDefinition processDefinition,
110: Map variables) {
111: this (processDefinition, variables, null);
112: }
113:
114: /**
115: * creates a new process instance for the given process definition,
116: * puts the root-token (=main path of execution) in the start state
117: * and executes the initial node. In case the initial node is a
118: * start-state, it will behave as a wait state.
119: * For each of the optional module definitions contained in the
120: * {@link ProcessDefinition}, the corresponding module instance
121: * will be created.
122: * @param variables will be inserted into the context variables
123: * after the context submodule has been created and before the
124: * process-start event is fired, which is also before the execution
125: * of the initial node.
126: * @throws JbpmException if processDefinition is null.
127: */
128: public ProcessInstance(ProcessDefinition processDefinition,
129: Map variables, String key) {
130: if (processDefinition == null)
131: throw new JbpmException(
132: "can't create a process instance when processDefinition is null");
133:
134: // initialize the members
135: this .processDefinition = processDefinition;
136: this .rootToken = new Token(this );
137: this .start = Clock.getCurrentTime();
138: this .key = key;
139:
140: // if this process instance is created in the context of a persistent operation
141: Services.assignId(this );
142:
143: // create the optional definitions
144: Map definitions = processDefinition.getDefinitions();
145: // if the state-definition has optional definitions
146: if (definitions != null) {
147: instances = new HashMap();
148: // loop over each optional definition
149: Iterator iter = definitions.values().iterator();
150: while (iter.hasNext()) {
151: ModuleDefinition definition = (ModuleDefinition) iter
152: .next();
153: // and create the corresponding optional instance
154: ModuleInstance instance = definition.createInstance();
155: if (instance != null) {
156: addInstance(instance);
157: }
158: }
159: }
160:
161: // add the creation log
162: rootToken.addLog(new ProcessInstanceCreateLog());
163:
164: // set the variables
165: ContextInstance contextInstance = getContextInstance();
166: if ((contextInstance != null) && (variables != null)) {
167: contextInstance.addVariables(variables);
168: }
169:
170: Node initialNode = rootToken.getNode();
171: // fire the process start event
172: if (initialNode != null) {
173: ExecutionContext executionContext = new ExecutionContext(
174: rootToken);
175: processDefinition.fireEvent(Event.EVENTTYPE_PROCESS_START,
176: executionContext);
177:
178: // execute the start node
179: initialNode.execute(executionContext);
180: }
181: }
182:
183: // optional module instances ////////////////////////////////////////////////
184:
185: /**
186: * adds the given optional moduleinstance (bidirectional).
187: */
188: public ModuleInstance addInstance(ModuleInstance moduleInstance) {
189: if (moduleInstance == null)
190: throw new IllegalArgumentException(
191: "can't add a null moduleInstance to a process instance");
192: if (instances == null)
193: instances = new HashMap();
194: instances.put(moduleInstance.getClass().getName(),
195: moduleInstance);
196: moduleInstance.setProcessInstance(this );
197: return moduleInstance;
198: }
199:
200: /**
201: * removes the given optional moduleinstance (bidirectional).
202: */
203: public ModuleInstance removeInstance(ModuleInstance moduleInstance) {
204: ModuleInstance removedModuleInstance = null;
205: if (moduleInstance == null)
206: throw new IllegalArgumentException(
207: "can't remove a null moduleInstance from a process instance");
208: if (instances != null) {
209: removedModuleInstance = (ModuleInstance) instances
210: .remove(moduleInstance.getClass().getName());
211: if (removedModuleInstance != null) {
212: moduleInstance.setProcessInstance(null);
213: }
214: }
215: return removedModuleInstance;
216: }
217:
218: /**
219: * looks up an optional module instance by its class.
220: */
221: public ModuleInstance getInstance(Class clazz) {
222: ModuleInstance moduleInstance = null;
223: if (instances != null) {
224: moduleInstance = (ModuleInstance) instances.get(clazz
225: .getName());
226: }
227:
228: if (moduleInstance == null) {
229: if (transientInstances == null)
230: transientInstances = new HashMap();
231:
232: // client requested an instance that is not in the map of instances.
233: // so we can safely assume that the client wants a transient instance
234: moduleInstance = (ModuleInstance) transientInstances
235: .get(clazz.getName());
236: if (moduleInstance == null) {
237: try {
238: moduleInstance = (ModuleInstance) clazz
239: .newInstance();
240: moduleInstance.setProcessInstance(this );
241:
242: } catch (Exception e) {
243: e.printStackTrace();
244: throw new JbpmException(
245: "couldn't instantiate transient module '"
246: + clazz.getName()
247: + "' with the default constructor");
248: }
249: transientInstances.put(clazz.getName(), moduleInstance);
250: }
251: }
252:
253: return moduleInstance;
254: }
255:
256: /**
257: * process instance extension for process variableInstances.
258: */
259: public ContextInstance getContextInstance() {
260: return (ContextInstance) getInstance(ContextInstance.class);
261: }
262:
263: /**
264: * process instance extension for managing the tasks and actors.
265: */
266: public TaskMgmtInstance getTaskMgmtInstance() {
267: return (TaskMgmtInstance) getInstance(TaskMgmtInstance.class);
268: }
269:
270: /**
271: * process instance extension for logging. Probably you don't need to access
272: * the logging instance directly. Mostly, {@link Token#addLog(ProcessLog)} is
273: * sufficient and more convenient.
274: */
275: public LoggingInstance getLoggingInstance() {
276: return (LoggingInstance) getInstance(LoggingInstance.class);
277: }
278:
279: // operations ///////////////////////////////////////////////////////////////
280:
281: /**
282: * instructs the main path of execution to continue by taking the default
283: * transition on the current node.
284: * @throws IllegalStateException if the token is not active.
285: */
286: public void signal() {
287: if (hasEnded()) {
288: throw new IllegalStateException(
289: "couldn't signal token : token has ended");
290: }
291: rootToken.signal();
292: }
293:
294: /**
295: * instructs the main path of execution to continue by taking the specified
296: * transition on the current node.
297: * @throws IllegalStateException if the token is not active.
298: */
299: public void signal(String transitionName) {
300: if (hasEnded()) {
301: throw new IllegalStateException(
302: "couldn't signal token : token has ended");
303: }
304: rootToken.signal(transitionName);
305: }
306:
307: /**
308: * instructs the main path of execution to continue by taking the specified
309: * transition on the current node.
310: * @throws IllegalStateException if the token is not active.
311: */
312: public void signal(Transition transition) {
313: if (hasEnded()) {
314: throw new IllegalStateException(
315: "couldn't signal token : token has ended");
316: }
317: rootToken.signal(transition);
318: }
319:
320: /**
321: * ends (=cancels) this process instance and all the tokens in it.
322: */
323: public void end() {
324: // end the main path of execution
325: rootToken.end();
326:
327: if (end == null) {
328: // mark this process instance as ended
329: end = Clock.getCurrentTime();
330:
331: // fire the process-end event
332: ExecutionContext executionContext = new ExecutionContext(
333: rootToken);
334: processDefinition.fireEvent(Event.EVENTTYPE_PROCESS_END,
335: executionContext);
336:
337: // add the process instance end log
338: rootToken.addLog(new ProcessInstanceEndLog());
339:
340: // check if this process was started as a subprocess of a super process
341: if (super ProcessToken != null) {
342: addCascadeProcessInstance(super ProcessToken
343: .getProcessInstance());
344:
345: ExecutionContext super ExecutionContext = new ExecutionContext(
346: super ProcessToken);
347: super ExecutionContext.setSubProcessInstance(this );
348: super ProcessToken.signal(super ExecutionContext);
349: }
350:
351: // make sure all the timers for this process instance are cancelled when the process end updates get saved in the database.
352: // TODO route this directly through the jobSession. just like the suspend and resume.
353: // NOTE Only timers should be deleted, messages-type of jobs should be kept.
354: SchedulerService schedulerService = (SchedulerService) Services
355: .getCurrentService(Services.SERVICENAME_SCHEDULER,
356: false);
357: if (schedulerService != null)
358: schedulerService.deleteTimersByProcessInstance(this );
359: }
360: }
361:
362: /**
363: * suspends this execution. This will make sure that tasks, timers and
364: * messages related to this process instance will not show up in database
365: * queries.
366: * @see #resume()
367: */
368: public void suspend() {
369: isSuspended = true;
370: rootToken.suspend();
371: }
372:
373: /**
374: * resumes a suspended execution. All timers that have been suspended might fire
375: * if the duedate has been passed. If an admin resumes a process instance, the option
376: * should be offered to update, remove and create the timers and messages related to
377: * this process instance.
378: * @see #suspend()
379: */
380: public void resume() {
381: isSuspended = false;
382: rootToken.resume();
383: }
384:
385: // runtime actions //////////////////////////////////////////////////////////
386:
387: /**
388: * adds an action to be executed upon a process event in the future.
389: */
390: public RuntimeAction addRuntimeAction(RuntimeAction runtimeAction) {
391: if (runtimeAction == null)
392: throw new IllegalArgumentException(
393: "can't add a null runtimeAction to a process instance");
394: if (runtimeActions == null)
395: runtimeActions = new ArrayList();
396: runtimeActions.add(runtimeAction);
397: runtimeAction.processInstance = this ;
398: return runtimeAction;
399: }
400:
401: /**
402: * removes a runtime action.
403: */
404: public RuntimeAction removeRuntimeAction(RuntimeAction runtimeAction) {
405: RuntimeAction removedRuntimeAction = null;
406: if (runtimeAction == null)
407: throw new IllegalArgumentException(
408: "can't remove a null runtimeAction from an process instance");
409: if (runtimeActions != null) {
410: if (runtimeActions.remove(runtimeAction)) {
411: removedRuntimeAction = runtimeAction;
412: runtimeAction.processInstance = null;
413: }
414: }
415: return removedRuntimeAction;
416: }
417:
418: /**
419: * is the list of all runtime actions.
420: */
421: public List getRuntimeActions() {
422: return runtimeActions;
423: }
424:
425: // various information retrieval methods ////////////////////////////////////
426:
427: /**
428: * tells if this process instance is still active or not.
429: */
430: public boolean hasEnded() {
431: return (end != null);
432: }
433:
434: /**
435: * calculates if this process instance has still options to continue.
436: */
437: public boolean isTerminatedImplicitly() {
438: boolean isTerminatedImplicitly = true;
439: if (end == null) {
440: isTerminatedImplicitly = rootToken.isTerminatedImplicitly();
441: }
442: return isTerminatedImplicitly;
443: }
444:
445: /**
446: * looks up the token in the tree, specified by the slash-separated token path.
447: * @param tokenPath is a slash-separated name that specifies a token in the tree.
448: * @return the specified token or null if the token is not found.
449: */
450: public Token findToken(String tokenPath) {
451: return (rootToken != null ? rootToken.findToken(tokenPath)
452: : null);
453: }
454:
455: /**
456: * collects all instances for this process instance.
457: */
458: public List findAllTokens() {
459: List tokens = new ArrayList();
460: tokens.add(rootToken);
461: rootToken.collectChildrenRecursively(tokens);
462: return tokens;
463: }
464:
465: void addCascadeProcessInstance(
466: ProcessInstance cascadeProcessInstance) {
467: if (cascadeProcessInstances == null) {
468: cascadeProcessInstances = new ArrayList();
469: }
470: cascadeProcessInstances.add(cascadeProcessInstance);
471: }
472:
473: public Collection removeCascadeProcessInstances() {
474: Collection removed = cascadeProcessInstances;
475: cascadeProcessInstances = null;
476: return removed;
477: }
478:
479: // equals ///////////////////////////////////////////////////////////////////
480: // hack to support comparing hibernate proxies against the real objects
481: // since this always falls back to ==, we don't need to overwrite the hashcode
482: public boolean equals(Object o) {
483: return EqualsUtil.equals(this , o);
484: }
485:
486: // getters and setters //////////////////////////////////////////////////////
487:
488: public long getId() {
489: return id;
490: }
491:
492: public Token getRootToken() {
493: return rootToken;
494: }
495:
496: public Date getStart() {
497: return start;
498: }
499:
500: public Date getEnd() {
501: return end;
502: }
503:
504: public Map getInstances() {
505: return instances;
506: }
507:
508: public ProcessDefinition getProcessDefinition() {
509: return processDefinition;
510: }
511:
512: public Token getSuperProcessToken() {
513: return super ProcessToken;
514: }
515:
516: public void setSuperProcessToken(Token super ProcessToken) {
517: this .super ProcessToken = super ProcessToken;
518: }
519:
520: public boolean isSuspended() {
521: return isSuspended;
522: }
523:
524: public int getVersion() {
525: return version;
526: }
527:
528: public void setVersion(int version) {
529: this .version = version;
530: }
531:
532: public void setEnd(Date end) {
533: this .end = end;
534: }
535:
536: public void setProcessDefinition(ProcessDefinition processDefinition) {
537: this .processDefinition = processDefinition;
538: }
539:
540: public void setRootToken(Token rootToken) {
541: this .rootToken = rootToken;
542: }
543:
544: public void setStart(Date start) {
545: this .start = start;
546: }
547:
548: /** a unique business key */
549: public String getKey() {
550: return key;
551: }
552:
553: /** set the unique business key */
554: public void setKey(String key) {
555: this .key = key;
556: }
557:
558: // private static Log log = LogFactory.getLog(ProcessInstance.class);
559: }
|