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.Serializable;
025: import java.util.ArrayList;
026: import java.util.Date;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.jbpm.JbpmException;
035: import org.jbpm.context.exe.ContextInstance;
036: import org.jbpm.graph.exe.ExecutionContext;
037: import org.jbpm.graph.exe.RuntimeAction;
038: import org.jbpm.graph.exe.Token;
039: import org.jbpm.graph.log.ActionLog;
040: import org.jbpm.instantiation.UserCodeInterceptor;
041: import org.jbpm.instantiation.UserCodeInterceptorConfig;
042: import org.jbpm.job.ExecuteActionJob;
043: import org.jbpm.msg.MessageService;
044: import org.jbpm.svc.Services;
045: import org.jbpm.taskmgmt.def.AssignmentHandler;
046: import org.jbpm.taskmgmt.def.TaskControllerHandler;
047: import org.jbpm.taskmgmt.exe.TaskInstance;
048: import org.jbpm.util.EqualsUtil;
049:
050: public abstract class GraphElement implements Serializable {
051:
052: private static final long serialVersionUID = 1L;
053:
054: long id = 0;
055: protected String name = null;
056: protected String description = null;
057: protected ProcessDefinition processDefinition = null;
058: protected Map events = null;
059: protected List exceptionHandlers = null;
060:
061: public GraphElement() {
062: }
063:
064: public GraphElement(String name) {
065: setName(name);
066: }
067:
068: // events ///////////////////////////////////////////////////////////////////
069:
070: /**
071: * indicative set of event types supported by this graph element.
072: * this is currently only used by the process designer to know which
073: * event types to show on a given graph element. in process definitions
074: * and at runtime, there are no contstraints on the event-types.
075: */
076: public abstract String[] getSupportedEventTypes();
077:
078: /**
079: * gets the events, keyd by eventType (java.lang.String).
080: */
081: public Map getEvents() {
082: return events;
083: }
084:
085: public boolean hasEvents() {
086: return ((events != null) && (events.size() > 0));
087: }
088:
089: public Event getEvent(String eventType) {
090: Event event = null;
091: if (events != null) {
092: event = (Event) events.get(eventType);
093: }
094: return event;
095: }
096:
097: public boolean hasEvent(String eventType) {
098: boolean hasEvent = false;
099: if (events != null) {
100: hasEvent = events.containsKey(eventType);
101: }
102: return hasEvent;
103: }
104:
105: public Event addEvent(Event event) {
106: if (event == null)
107: throw new IllegalArgumentException(
108: "can't add a null event to a graph element");
109: if (event.getEventType() == null)
110: throw new IllegalArgumentException(
111: "can't add an event without an eventType to a graph element");
112: if (events == null)
113: events = new HashMap();
114: events.put(event.getEventType(), event);
115: event.graphElement = this ;
116: return event;
117: }
118:
119: public Event removeEvent(Event event) {
120: Event removedEvent = null;
121: if (event == null)
122: throw new IllegalArgumentException(
123: "can't remove a null event from a graph element");
124: if (event.getEventType() == null)
125: throw new IllegalArgumentException(
126: "can't remove an event without an eventType from a graph element");
127: if (events != null) {
128: removedEvent = (Event) events.remove(event.getEventType());
129: if (removedEvent != null) {
130: event.graphElement = null;
131: }
132: }
133: return removedEvent;
134: }
135:
136: // exception handlers ///////////////////////////////////////////////////////
137:
138: /**
139: * is the list of exception handlers associated to this graph element.
140: */
141: public List getExceptionHandlers() {
142: return exceptionHandlers;
143: }
144:
145: public ExceptionHandler addExceptionHandler(
146: ExceptionHandler exceptionHandler) {
147: if (exceptionHandler == null)
148: throw new IllegalArgumentException(
149: "can't add a null exceptionHandler to a graph element");
150: if (exceptionHandlers == null)
151: exceptionHandlers = new ArrayList();
152: exceptionHandlers.add(exceptionHandler);
153: exceptionHandler.graphElement = this ;
154: return exceptionHandler;
155: }
156:
157: public void removeExceptionHandler(ExceptionHandler exceptionHandler) {
158: if (exceptionHandler == null)
159: throw new IllegalArgumentException(
160: "can't remove a null exceptionHandler from an graph element");
161: if (exceptionHandlers != null) {
162: if (exceptionHandlers.remove(exceptionHandler)) {
163: exceptionHandler.graphElement = null;
164: }
165: }
166: }
167:
168: public void reorderExceptionHandler(int oldIndex, int newIndex) {
169: if ((exceptionHandlers != null)
170: && (Math.min(oldIndex, newIndex) >= 0)
171: && (Math.max(oldIndex, newIndex) < exceptionHandlers
172: .size())) {
173: Object o = exceptionHandlers.remove(oldIndex);
174: exceptionHandlers.add(newIndex, o);
175: } else {
176: throw new IndexOutOfBoundsException(
177: "couldn't reorder element from index '" + oldIndex
178: + "' to index '" + newIndex
179: + "' in exceptionHandler-list '"
180: + exceptionHandlers + "'");
181: }
182: }
183:
184: // event handling ///////////////////////////////////////////////////////////
185:
186: public void fireEvent(String eventType,
187: ExecutionContext executionContext) {
188: Token token = executionContext.getToken();
189:
190: log.debug("event '" + eventType + "' on '" + this + "' for '"
191: + token + "'");
192:
193: try {
194: executionContext.setEventSource(this );
195: fireAndPropagateEvent(eventType, executionContext);
196: } finally {
197: executionContext.setEventSource(null);
198: }
199: }
200:
201: public void fireAndPropagateEvent(String eventType,
202: ExecutionContext executionContext) {
203: // calculate if the event was fired on this element or if it was a propagated event
204: boolean isPropagated = !(this .equals(executionContext
205: .getEventSource()));
206:
207: // execute static actions
208: Event event = getEvent(eventType);
209: if (event != null) {
210: // update the context
211: executionContext.setEvent(event);
212: // execute the static actions specified in the process definition
213: executeActions(event.getActions(), executionContext,
214: isPropagated);
215: }
216:
217: // execute the runtime actions
218: List runtimeActions = getRuntimeActionsForEvent(
219: executionContext, eventType);
220: executeActions(runtimeActions, executionContext, isPropagated);
221:
222: // remove the event from the context
223: executionContext.setEvent(null);
224:
225: // propagate the event to the parent element
226: GraphElement parent = getParent();
227: if (parent != null) {
228: parent.fireAndPropagateEvent(eventType, executionContext);
229: }
230: }
231:
232: void executeActions(List actions,
233: ExecutionContext executionContext, boolean isPropagated) {
234: if (actions != null) {
235: Iterator iter = actions.iterator();
236: while (iter.hasNext()) {
237: Action action = (Action) iter.next();
238: if (action.acceptsPropagatedEvents() || (!isPropagated)) {
239: if (action.isAsync()) {
240: ExecuteActionJob job = createAsyncActionExecutionJob(
241: executionContext.getToken(), action);
242: MessageService messageService = (MessageService) Services
243: .getCurrentService(Services.SERVICENAME_MESSAGE);
244: messageService.send(job);
245: } else {
246: executeAction(action, executionContext);
247: }
248: }
249: }
250: }
251: }
252:
253: protected ExecuteActionJob createAsyncActionExecutionJob(
254: Token token, Action action) {
255: ExecuteActionJob job = new ExecuteActionJob(token);
256: job.setAction(action);
257: job.setDueDate(new Date());
258: job.setExclusive(action.isAsyncExclusive());
259: return job;
260: }
261:
262: public void executeAction(Action action,
263: ExecutionContext executionContext) {
264: Token token = executionContext.getToken();
265:
266: // create action log
267: ActionLog actionLog = new ActionLog(action);
268: token.startCompositeLog(actionLog);
269:
270: // if this is an action being executed in an event,
271: // the token needs to be locked. if this is an action
272: // being executed as the node behaviour or if the token
273: // is already locked, the token doesn't need to be locked.
274: boolean actionMustBeLocked = (executionContext.getEvent() != null)
275: && (!token.isLocked());
276:
277: try {
278: // update the execution context
279: executionContext.setAction(action);
280:
281: // execute the action
282: log.debug("executing action '" + action + "'");
283: String lockOwnerId = "token[" + token.getId() + "]";
284: try {
285: if (actionMustBeLocked) {
286: token.lock(lockOwnerId);
287: }
288:
289: if (UserCodeInterceptorConfig.userCodeInterceptor != null) {
290: UserCodeInterceptorConfig.userCodeInterceptor
291: .executeAction(action, executionContext);
292: } else {
293: action.execute(executionContext);
294: }
295:
296: } finally {
297: if (actionMustBeLocked) {
298: token.unlock(lockOwnerId);
299: }
300: }
301:
302: } catch (Exception exception) {
303: // NOTE that Error's are not caught because that might halt the JVM and mask the original Error.
304: log.error("action threw exception: "
305: + exception.getMessage(), exception);
306:
307: // log the action exception
308: actionLog.setException(exception);
309:
310: // if an exception handler is available
311: raiseException(exception, executionContext);
312:
313: } finally {
314: executionContext.setAction(null);
315: token.endCompositeLog();
316: }
317: }
318:
319: List getRuntimeActionsForEvent(ExecutionContext executionContext,
320: String eventType) {
321: List runtimeActionsForEvent = null;
322: List runtimeActions = executionContext.getProcessInstance()
323: .getRuntimeActions();
324: if (runtimeActions != null) {
325: Iterator iter = runtimeActions.iterator();
326: while (iter.hasNext()) {
327: RuntimeAction runtimeAction = (RuntimeAction) iter
328: .next();
329: // if the runtime-action action is registered on this element and this eventType
330: if ((this .equals(runtimeAction.getGraphElement()))
331: && (eventType.equals(runtimeAction
332: .getEventType()))) {
333: // ... add its action to the list of runtime actions
334: if (runtimeActionsForEvent == null)
335: runtimeActionsForEvent = new ArrayList();
336: runtimeActionsForEvent.add(runtimeAction
337: .getAction());
338: }
339: }
340: }
341: return runtimeActionsForEvent;
342: }
343:
344: /*
345: // the next instruction merges the actions specified in the process definition with the runtime actions
346: List actions = event.collectActions(executionContext);
347:
348: // loop over all actions of this event
349: Iterator iter = actions.iterator();
350: while (iter.hasNext()) {
351: Action action = (Action) iter.next();
352: executionContext.setAction(action);
353:
354: if ( (!isPropagated)
355: || (action.acceptsPropagatedEvents() ) ) {
356:
357: // create action log
358: ActionLog actionLog = new ActionLog(action);
359: executionContext.getToken().startCompositeLog(actionLog);
360:
361: try {
362: // execute the action
363: action.execute(executionContext);
364:
365: } catch (Exception exception) {
366: // NOTE that Error's are not caught because that might halt the JVM and mask the original Error.
367: Event.log.error("action threw exception: "+exception.getMessage(), exception);
368:
369: // log the action exception
370: actionLog.setException(exception);
371:
372: // if an exception handler is available
373: event.graphElement.raiseException(exception, executionContext);
374: } finally {
375: executionContext.getToken().endCompositeLog();
376: }
377: }
378: }
379: }
380: */
381:
382: /**
383: * throws an ActionException if no applicable exception handler is found.
384: * An ExceptionHandler is searched for in this graph element and then recursively up the
385: * parent hierarchy.
386: * If an exception handler is found, it is applied. If the exception handler does not
387: * throw an exception, the exception is considered handled. Otherwise the search for
388: * an applicable exception handler continues where it left of with the newly thrown
389: * exception.
390: */
391: public void raiseException(Throwable exception,
392: ExecutionContext executionContext)
393: throws DelegationException {
394: boolean isHandled = false;
395: if (exceptionHandlers != null) {
396: try {
397: ExceptionHandler exceptionHandler = findExceptionHandler(exception);
398: if (exceptionHandler != null) {
399: executionContext.setException(exception);
400: exceptionHandler.handleException(this ,
401: executionContext);
402: isHandled = true;
403: }
404: } catch (Exception e) {
405: // NOTE that Error's are not caught because that might halt the JVM and mask the original Error.
406: exception = e;
407: }
408: }
409:
410: if (!isHandled) {
411: GraphElement parent = getParent();
412: // if this graph element has a parent
413: if ((parent != null) && (parent != this )) {
414: // action to the parent
415: parent.raiseException(exception, executionContext);
416: } else {
417: // rollback the actions
418: // rollbackActions(executionContext);
419:
420: // if there is no parent we need to throw an action exception to the client
421: if (exception instanceof JbpmException) {
422: throw (JbpmException) exception;
423: } else {
424: throw new DelegationException(exception,
425: executionContext);
426: }
427: }
428: }
429: }
430:
431: protected ExceptionHandler findExceptionHandler(Throwable exception) {
432: ExceptionHandler exceptionHandler = null;
433:
434: if (exceptionHandlers != null) {
435: Iterator iter = exceptionHandlers.iterator();
436: while (iter.hasNext() && (exceptionHandler == null)) {
437: ExceptionHandler candidate = (ExceptionHandler) iter
438: .next();
439: if (candidate.matches(exception)) {
440: exceptionHandler = candidate;
441: }
442: }
443: }
444:
445: return exceptionHandler;
446: }
447:
448: public GraphElement getParent() {
449: return processDefinition;
450: }
451:
452: /**
453: * @return all the parents of this graph element ordered by age.
454: */
455: public List getParents() {
456: List parents = new ArrayList();
457: GraphElement parent = getParent();
458: if (parent != null) {
459: parent.addParentChain(parents);
460: }
461: return parents;
462: }
463:
464: /**
465: * @return this graph element plus all the parents ordered by age.
466: */
467: public List getParentChain() {
468: List parents = new ArrayList();
469: this .addParentChain(parents);
470: return parents;
471: }
472:
473: void addParentChain(List parentChain) {
474: parentChain.add(this );
475: GraphElement parent = getParent();
476: if (parent != null) {
477: parent.addParentChain(parentChain);
478: }
479: }
480:
481: public String toString() {
482: String className = getClass().getName();
483: className = className.substring(className.lastIndexOf('.') + 1);
484: if (name != null) {
485: className = className + "(" + name + ")";
486: } else {
487: className = className
488: + "("
489: + Integer
490: .toHexString(System.identityHashCode(this ))
491: + ")";
492: }
493: return className;
494: }
495:
496: // equals ///////////////////////////////////////////////////////////////////
497: // hack to support comparing hibernate proxies against the real objects
498: // since this always falls back to ==, we don't need to overwrite the hashcode
499: public boolean equals(Object o) {
500: return EqualsUtil.equals(this , o);
501: }
502:
503: // getters and setters //////////////////////////////////////////////////////
504:
505: public long getId() {
506: return id;
507: }
508:
509: public String getName() {
510: return name;
511: }
512:
513: public void setName(String name) {
514: this .name = name;
515: }
516:
517: public String getDescription() {
518: return description;
519: }
520:
521: public void setDescription(String description) {
522: this .description = description;
523: }
524:
525: public ProcessDefinition getProcessDefinition() {
526: return processDefinition;
527: }
528:
529: public void setProcessDefinition(ProcessDefinition processDefinition) {
530: this .processDefinition = processDefinition;
531: }
532:
533: // logger ///////////////////////////////////////////////////////////////////
534: private static final Log log = LogFactory
535: .getLog(GraphElement.class);
536: }
|