All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.bpm.model.ProcessDefinitionBuilder Maven / Gradle / Ivy

There is a newer version: 1.0.3
Show newest version
package io.takari.bpm.model;

import io.takari.bpm.model.SourceMap.Significance;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

public abstract class ProcessDefinitionBuilder {

    /**
     * Creates a new ProcessBuilder which will return a fully built SourceAwareProcessDefinition when complete
     */
    public static Process newProcess(String id) {
        return new ProcessImpl(id);
    }

    /**
     * Basic builder which allows a number of elements to be added in sequence. This builder is not generified to allow simplified usage.
     */
    public interface Seq {

        /**
         * Adds a ServiceTask element
         */
        Seq task(ExpressionType expType, String expr);

        /**
         * Adds a ServiceTask element with in/out mappings
         */
        Seq task(ExpressionType expType, String expr, Set in, Set out);

        /**
         * Adds a ServiceTask element with in/out mappings
         */
        Seq task(ExpressionType expType, String expr, Set in, Set out, boolean copyAllVariables);

        /**
         * Adds a simple ServiceTask element
         */
        Seq task(String expr);

        /**
         * Adds a simple ServiceTask with in/out mappings
         */
        Seq task(String expr, Set in, Set out);

        /**
         * Adds a simple ServiceTask with in/out mappings
         */
        Seq task(String expr, Set in, Set out, boolean copyAllVariables);

        /**
         * Adds a delegate ServiceTask element
         */
        Seq taskDelegate(String expr);

        Seq userTask(List extensions);

        /**
         * Adds a ScriptTask element.
         */
        Seq script(ScriptTask.Type type, String language, String content);

        /**
         * Adds a ScriptTask element.
         */
        Seq script(ScriptTask.Type type, String language, String content, boolean copyAllVariables);

        /**
         * Adds a CallActivity element
         */
        Seq call(String calledElement);

        /**
         * Adds a CallActivity element
         */
        Seq call(String calledElement, boolean copyAllVariables);

        /**
         * Adds a CallActivity element
         */
        Seq call(String calledElement, Set in, Set out);

        /**
         * Adds a CallActivity element
         */
        Seq call(String calledElement, Set in, Set out, boolean copyAllVariables);

        /**
         * Adds an EventBasedGateway element
         */
        Seq eventGate();

        /**
         * Adds a ParallelGateway element
         */
        Seq parallelGate();

        /**
         * Adds an InclusiveGateway element
         */
        Seq inclusiveGate();

        /**
         * Adds an ExclusiveGateway element
         */
        Seq exclusiveGate();

        /**
         * Adds an ExclusiveGateway element
         */
        Seq exclusiveGate(String defaultFlow);

        /**
         * Adds an explicit named flow to be used by exclusive gate
         */
        Seq flow(String name);

        /**
         * Adds an explicit flow to be used by exclusive gate when the expression evaluates to true
         */
        Seq flowExpr(String expression);

        /**
         * Adds an explicit named flow to be used by exclusive gate when the expression evaluates to true
         */
        Seq flowExpr(String name, String expression);

        /**
         * Adds an IntermediateCatchEvent element
         */
        Seq catchEvent();

        /**
         * Adds an IntermediateCatchEvent element
         */
        Seq catchEvent(String messageRef);

        /**
         * Adds an IntermediateCatchEvent element with a messageRef expression
         */
        Seq catchEventExpr(String messageRefExpr);

        /**
         * Adds an IntermediateCatchEvent element
         */
        Seq catchEvent(String messageRef, String timeDate, String timeDuration);

        /**
         * Adds an IntermediateCatchEvent element with a messageRef expression
         */
        Seq catchEventExpr(String messageRefExpr, String timeDate, String timeDuration);

        /**
         * Starts a nested builder which will add a new SubProcess to the parent builder when done
         */
        Sub sub();

        /**
         * Starts a nested builder which will add a new SubProcess (with separate execution context) to the parent builder when done
         */
        Sub sub(boolean useSeparateContext);

        /**
         * Starts a nested builder which will add a new SubProcess (with separate execution context and specified out variables) to the parent builder when done
         */
        Sub sub(boolean useSeparateContext, Set out);

