package brainslug.flow.builder;
import brainslug.flow.definition.EnumIdentifier;
import brainslug.flow.definition.FlowDefinition;
import brainslug.flow.definition.Identifier;
import brainslug.flow.definition.StringIdentifier;
import brainslug.flow.expression.*;
import brainslug.flow.node.*;
import brainslug.flow.node.event.AbstractEventDefinition;
import brainslug.flow.node.event.StartEvent;
import brainslug.flow.node.event.timer.StartTimerDefinition;
import brainslug.flow.node.task.*;
import brainslug.flow.path.FlowPathDefinition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class FlowBuilderSupport {
protected FlowDefinition definition = new FlowDefinition();
protected ServiceCallInvocationSupport serviceCallInvocation = new ServiceCallInvocationSupport();
public FlowBuilderSupport() {
}
public static Identifier id(Enum id) {
return new EnumIdentifier(id);
}
public static Identifier id(String id) {
return new StringIdentifier(id);
}
public static Identifier id() {
return new StringIdentifier(generateId());
}
protected static String generateId() {
return UUID.randomUUID().toString();
}
/**
* creates a start timer with given interval and unit
*
* @param interval the interval
* @param intervalUnit the unit
* @return a start timer to be used for a start event definition
*/
public StartTimerDefinition every(int interval, TimeUnit intervalUnit) {
return new StartTimerDefinition(interval, intervalUnit);
}
/**
* create a start event with the given id.
*
* @param startId the start event id
*
* @return the flow path starting with this event
*/
public FlowPathDefinition start(Identifier startId) {
return start(event(startId));
}
/**
* create a start event with the given id,
* which will be started every interval with
* the duration given in the timer definition.
*
* @param startId the start event id
* @param startTimerDefinition the timer definition defining the interval
*
* @return the flow path starting with this event
*/
public FlowPathDefinition start(Identifier startId, StartTimerDefinition startTimerDefinition) {
return start(event(startId), startTimerDefinition);
}
public FlowPathDefinition start(AbstractEventDefinition event) {
return new FlowPathDefinition(definition, definition.addStartNode(startEvent(event)));
}
/**
* create a start event with the given event,
* which will be started every interval with
* the duration given in the timer definition.
*
* @param event the start event id
* @param startTimerDefinition the timer definition defining the interval
*
* @return the flow path starting with this event
*/
public FlowPathDefinition start(AbstractEventDefinition event, StartTimerDefinition startTimerDefinition) {
return start(timerEvent(event, startTimerDefinition));
}
protected <T extends AbstractEventDefinition> AbstractEventDefinition<T> startEvent(AbstractEventDefinition<T> event) {
if (event.is(StartEvent.class)) {
return event;
}
event.with(new StartEvent());
return event;
}
protected AbstractEventDefinition timerEvent(AbstractEventDefinition event, StartTimerDefinition startTimerDefinition) {
StartEvent startEvent = (StartEvent) startEvent(event).as(StartEvent.class);
startEvent.withRecurringTimerDefinition(startTimerDefinition);
return event;
}
/**
* define task as first node in the flow
* @param task the task
* @return the flow path starting with this task
*/
public FlowPathDefinition start(AbstractTaskDefinition task) {
return new FlowPathDefinition(definition, definition.addStartNode(task));
}
public FlowPathDefinition start(Identifier id, AbstractTaskDefinition task, StartTimerDefinition startTimerDefinition) {
return start(event(id), startTimerDefinition).execute(task);
}
/**
* create a flow path for execution after of the node id
*
* @param id of the node to continue after
* @return the flow after the given node
*/
public FlowPathDefinition after(Identifier id) {
return new FlowPathDefinition(definition, definition.getNode(id));
}
/**
* create a flow path for execution after of the given event id
*
* @param id of the event to continue after
* @return the flow after the given event
*/
public FlowPathDefinition on(Identifier id) {
return new FlowPathDefinition(definition, definition.getNode(id, AbstractEventDefinition.class));
}
/**
* create a flow path merging the execution after the given nodes
* by introducing a new merge node to which the nodes connect.
*
* execution will continue for every token that triggers this merge node.
*
* Example:
*
* <pre>
* {@code {@literal @}Override public void define() {
start(event(id(START)))
.choice(id(CHOICE))
.when(eq(constant(x) ,"test")).execute(task(id(TASK)))
.or()
.when(eq(constant(x), "test2")).execute(task(id(TASK2)));
merge(id(MERGE), id(TASK), id(TASK2))
.end(event(id(END)));
}
* }
* </pre>
*
* @param mergeId the id of the new merge node
* @param ids of the nodes to be merged
* @return the flow path beginning with the merge node
*/
public FlowPathDefinition merge(Identifier mergeId, Identifier... ids) {
MergeDefinition mergeDefinition = new MergeDefinition().id(mergeId);
definition.addNode(mergeDefinition);
connectToNode(mergeDefinition, Arrays.asList(ids));
return new FlowPathDefinition(definition, mergeDefinition);
}
public FlowPathDefinition merge(FlowNodeDefinition<?>... nodeDefinitions) {
return merge(id(generateId()), nodeDefinitions);
}
public FlowPathDefinition merge(Identifier id, FlowNodeDefinition<?>... nodeDefinitions) {
MergeDefinition mergeDefinition = new MergeDefinition().id(id);
definition.addNode(mergeDefinition);
connectToNode(mergeDefinition, idList(nodeDefinitions));
return new FlowPathDefinition(definition, mergeDefinition);
}
/**
* create a flow path joining the execution after the given nodes
* by introducing a new merge join to which the nodes connect.
*
* execution will continue only if a token exists for
* every joined node during the execution of the join node.
*
* Example:
*
* <pre>
* {@code {@literal @}Override public void define() {
start(event(id(StartEvent)))
.parallel(id(Parallel))
.execute(task(id(SecondTask)))
.and()
.execute(task(id(ThirdTask)));
join(id(Join), id(SecondTask), id(ThirdTask))
.end(event(id(EndEvent2)));
}
* }
* </pre>
*
* @param joinId the id of the new join node
* @param ids of the nodes to be merged
* @return the flow path beginning with the join node
*/
public FlowPathDefinition join(Identifier joinId, Identifier... ids) {
JoinDefinition joinDefinition = new JoinDefinition().id(joinId);
definition.addNode(joinDefinition);
connectToNode(joinDefinition, Arrays.asList(ids));
return new FlowPathDefinition(definition, joinDefinition);
}
public FlowPathDefinition join(FlowNodeDefinition<?>... flowNodeDefinitions) {
return join(id(generateId()), flowNodeDefinitions);
}
public FlowPathDefinition join(Identifier joinId, FlowNodeDefinition<?>... flowNodeDefinitions) {
JoinDefinition joinDefinition = new JoinDefinition().id(joinId);
definition.addNode(joinDefinition);
connectToNode(joinDefinition, idList(flowNodeDefinitions));
return new FlowPathDefinition(definition, joinDefinition);
}
List<Identifier> idList(FlowNodeDefinition<?>... flowNodeDefinitions) {
List<Identifier> identifiers = new ArrayList<Identifier>();
for (FlowNodeDefinition<?> nodeDefinition: flowNodeDefinitions) {
identifiers.add(nodeDefinition.getId());
}
return identifiers;
}
private void connectToNode(FlowNodeDefinition node, List<Identifier> idsToConnect) {
for (Identifier id : idsToConnect) {
definition.getNode(id).addOutgoing(node);
definition.getNode(node.getId()).addIncoming(definition.getNode(id));
}
}
public static TaskDefinition task(Identifier id) {
return new TaskDefinition().id(id).display(id.toString());
}
/**
* create a task definition with the given task to be executed
*
* Example:
*
* <pre>
* {@code {@literal @}Override
public void define() {
Task callee = new Task() {
{@literal @}Override
public void execute(ExecutionContext o) {
}
};
start(event(id("start")), every(5, TimeUnit.SECONDS))
.execute(task(id("task"), callee));
}
* }
* </pre>
*
* @param id the task id
* @param callee the callee task
* @return the task definition with the given task
*/
public static TaskDefinition task(Identifier id, Task callee) {
return new TaskDefinition().id(id).display(id.toString()).call(new HandlerCallDefinition(callee));
}
public static EventDefinition event(Identifier id) {
return new EventDefinition().id(id).display(id.toString());
}
public static StringExpression expression(String expression) {
return new StringExpression(expression);
}
public static <T> Value<T> constant(T value) {
return new Value<T>(value);
}
public static Property<Object> property(Identifier id) {
return new Property<Object>(id, Object.class);
}
public static <T> Property<T> property(Identifier id, Class<T> clazz) {
return new Property<T>(id, clazz);
}
public <T> T value(Identifier id, Class<T> clazz) {
return (T) value(new Property(id, clazz));
}
public <T> T value(Property property, Class<T> clazz) {
return (T) value(property);
}
public <T> T value(Property<T> property) {
serviceCallInvocation.argument(property);
return null;
}
/**
* add a parameter to the invocation arguments of a proxy method call definition.
*
* @param value the parameter value to add
* @param <T> the type of the value
* @return null of type T
*/
public <T> T value(Value<T> value) {
serviceCallInvocation.argument(value);
return null;
}
public static EqualsExpression<Expression, Value<Object>> eq(Expression actual, Object expected) {
return new ExpressionBuilder<Expression>(actual).isEqualTo(expected);
}
public static EqualsExpression<Expression, Value<Boolean>> isTrue(Expression actual) {
return new ExpressionBuilder<Expression>(actual).isEqualTo(true);
}
public static <T extends Predicate> PredicateExpression<T> predicate(T predicate) {
return new PredicateExpression<T>(predicate);
}
public static InvokeDefinition method(Class<?> clazz) {
return new InvokeDefinition(clazz);
}
public CallDefinition method(Object returnValueOfProxyInvocation) {
return serviceCallInvocation.createCallDefinitionFromCurrentStack();
}
/**
* create a service proxy to be used for type-safe call definitions.
*
* Example:
*
* <pre>
* {@code {@literal @}Override
public void define() {
TestService testService = service(TestService.class);
start(event(id(START)))
.execute(task(id(TASK)).call(method(testService.echo(testService.getString()))))
.end(event(id(END)));
}
* }
* </pre>
*
* @param clazz the interface type to proxy
* @param <T> the type of the interface
* @return a service proxy of type T
*/
public <T> T service(Class<T> clazz) {
return serviceCallInvocation.createServiceProxy(clazz);
}
public static ExpressionBuilder<Value<CallDefinition>> resultOf(CallDefinition methodCall) {
return new ExpressionBuilder<Value<CallDefinition>>(new Value<CallDefinition>(methodCall));
}
/**
* create a goal definition with the given id.
*
* Example:
*
* <pre>
* {@code {@literal @}Override
public void define() {
start(id("start"))
.execute(task(id("simpleTask"))
.retryAsync(true)
.goal(id("taskExecuted")))
.end(id("end"));
goal(id("taskExecuted")).check(predicate(new GoalPredicate<Void>() {
{@literal @}Override
public boolean isFulfilled(Void aVoid) {
return true;
}
}));
}
* }
* </pre>
*
* @param id the goal id
* @return the goal definition
*/
public static GoalDefinition goal(Identifier id) {
return new GoalDefinition().id(id);
}
public static GoalDefinition goal() {
return new GoalDefinition();
}
public static GoalDefinition check(PredicateExpression goalPredicate) {
return new GoalDefinition().check(goalPredicate);
}
public static GoalDefinition check(Predicate<?> goalPredicate) {
return check(new PredicateExpression<Predicate>(goalPredicate));
}
}