/*
* 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.jbpm.pvm;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.jbpm.PvmException;
import org.jbpm.pvm.impl.EventListenerReference;
import org.jbpm.pvm.impl.CompositeElementImpl;
import org.jbpm.pvm.impl.EventImpl;
import org.jbpm.pvm.impl.ExceptionHandlerImpl;
import org.jbpm.pvm.impl.NodeImpl;
import org.jbpm.pvm.impl.ObjectReference;
import org.jbpm.pvm.impl.ObservableElementImpl;
import org.jbpm.pvm.impl.ProcessDefinitionImpl;
import org.jbpm.pvm.impl.ProcessElementImpl;
import org.jbpm.pvm.impl.TransitionImpl;
import org.jbpm.wire.Descriptor;
import org.jbpm.wire.descriptor.StringDescriptor;
/** factory for process definitions.
*
* <p>Use this factory as a <a href="http://martinfowler.com/bliki/FluentInterface.html">fluent interface</a>
* 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.
* </p>
*
* <p>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 :
* </p>
* <pre>
* ProcessDefinition processDefinition = ProcessFactory.build()
* .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();
* </pre>
*
* <hr />
*
* <p>If more control is needed over the creation of the process definition
* objects, then consider using the concrete implementation classes from
* package {@link org.jbpm.pvm.impl} directly. The implementation code
* of this class might be a good guide to get you on your way.
* </p>
*
* @author Tom Baeyens
*/
public class ProcessFactory {
// static factory methods ///////////////////////////////////////////////////
protected ProcessDefinitionImpl processDefinition;
protected NodeImpl node;
protected TransitionImpl transition;
protected List<DestinationReference> destinationReferences;
protected ObservableElementImpl observableElement;
protected EventImpl event;
protected EventListenerReference eventListenerReference;
protected ExceptionHandlerImpl exceptionHandler;
protected CompositeElementImpl compositeElement;
protected Stack<CompositeElementImpl> 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, ProcessDefinitionImpl processDefinition) {
if (processDefinition!=null) {
this.processDefinition = processDefinition;
} else {
this.processDefinition = instantiateProcessDefinition();
}
this.processDefinition.setName(processName);
this.observableElement = this.processDefinition;
this.compositeElement = 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, ProcessDefinitionImpl processDefinition) {
return new ProcessFactory(processName, processDefinition);
}
/** to be overwritten by specific process language factories */
protected ProcessDefinitionImpl instantiateProcessDefinition() {
return new ProcessDefinitionImpl();
}
/** marks the last created node as the initial node in the process. */
public ProcessFactory initial() {
if (node==null) {
throw new PvmException("no current node");
}
if (processDefinition.getInitial()!=null) {
throw new PvmException("duplicate initial node");
}
processDefinition.setInitial(node);
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);
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (node==null) {
throw new PvmException("no current node");
}
node.setBehaviour(activity);
return this;
}
/** sets the asyncExecute property on the current node.
* A current node is required. */
public ProcessFactory asyncExecute() {
if (exceptionHandler!=null) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (node==null) {
throw new PvmException("no current node");
}
node.setExecutionAsync(true);
return this;
}
/** sets the asyncLeave property on the current node.
* A current node is required. */
public ProcessFactory asyncLeave() {
if (exceptionHandler!=null) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (node==null) {
throw new PvmException("no current node");
}
node.setLeaveAsync(true);
return this;
}
/** sets the asyncSignal property on the current node.
* A current node is required. */
public ProcessFactory asyncSignal() {
if (exceptionHandler!=null) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (node==null) {
throw new PvmException("no current node");
}
node.setSignalAsync(true);
return this;
}
/** sets the property needsPrevious on the current node.
* A current node is required. */
public ProcessFactory needsPrevious() {
if (exceptionHandler!=null) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (node==null) {
throw new PvmException("no current node");
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (compositeElementStack==null) {
compositeElementStack = new Stack<CompositeElementImpl>();
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (compositeElementStack==null) {
throw new PvmException("no composite node was started");
}
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) {
throw new PvmException("no current node");
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (transition==null) {
throw new PvmException("no current transition");
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (transition==null) {
throw new PvmException("no current transition");
}
if (destinationReferences==null) {
destinationReferences = new ArrayList<DestinationReference>();
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (transition==null) {
throw new PvmException("no current transition");
}
ObjectReference<Condition> waitConditionReference = new ObjectReference<Condition>(condition);
transition.setWaitConditionReference(waitConditionReference);
return this;
}
/** sets the guard condition on the current transition.
* This method requires a current transition. */
public ProcessFactory guardCondition(Condition condition) {
if (exceptionHandler!=null) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (transition==null) {
throw new PvmException("no current transition");
}
ObjectReference<Condition> guardConditionReference = new ObjectReference<Condition>(condition);
transition.setGuardConditionReference(guardConditionReference);
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (observableElement==null) {
throw new PvmException("no current process element");
}
if (observableElement instanceof Transition) {
throw new PvmException("for actions on transitions, you don't need to call event");
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
ProcessElementImpl processElement = null;
if (eventListenerReference!=null) {
processElement = eventListenerReference;
} else if (event!=null) {
processElement = event;
} else if (observableElement!=null) {
processElement = observableElement;
} else {
throw new PvmException("no current process element, event or action");
}
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) {
throw new PvmException("transactional is a property of an exception handler");
}
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.createActivityReference(descriptor);
} else {
getEvent().createListenerReference(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(Activity activity) {
if (exceptionHandler!=null) {
exceptionHandler.createActivityReference(activity);
} else {
eventListenerReference = getEvent().createListenerReference(activity);
}
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().createListenerReference(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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (eventListenerReference==null) {
throw new PvmException("no current event action");
}
eventListenerReference.setPropagationEnabled(false);
return this;
}
private EventImpl getEvent() {
if ( (event==null)
&& (observableElement instanceof TransitionImpl)
) {
event = ((TransitionImpl)observableElement).createEvent();
return event;
}
if (event==null) {
throw new PvmException("no current event");
}
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) {
throw new PvmException("exceptionHandler needs to be closed with exceptionHandlerEnd");
}
if (observableElement==null) {
throw new PvmException("no current process element");
}
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) {
throw new PvmException("couldn't find destination node "+destinationName+" for "+transition);
}
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 ProcessDefinition done() {
resolveDestinations();
return processDefinition;
}
private void resolveDestinations() {
if (destinationReferences!=null) {
for (DestinationReference destinationReference : destinationReferences) {
destinationReference.resolve();
}
}
}
}