        /**
         * Creates a ForkBuilder which fills a forked flow of elements. When complete, will return parent builder that will continue building flow from the point of fork.
         * 

*

* Some fork examples: *

* * * * * * * * * * * * * * * * * * * * *
CodeResult flow
{@code
         * builder
         *  .task("t1")
         *  .fork().task("t2").end()
         *  .fork().task("t3").end("err")
         *  .task("t4").end()
         *    }
*
         *               -> t2 -> [end]
         *              /
         *  [start] -> t1 -> t3 -> [end "err"]
         *              \
         *               -> t4 -> [end]
         *      
*
{@code
         * builder
         *  .task("t1")
         *  .fork().task("t2").joinTo("join1")
         *  .fork().task("t3").joinTo("join1")
         *  .fork().task("t4").joinTo("join1")
         *  .joinAll("join1").task("t5").end()
         *    }
*
         *               -> t2 ->
         *              /         \
         *  [start] -> t1 -> t3 -> t5 -> [end]
         *              \         /
         *               -> t4 ->
         *      
*
{@code
         * builder
         *  .task("t1")
         *  .fork().task("t2").joinTo("join1")
         *  .fork().task("t3").joinTo("join1")
         *  .task("t4")
         *  .joinPoint("join1").task("t5")
         *  .end()
         *    }
*
         *               -> t2 ->
         *              /         \
         *  [start] -> t1 -> t3 -> t5 -> [end]
         *              \         /
         *               -> t4 ->
         *      
*
{@code
         * builder
         *  .joinPoint("join1")
         *  .task("t1")
         *  .task("t2")
         *  .task("t3")
         *   // joins it to "t1"
         *  .fork().task("t4").joinTo("join1")
         *  .end()
         *    }
*
         *               <- t4 <-
         *              /         \
         *  [start] -> t1 -> t3 -> t3 -> [end]
         *
         *      
*
*/ Fork fork(); /** * Forks workflow to handle raised errors */ Fork boundaryEvent(); /** * Forks workflow to handle raised errors */ Fork boundaryEvent(String errorRef); /** * Forks workflow to handle raised errors */ Fork boundaryEvent(String errorRef, String timeDuration); /** * Marks current position as a join point. Any dangling fork tails (with the same joinName) will get joined to the next element added to the sequence. * The added element will become a continuation of a previous element, if you need to tie multiple forks and continue the flow from them without creating * another parallel flow, use {@link #joinAll(String)} method instead. *

*

* These two code blocks yield the same result: *

         * {@code
         * builder
         *   ...
         *   .fork()
         *     ...(1)
         *     .joinTo("join1")
         *   .fork()
         *     ...(2)
         *     .joinTo("join1")
         *   ...(3)
         *   .joinPoint("join1")
         *   ...
         *  }
         * 
*
         * {@code
         * builder
         *   ...
         *   .fork()
         *     ...(1)
         *     .joinTo("join1")
         *   .fork()
         *     ...(2)
         *     .joinTo("join1")
         *   .fork()
         *     ...(3)
         *     .joinTo("join1")
         *   .joinAll("join1")
         *   ...
         *  }
         * 
*/ Seq joinPoint(String joinName); /** * Marks current position as a join point. Any dangling fork tails (with the same joinName) will get joined to the next element added to the sequence. * Unlike with the {@link #joinPoint(String)} method the new element will be a continuation of those tails only, there will be no flow from the fork point. */ Seq joinAll(String joinName); /** * Identical to joinAll but for forks that were ended with {@code Fork#loop()} */ Seq tieForks(); /** * Perform provided function on this object. Useful for reusing portions of builder and not break the execution chain at the same time: *
         * b.sub()
         *   .apply(this::doSomething);
         *   .end()
         *
         * Seq doSomething(Seq seq) {
         *   return seq
         *     .sub()
         *       ...
         *       .end();
         * }
         * 
*/ Seq apply(Function f); /** * Perform provided BiFunction on each element of the provided collection, similar to {@link #apply(Function)} */ Seq applyEach(Collection elements, BiFunction f); /** * Adds an EndEvent */ Object endEvent(); /** * Adds an EndEvent with an errorRef */ Object endEvent(String errorRef); /** * Adds an EndEvent and completes current sequence */ Object end(); /** * Adds an EndEvent with the specified source map element and completes current sequence */ Object end(Significance sig, int line, int col, String desc); /** * Adds an EndEvent with an errorRef and completes current sequence */ Object end(String errorRef); /** * Adds an EndEvent with an errorRef and causeExpression and completes current sequence */ Object end(String errorRef, String causeExpression); /** * Adds an EndEvent with an errorRef, causeExpression and source map and completes current sequence */ Object end(String errorRef, String causeExpression, Significance sig, int line, int col, String desc); /** * Adds a source map to the last added element. */ Seq sourceMap(Significance sig, int line, int col, String desc); } /** * Introduces type parameter for nested builders */ public interface TypedSeq, P> extends Seq { String nextStepId(); T add(AbstractElement elem); @Override default T task(ExpressionType expType, String expr) { return task(expType, expr, null, null); } default T task(ExpressionType expType, String expr, Set in, Set out) { return task(expType, expr, in, out, false); } default T task(ExpressionType expType, String expr, Set in, Set out, boolean copyAllVariables) { return add(new ServiceTask(nextStepId(), expType, expr, in, out, copyAllVariables)); } @Override default T task(String expr) { return task(ExpressionType.SIMPLE, expr); } default T task(String expr, Set in, Set out) { return task(ExpressionType.SIMPLE, expr, in, out); } default T task(String expr, Set in, Set out, boolean copyAllVariables) { return task(ExpressionType.SIMPLE, expr, in, out, copyAllVariables); } @Override default T taskDelegate(String expr) { return task(ExpressionType.DELEGATE, expr); } @Override default Seq userTask(List extensions) { return add(new UserTask(nextStepId(), extensions)); } @Override default T script(ScriptTask.Type type, String language, String content) { return script(type, language, content, false); } @Override default T script(ScriptTask.Type type, String language, String content, boolean copyAllVariables) { return add(new ScriptTask(nextStepId(), type, language, content, copyAllVariables)); } @Override default T call(String calledElement) { return add(new CallActivity(nextStepId(), calledElement)); } @Override default T call(String calledElement, boolean copyAllVariables) { return add(new CallActivity(nextStepId(), calledElement, copyAllVariables)); } @Override default T call(String calledElement, Set in, Set out) { return add(new CallActivity(nextStepId(), calledElement, in, out)); } @Override default T call(String calledElement, Set in, Set out, boolean copyAllVariables) { return add(new CallActivity(nextStepId(), calledElement, in, out, copyAllVariables)); } @Override default T eventGate() { return add(new EventBasedGateway(nextStepId())); } @Override default T parallelGate() { return add(new ParallelGateway(nextStepId())); } @Override default T inclusiveGate() { return add(new InclusiveGateway(nextStepId())); } @Override default T exclusiveGate() { return add(new ExclusiveGateway(nextStepId())); } @Override default T exclusiveGate(String defaultFlow) { return add(new ExclusiveGateway(nextStepId(), defaultFlow)); } @Override T flow(String name); @Override T flowExpr(String expression); @Override T flowExpr(String name, String expression); @Override default T catchEvent() { return add(new IntermediateCatchEvent(nextStepId())); } @Override default T catchEvent(String messageRef) { return catchEvent(messageRef, null, null); } @Override default T catchEventExpr(String messageRefExpr) { return catchEventExpr(messageRefExpr, null, null); } @Override default T catchEvent(String messageRef, String timeDate, String timeDuration) { return add(new IntermediateCatchEvent(nextStepId(), messageRef, timeDate, timeDuration)); } @Override default T catchEventExpr(String messageRefExpr, String timeDate, String timeDuration) { return add(new IntermediateCatchEvent(nextStepId(), null, messageRefExpr, timeDate, timeDuration)); } @Override default T apply(Function f) { if (f != null) { Object s = f.apply(this); if (s != this) { throw new IllegalStateException("Function did not return what was passed in"); } } @SuppressWarnings("unchecked") T t = (T) this; return t; } @Override default T applyEach(Collection elements, BiFunction f) { for (E e : elements) { Object s = f.apply(this, e); if (s != this) { throw new IllegalStateException("Function did not return what was passed in"); } } @SuppressWarnings("unchecked") T t = (T) this; return t; } /** * Applies provided function on this subprocess */ default T applySub(Function function) { @SuppressWarnings("unchecked") Function f = (Function) function; return apply(f); } /** * Similar to {@link #applySub(Function)}, but also allows to end the sub from within the provided function */ default P applyEnd(Function function) { @SuppressWarnings("unchecked") T t = (T) this; @SuppressWarnings("unchecked") P par = (P) function.apply(t); if (parent() != null && par != parent()) { throw new IllegalStateException("Function did not return the correct parent"); } return par; } @Override Sub sub(); @Override Sub sub(boolean useSeparateContext); @Override Sub sub(boolean useSeparateContext, Set out); @Override Fork fork(); @Override Fork boundaryEvent(); @Override Fork boundaryEvent(String errorRef); @Override Fork boundaryEvent(String errorRef, String timeDuration); @Override T joinPoint(String joinName); @Override T joinAll(String joinName); @Override T tieForks(); @Override P end(); @Override P end(Significance sig, int line, int col, String desc); @Override P end(String errorRef); @Override P end(String errorRef, String causeExpression); @Override P end(String errorRef, String causeExpression, Significance sig, int line, int col, String desc); P parent(); @Override T sourceMap(Significance sig, int line, int col, String desc); } /** * Nested builder which represents a sub-process */ public interface Sub

extends TypedSeq, P> { } /** * Nested builder which represents a forked flow of a sequence */ public interface Fork

extends TypedSeq, P> { /** * Completes this fork and adds a dangling tail to be joined later */ P joinTo(String joinName); /** * Completes this fork as a loop, which is identical to joinTo, bu will always use parent's last element as a join point. * If {@code Seq#tieForks()} method is used, it works identical to {@code Seq#joinAll(String)}, but if not, an implicit {@code Seq#joinPoint(String)} is performed. */ P loop(); } /** * Top-level process builder which produces a SourceAwareProcessDefinition when complete */ public interface Process extends TypedSeq { } private static abstract class SeqImpl, P> implements TypedSeq { private final List elements = new ArrayList<>(); protected final String prefix; protected int stepCounter = 0; protected int endCounter = 0; protected String lastId; protected AbstractElement lastElement; protected FlowFactory flows = new FlowFactory(this); protected Map joins; protected String joinPoint; private boolean joinPointAll; SeqImpl(String prefix) { this.prefix = prefix; } protected List getElements() { return elements; } protected String nextFlowId(String from, String to) { if (from.startsWith(prefix)) { from = from.substring(prefix.length()); } if (to.startsWith(prefix)) { to = to.substring(prefix.length()); } return prefix + "flow_" + from + "_" + to; } @Override public String nextStepId() { return prefix + ++stepCounter; } protected String startId() { return prefix + "start"; } protected String nextEndId() { if (endCounter++ == 0) { return prefix + "end"; } return prefix + "end" + endCounter; } protected String loopJoin() { return "__loop_" + lastId; } @SuppressWarnings("unchecked") protected T ret() { return (T) this; } protected abstract P done(); protected void addFlow(String from, String to) { addElement(flows.newFlow(from, to)); } @Override public T flow(String name) { flows.prime(name, null); return ret(); } @Override public T flowExpr(String expression) { flows.prime(null, expression); return ret(); } @Override public T flowExpr(String name, String expression) { flows.prime(name, expression); return ret(); } protected void addStart() { add(new StartEvent(startId())); } protected void addEnd(String errorRef, String causeExpression) { addEnd(errorRef, causeExpression, null, -1, -1, null); } protected void addEnd(String errorRef, String causeExpression, Significance sig, int line, int col, String desc) { add(new EndEvent(nextEndId(), errorRef, causeExpression)); if (sig != null) { sourceMap(sig, line, col, desc); } validate(); } protected void addElement(AbstractElement e) { elements.add(e); } @Override public T add(AbstractElement elem) { String newId = elem.getId(); // perform joining, if needed String loopJoin = loopJoin(); if (joinPoint == null && hasJoins(loopJoin)) { joinPoint(loopJoin); } // save those for the new element since doJoinPoint() might overwrite those String flowName = null; String flowExpr = null; if(flows.primed) { flowName = flows.name; flowExpr = flows.expression; } doJoinPoint(newId); if (lastId != null) { // restore flow data if needed if(flowName != null || flowExpr != null) { flows.prime(flowName, flowExpr); } addFlow(lastId, newId); } addElement(elem); lastId = newId; lastElement = elem; return ret(); } @Override public T endEvent() { addEnd(null, null); return ret(); } @Override public T endEvent(String errorRef) { addEnd(errorRef, null); return ret(); } public P end() { if(!(lastElement instanceof EndEvent)) { addEnd(null, null); } return done(); } public P end(Significance sig, int line, int col, String desc) { addEnd(null, null, sig, line, col, desc); return done(); } public P end(String errorRef) { addEnd(errorRef, null); return done(); } public P end(String errorRef, String causeExpression) { addEnd(errorRef, causeExpression); return done(); } public P end(String errorRef, String causeExpression, Significance sig, int line, int col, String desc) { addEnd(errorRef, causeExpression, sig, line, col, desc); return done(); } public SubImpl sub() { return sub(false); } public SubImpl sub(boolean useSeparateContext) { return sub(useSeparateContext, Collections.emptySet()); } public SubImpl sub(boolean useSeparateContext, Set out) { return new SubImpl(this, nextStepId(), useSeparateContext, out); } public ForkImpl fork() { return new ForkImpl(this, lastId, lastElement); } public ForkImpl boundaryEvent() { return boundaryEvent(null); } public ForkImpl boundaryEvent(String errorRef) { return boundaryEvent(errorRef, null); } public ForkImpl boundaryEvent(String errorRef, String timeDuration) { return new ForkImpl(this, new BoundaryEvent(nextStepId(), lastId, errorRef, timeDuration)); } public T joinPoint(String joinName) { return join(joinName, false); } public T joinAll(String joinName) { return join(joinName, true); } @Override public T tieForks() { return join(loopJoin(), true); } private T join(String joinName, boolean all) { this.joinPoint = joinName; this.joinPointAll = all; return ret(); } protected void doJoin(String joinName, String id, AbstractElement element, String flowName, String flowExpr, List additionalDanglingJoins) { JoinData j = getJoin(joinName); if(!(element instanceof EndEvent)) { if(additionalDanglingJoins.isEmpty()) { j.danglingJoins.add(new Tail(id, flowName, flowExpr)); } j.danglingJoins.addAll(additionalDanglingJoins); } flushJoins(j); } protected void doJoinPoint(String id) { if (joinPoint != null) { JoinData j = getJoin(joinPoint); if (j.target != null) { throw new IllegalStateException("Join " + joinPoint + " target is already defined as " + j.target); } j.target = id; flushJoins(j); if (joinPointAll) { lastId = null; lastElement = null; } joinPoint = null; joinPointAll = false; } } private JoinData getJoin(String joinName) { if (joins == null) { joins = new HashMap<>(); } JoinData j = joins.get(joinName); if (j == null) { j = new JoinData(); joins.put(joinName, j); } return j; } private boolean hasJoins(String joinName) { return joins != null && joins.containsKey(joinName); } private void flushJoins(JoinData j) { if (j.target != null) { for (Tail tail: j.danglingJoins) { if(tail.flowName != null || tail.flowExpression != null) { flows.prime(tail.flowName, tail.flowExpression); } addFlow(tail.id, j.target); } j.danglingJoins.clear(); } } protected void validate() { if (joins != null) { for (Map.Entry e : joins.entrySet()) { String k = e.getKey(); JoinData j = e.getValue(); if (!j.danglingJoins.isEmpty()) { throw new IllegalStateException("Not all tails were joined into " + k + " (" + j.danglingJoins + ")"); } } } } protected abstract void sourceMap(String id, Significance sig, int line, int col, String desc); } private static class SubImpl

extends SeqImpl, P> implements Sub

{ private SeqImpl parent; private String id; private boolean useSeparateContext; private Set out; SubImpl(SeqImpl parent, String id, boolean useSeparateContext, Set out) { super(id + "_"); this.parent = parent; this.id = id; this.useSeparateContext = useSeparateContext; this.out = out; addStart(); } @Override protected P done() { parent.add(new SubProcess(id, useSeparateContext, out, getElements())); return parent(); } @SuppressWarnings("unchecked") @Override public P parent() { return (P) parent; } @Override public Sub

sourceMap(Significance sig, int line, int col, String desc) { sourceMap(lastId, sig, line, col, desc); return this; } @Override protected void sourceMap(String id, Significance sig, int line, int col, String desc) { parent.sourceMap(id, sig, line, col, desc); } } private static class ForkImpl

extends SeqImpl, P> implements Fork

{ private SeqImpl parent; ForkImpl(SeqImpl parent, String lastId, AbstractElement lastElement) { super(parent.prefix); this.parent = parent; this.lastId = lastId; this.lastElement = lastElement; } ForkImpl(SeqImpl parent, AbstractElement start) { this(parent, start.getId(), start); addElement(start); } @SuppressWarnings("unchecked") @Override public P parent() { return (P) parent; } @Override protected String nextFlowId(String from, String to) { return parent.nextFlowId(from, to); } @Override public String nextStepId() { return parent.nextStepId(); } @Override protected String nextEndId() { return parent.nextEndId(); } public P joinTo(String joinName) { P p = done(); String flowName = flows.primed ? flows.name : null; String flowExpr = flows.primed ? flows.expression : null; parent.doJoin(joinName, lastId, lastElement, flowName, flowExpr, getActiveDanglingItems()); return p; } @Override public P loop() { return joinTo(parent.loopJoin()); } @SuppressWarnings("unchecked") @Override protected P done() { parent.getElements().addAll(getElements()); return (P) parent; } private List getActiveDanglingItems() { if(joinPoint == null) { return Collections.emptyList(); } JoinData j = joins.get(joinPoint); if(j == null) { return Collections.emptyList(); } return j.danglingJoins; } @Override public Fork

sourceMap(Significance sig, int line, int col, String desc) { sourceMap(lastId, sig, line, col, desc); return this; } @Override protected void sourceMap(String id, Significance sig, int line, int col, String desc) { parent.sourceMap(id, sig, line, col, desc); } } private static class JoinData { private String target; private List danglingJoins = new ArrayList<>(); } private static class Tail { final String id; final String flowName; final String flowExpression; public Tail(String id, String flowName, String flowExpression) { this.id = id; this.flowName = flowName; this.flowExpression = flowExpression; } } private static class FlowFactory { private SeqImpl seq; boolean primed = false; String name; String expression; public FlowFactory(SeqImpl seq) { this.seq = seq; } void prime(String name, String expression) { primed = true; this.name = name; this.expression = expression; } SequenceFlow newFlow(String from, String to) { if (primed) { primed = false; String id = name; if (id == null) { id = seq.nextFlowId(from, to); } return new SequenceFlow(id, from, to, expression); } return new SequenceFlow(seq.nextFlowId(from, to), from, to); } } public static class ProcessImpl extends SeqImpl implements Process { private String id; private final Map sourceMaps = new HashMap<>(); ProcessImpl(String id) { super(id + "_"); this.id = id; addStart(); } @Override protected SourceAwareProcessDefinition done() { return new SourceAwareProcessDefinition(id, getElements(), Collections.emptyMap(), sourceMaps); } @Override public SourceAwareProcessDefinition parent() { return null; } @Override public Process sourceMap(Significance sig, int line, int col, String desc) { sourceMap(lastId, sig, line, col, desc); return this; } @Override protected void sourceMap(String id, Significance sig, int line, int col, String desc) { Object old = sourceMaps.put(id, new SourceMap(sig, null, line, col, desc)); if (old != null) { throw new IllegalArgumentException("Duplicate source map ID: " + id); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy