
org.ow2.bonita.pvm.model.ProcessFactory Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.ow2.bonita.pvm.model;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Stack;
import org.ow2.bonita.pvm.ProcessDefinition;
import org.ow2.bonita.pvm.activity.Activity;
import org.ow2.bonita.pvm.client.ClientProcessDefinition;
import org.ow2.bonita.pvm.internal.model.CompositeElementImpl;
import org.ow2.bonita.pvm.internal.model.EventImpl;
import org.ow2.bonita.pvm.internal.model.EventListenerReference;
import org.ow2.bonita.pvm.internal.model.ExceptionHandlerImpl;
import org.ow2.bonita.pvm.internal.model.NodeImpl;
import org.ow2.bonita.pvm.internal.model.ObservableElementImpl;
import org.ow2.bonita.pvm.internal.model.PVMProcessDefinitionImpl;
import org.ow2.bonita.pvm.internal.model.ProcessElementImpl;
import org.ow2.bonita.pvm.internal.model.TimerDefinitionImpl;
import org.ow2.bonita.pvm.internal.model.TransitionImpl;
import org.ow2.bonita.pvm.internal.model.VariableDefinitionImpl;
import org.ow2.bonita.pvm.internal.wire.Descriptor;
import org.ow2.bonita.pvm.internal.wire.descriptor.ObjectDescriptor;
import org.ow2.bonita.pvm.internal.wire.descriptor.ProvidedObjectDescriptor;
import org.ow2.bonita.pvm.internal.wire.descriptor.StringDescriptor;
import org.ow2.bonita.pvm.listener.EventListener;
import org.ow2.bonita.util.BonitaRuntimeException;
import org.ow2.bonita.util.ExceptionManager;
/**
* factory for process definitions.
*
*
* Use this factory as a fluent
* interface for building a process definition. To use it in this way, start
* with instantiating a ProcessFactory object. Then a number of methods can be
* invoked concatenated with dots cause all the methods return the same process
* factory object. When done, end that sequence with {@link #done()} to get the
* constructed ProcessDefinition.
*
*
*
* The idea is that this results into a more compact and more readable code to
* build process definitions as opposed to including xml inline. For example :
*
*
*
* ProcessDefinition processDefinition = ProcessFactory.build().node().initial()
* .behaviour(new WaitState()).transition("normal").to("a").transition(
* "shortcut").to("c").node("a").behaviour(new WaitState()).transition()
* .to("b").node("b").behaviour(new WaitState()).transition().to("c")
* .node("c").behaviour(new WaitState()).done();
*
*
*
*
*
* If more control is needed over the creation of the process definition
* objects, then consider using the concrete implementation classes from package
* {@link org.ow2.bonita.pvm.internal.model} directly. The implementation code
* of this class might be a good guide to get you on your way.
*
*
* @author Tom Baeyens
*/
public class ProcessFactory {
// static factory methods ///////////////////////////////////////////////////
protected PVMProcessDefinitionImpl processDefinition;
protected NodeImpl node;
protected TransitionImpl transition;
protected List destinationReferences;
protected ObservableElementImpl observableElement;
protected EventImpl event;
protected EventListenerReference eventListenerReference;
protected ExceptionHandlerImpl exceptionHandler;
protected CompositeElementImpl compositeElement;
protected CompositeElementImpl scope;
protected Stack compositeElementStack;
/** start building a process definition without a name. */
protected ProcessFactory() {
this(null);
}
/** start building a process definition with the given name. */
protected ProcessFactory(String processName) {
this(processName, null);
}
/** start building a process definition with the given name. */
protected ProcessFactory(String processName,
PVMProcessDefinitionImpl processDefinition) {
if (processDefinition != null) {
this.processDefinition = processDefinition;
} else {
this.processDefinition = instantiateProcessDefinition();
}
this.processDefinition.setName(processName);
this.observableElement = this.processDefinition;
this.compositeElement = this.processDefinition;
this.scope = this.processDefinition;
}
/** starts building a process definition */
public static ProcessFactory build() {
return new ProcessFactory();
}
/** starts building a process definition */
public static ProcessFactory build(String processName) {
return new ProcessFactory(processName);
}
/** starts populating a given process definition */
public static ProcessFactory build(String processName,
PVMProcessDefinitionImpl processDefinition) {
return new ProcessFactory(processName, processDefinition);
}
/** to be overwritten by specific process language factories */
protected PVMProcessDefinitionImpl instantiateProcessDefinition() {
return new PVMProcessDefinitionImpl();
}
/** marks the last created node as the initial node in the process. */
public ProcessFactory initial() {
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_1");
throw new BonitaRuntimeException(message);
}
if (processDefinition.getInitial() != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_2");
throw new BonitaRuntimeException(message);
}
processDefinition.setInitial(node);
return this;
}
/**
* applies on a node and makes it create a local activity instance scope. This
* is automatically implied when {@link #variable(String) adding a variable} or
* {@link #timer() adding a timer}
*/
public ProcessFactory scope() {
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_3");
throw new BonitaRuntimeException(message);
}
node.setLocalScope(true);
scope = node;
return this;
}
/** declares a local variable. {@link #scope()} is automatically implied. */
public ProcessFactory variable(String key) {
if (node != null) {
scope();
}
VariableDefinitionImpl variableDefinition = compositeElement
.createVariableDefinition();
variableDefinition.setKey(key);
return this;
}
/** declares a local variable. {@link #scope()} is automatically implied. */
public ProcessFactory variable(Descriptor sourceDescriptor) {
if (node != null && scope == null) {
scope();
}
VariableDefinitionImpl variableDefinition = scope
.createVariableDefinition();
variableDefinition.setKey(sourceDescriptor.getName());
variableDefinition.setSourceDescriptor(sourceDescriptor);
return this;
}
/** declares a local variable. {@link #scope()} is automatically implied. */
public ProcessFactory variable(String key, String initialValue) {
return variable(new StringDescriptor(key, initialValue));
}
/**
* declares a timer on the current node or process. {@link #scope()} is
* automatically implied.
*/
public ProcessFactory timer(String dueDateDescription, String signalName) {
return timer(dueDateDescription, null, signalName, null);
}
/**
* declares a timer on the current node or process. {@link #scope()} is
* automatically implied.
*/
public ProcessFactory timer(String dueDateDescription, String signalName,
String repeat) {
return timer(dueDateDescription, null, signalName, repeat);
}
/**
* declares a timer on the current node or process. {@link #scope()} is
* automatically implied.
*/
public ProcessFactory timer(Date dueDate, String signalName) {
return timer(null, dueDate, signalName, null);
}
protected ProcessFactory timer(String dueDateDescription, Date dueDate,
String signalName, String repeat) {
if (node != null && scope == null) {
scope();
}
TimerDefinitionImpl timerDefinition = scope.createTimerDefinition();
if (dueDate != null) {
timerDefinition.setDueDate(dueDate);
} else {
timerDefinition.setDueDateDescription(dueDateDescription);
}
timerDefinition.setSignalName(signalName);
timerDefinition.setRepeat(repeat);
return this;
}
/**
* creates a node in the current parent. The current parent is either the
* process definition or a composite node in case method
* {@link #compositeNode(String)} was called previously.
*/
public ProcessFactory node() {
return node(null);
}
/**
* creates a named node. The current parent is either the process definition
* or a composite node in case method {@link #compositeNode(String)} was
* called previously.
*/
public ProcessFactory node(String nodeName) {
if (exceptionHandler != null) {
exceptionHandler.setNodeName(nodeName);
} else {
node = compositeElement.createNode(nodeName);
scope = null;
observableElement = node;
event = null;
eventListenerReference = null;
transition = null;
exceptionHandler = null;
}
return this;
}
/**
* sets the behaviour on the current node. A current node is required.
*/
public ProcessFactory behaviour(Activity activity) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_4");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_5");
throw new BonitaRuntimeException(message);
}
node.setBehaviour(activity);
return this;
}
/**
* sets the behaviour on the current node. A current node is required.
*/
public ProcessFactory behaviour(Descriptor descriptor) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_6");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_7");
throw new BonitaRuntimeException(message);
}
node.setBehaviour(descriptor);
return this;
}
/**
* sets the behaviour on the current node. A current node is required.
*/
public ProcessFactory behaviour(Class extends Activity> activityClass) {
return behaviour(new ObjectDescriptor(activityClass));
}
/**
* sets the behaviour on the current node. A current node is required.
*/
public ProcessFactory behaviour(String expression) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_8");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_9");
throw new BonitaRuntimeException(message);
}
node.setBehaviour(expression);
return this;
}
/**
* sets the asyncExecute property on the current node. A current node is
* required.
*/
public ProcessFactory asyncExecute() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_10");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_11");
throw new BonitaRuntimeException(message);
}
node.setExecutionAsync(true);
return this;
}
/**
* sets the asyncLeave property on the current node. A current node is
* required.
*/
public ProcessFactory asyncLeave() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_12");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_13");
throw new BonitaRuntimeException(message);
}
node.setLeaveAsync(true);
return this;
}
/**
* sets the asyncSignal property on the current node. A current node is
* required.
*/
public ProcessFactory asyncSignal() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_14");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_15");
throw new BonitaRuntimeException(message);
}
node.setSignalAsync(true);
return this;
}
/**
* sets the property needsPrevious on the current node. A current node is
* required.
*/
public ProcessFactory needsPrevious() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_16");
throw new BonitaRuntimeException(message);
}
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_17");
throw new BonitaRuntimeException(message);
}
node.setPreviousNeeded(true);
return this;
}
/**
* starts a block in which nested nodes can be created. This block can be
* ended with {@link #compositeEnd()}. A current node is required.
*/
public ProcessFactory compositeNode() {
return compositeNode(null);
}
/**
* starts a block in which nested nodes can be created. This block can be
* ended with {@link #compositeEnd()}. A current node is required.
*/
public ProcessFactory compositeNode(String nodeName) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_18");
throw new BonitaRuntimeException(message);
}
if (compositeElementStack == null) {
compositeElementStack = new Stack();
}
compositeElementStack.push(compositeElement);
node(nodeName);
compositeElement = node;
return this;
}
/**
* ends a block in which nested nodes are created. This method requires that a
* nested node block was started before with {@link #compositeNode(String)}
*/
public ProcessFactory compositeEnd() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_19");
throw new BonitaRuntimeException(message);
}
if (compositeElementStack == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_20");
throw new BonitaRuntimeException(message);
}
compositeElement = compositeElementStack.pop();
if (compositeElementStack.isEmpty()) {
compositeElementStack = null;
}
return this;
}
/**
* creates a transition on the current node. This method requires a current
* node
*/
public ProcessFactory transition() {
return transition(null);
}
/**
* creates a named transition on the current node. This method requires a
* current node
*/
public ProcessFactory transition(String transitionName) {
if (exceptionHandler != null) {
exceptionHandler.setTransitionName(transitionName);
} else {
if (node == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_21");
throw new BonitaRuntimeException(message);
}
transition = node.createOutgoingTransition(null, transitionName);
observableElement = transition;
event = null;
eventListenerReference = null;
exceptionHandler = null;
}
return this;
}
/**
* sets the takeAsync property on the current transition This method requires
* a current transition.
*/
public ProcessFactory asyncTake() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_22");
throw new BonitaRuntimeException(message);
}
if (transition == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_23");
throw new BonitaRuntimeException(message);
}
transition.setTakeAsync(true);
return this;
}
/**
* sets the destination node on the current transition. This method requires a
* current transition.
*/
public ProcessFactory to(String destination) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_24");
throw new BonitaRuntimeException(message);
}
if (transition == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_25");
throw new BonitaRuntimeException(message);
}
if (destinationReferences == null) {
destinationReferences = new ArrayList();
}
destinationReferences
.add(new DestinationReference(transition, destination));
return this;
}
/**
* sets the wait condition on the current transition. This method requires a
* current transition.
*/
public ProcessFactory waitCondition(Condition condition) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_26");
throw new BonitaRuntimeException(message);
}
if (transition == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_27");
throw new BonitaRuntimeException(message);
}
Descriptor conditionDescriptor = new ProvidedObjectDescriptor(condition);
transition.setWaitConditionDescriptor(conditionDescriptor);
return this;
}
/**
* sets the guard condition on the current transition. This method requires a
* current transition.
*/
public ProcessFactory guardCondition(Condition condition) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_28");
throw new BonitaRuntimeException(message);
}
if (transition == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_29");
throw new BonitaRuntimeException(message);
}
Descriptor conditionDescriptor = new ProvidedObjectDescriptor(condition);
transition.setConditionDescriptor(conditionDescriptor);
return this;
}
/**
* creates the given event on the current process element. This method
* requires a process element. A process element is either a process
* definition or a node. This method doesn't need to be called for
* transitions. If you have exception handlers and listeners on an event, make
* sure that you put the invocations of {@link #exceptionHandler(Class)}
* first.
*/
public ProcessFactory event(String eventName) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_30");
throw new BonitaRuntimeException(message);
}
if (observableElement == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_31");
throw new BonitaRuntimeException(message);
}
if (observableElement instanceof Transition) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_32");
throw new BonitaRuntimeException(message);
}
event = observableElement.createEvent(eventName);
exceptionHandler = null;
return this;
}
/**
* creates an exception handler for the given exception class on the current
* process element; until the {@link #exceptionHandlerEnd()}. Subsequent
* invocations of {@link #listener(Activity) listeners} or
* {@link #transition() transitions} will have the created exception handler as
* a target.
*
* DONT'T FORGET TO CLOSE THE EXCEPTION HANDLER WITH exceptionHandlerEnd.
*/
public ProcessFactory exceptionHandler(
Class extends Exception> exceptionClass) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_33");
throw new BonitaRuntimeException(message);
}
ProcessElementImpl processElement = null;
if (eventListenerReference != null) {
processElement = eventListenerReference;
} else if (event != null) {
processElement = event;
} else if (observableElement != null) {
processElement = observableElement;
} else {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_34");
throw new BonitaRuntimeException(message);
}
exceptionHandler = processElement.createExceptionHandler();
if (exceptionClass != null) {
exceptionHandler.setExceptionClassName(exceptionClass.getName());
}
return this;
}
public ProcessFactory exceptionHandlerEnd() {
exceptionHandler = null;
return this;
}
public ProcessFactory transactional() {
if (exceptionHandler == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_35");
throw new BonitaRuntimeException(message);
}
exceptionHandler.setTransactional(true);
return this;
}
/**
* adds an action to the current event. The current event was either created
* by {@link #event(String)} or by a {@link #transition()}. Subsequent
* invocations of {@link #exceptionHandler(Class)} will be associated to this
* event listener.
*/
public ProcessFactory listener(Descriptor descriptor) {
if (exceptionHandler != null) {
exceptionHandler.createEventListenerReference(descriptor);
} else {
getEvent().createEventListenerReference(descriptor);
}
return this;
}
/**
* adds an action to the current event. The current event was either created
* by {@link #event(String)} or by a {@link #transition()}. Subsequent
* invocations of {@link #exceptionHandler(Class)} will be associated to this
* event listener.
*/
public ProcessFactory listener(EventListener eventListener) {
if (exceptionHandler != null) {
exceptionHandler.createEventListenerReference(eventListener);
} else {
eventListenerReference = getEvent().createEventListenerReference(
eventListener);
}
return this;
}
/**
* adds an action to the current event. The current event was either created
* by {@link #event(String)} or by a {@link #transition()}. Subsequent
* invocations of {@link #exceptionHandler(Class)} will be associated to this
* event listener.
*/
public ProcessFactory listener(String expression) {
if (exceptionHandler != null) {
exceptionHandler.createActivityReference(expression);
} else {
eventListenerReference = getEvent().createEventListenerReference(
expression);
}
return this;
}
/**
* disables propagated events. This means that this action will only be
* executed if the event is fired on the actual process element of the event.
* The current action will not be executed if an event is fired on one of the
* children of the process element to which this event relates.
*/
public ProcessFactory propagationDisabled() {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_36");
throw new BonitaRuntimeException(message);
}
if (eventListenerReference == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_37");
throw new BonitaRuntimeException(message);
}
eventListenerReference.setPropagationEnabled(false);
return this;
}
private EventImpl getEvent() {
if ((event == null) && (observableElement instanceof TransitionImpl)) {
event = ((TransitionImpl) observableElement).createEvent();
return event;
}
if (event == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_38");
throw new BonitaRuntimeException(message);
}
return event;
}
/** adds a string-valued configuration to the current process element */
public ProcessFactory property(String name, String stringValue) {
StringDescriptor stringDescriptor = new StringDescriptor();
stringDescriptor.setName(name);
stringDescriptor.setValue(stringValue);
property(stringDescriptor);
return this;
}
/** adds a configuration to the current process element */
public ProcessFactory property(Descriptor descriptor) {
if (exceptionHandler != null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_39");
throw new BonitaRuntimeException(message);
}
if (observableElement == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_40");
throw new BonitaRuntimeException(message);
}
if (event != null) {
event.addProperty(descriptor);
} else {
observableElement.addProperty(descriptor);
}
return this;
}
public class DestinationReference {
TransitionImpl transition;
String destinationName;
public DestinationReference(TransitionImpl transition,
String destinationName) {
this.transition = transition;
this.destinationName = destinationName;
}
public void resolve() {
NodeImpl destination = (NodeImpl) processDefinition
.findNode(destinationName);
if (destination == null) {
String message = ExceptionManager.getInstance().getFullMessage(
"pb_PF_41", destinationName, transition);
throw new BonitaRuntimeException(message);
}
destination.addIncomingTransition(transition);
transition.setDestination(destination);
}
}
/**
* extract the process definition from the factory. This should be the last
* method in the chain of subsequent invoked methods on this factory object.
*/
public ClientProcessDefinition done() {
resolveDestinations();
if (processDefinition.getInitial() == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_PF_42");
throw new BonitaRuntimeException(message);
}
return processDefinition;
}
/**
* sets the {@link ProcessDefinition#getVersion() version} of the process
* definition explicitely
*/
public ProcessFactory version(int version) {
processDefinition.setVersion(version);
return this;
}
/**
* sets the {@link ProcessDefinition#getKey() key} of the process definition
* explicitely
*/
public ProcessFactory key(String key) {
processDefinition.setKey(key);
return this;
}
private void resolveDestinations() {
if (destinationReferences != null) {
for (DestinationReference destinationReference : destinationReferences) {
destinationReference.resolve();
}
}
}
}