Em certos domínios, é comum existirem processos com regras bem definidas que podem ser descritos através de workflows. Os workflows podem ser definidos através de uma gramática que inclui quatro elementos principais: um comando executável, uma sequência para executar dois ou mais comandos seguidos, uma alternativa para executar um ou outro comando com base em uma condição e uma repetição que executará um mesmo comando até que uma condição seja satisfeita. Devido a essa forma estruturada e predefinida, alguns workflows permitem a automação de seus comandos. Outra característica é a simplicidade de visualização dos workflows por humanos através de fluxogramas, além da facilidade de eles serem entendidos por outros softwares se convertidos para um formato como XML. Dessa forma, o workflow torna-se compartilhável e pode ser reaproveitado, por exemplo, caso uma filial da empresa possua um processo semelhante cujo comportamento poderia também ser automatizado. A interface Action representa um dos elementos do fluxo de trabalho e define a operação accept que recebe um Visitor. A classe CommandAction representa um comando que pode ser executado no fluxo, enquanto a classe SequenceAction representa uma sequência de comandos e a classe abstrata ConditionalAction representa um comando que depende de uma condição, todas herdam de Action e, com exceção de ConditionalAction, implemenam a operação accept. WhileRepetitionAction, UntilRepetitionAction e AlternationAction herdam de ConditionalAction, onde as duas primeiras representam laços de repetição e a terceira uma alternação entre comandos, todas implementando a operação accept. A interface ActionVisitor define uma operação para cada tipo de elemento do fluxo, enquanto a classe InterpretingVisitor implementa ActionVisitor e suas operações. InterpretingVisitor também possui uma referência para um objeto que armazena o estado do alvo das tarefas do fluxo, representado pela classe Object. A classe XMLGeneratingVisitor também implementa ActionVisitor e define as suas operações com o objetivo de gerar o XML do workflow. A classe Client é responsável por instanciar o workflow e o Visitor. As interfaces Command, referenciada por CommandAction, e BooleanExpression, referenciada por ConditionalAction, permitem implementar os comandos e as condições que podem ser definidas no workflow.
package visitor;
// Interface que define uma ação de um workflow
public interface Action {
// Definição da operação
public void accept(ActionVisitor visitor);
}
package visitor;
// Definição do Visitor do workflow, onde cara operação visit atende a um dos tipos de ação
public interface ActionVisitor {
public void visit(CommandAction node);
public void visit(SequenceAction node);
public void visit(AlternationAction node);
public void visit(WhileRepetitionAction node);
public void visit(UntilRepetitionAction node);
}
package visitor;
// Definição de um comando realizado no alvo do workflow
public interface Command {
public void execute(Object context);
public Object getValue();
}
package visitor;
// Definição da interface para uma condição booleana
public interface BooleanExpression {
public boolean evaluate(Object context);
public Object getParameter();
}
package visitor;
// Implementação da ação que define um laço de repetição enquanto-faça
public class WhileRepetitionAction extends ConditionalAction {
public WhileRepetitionAction(Action action, BooleanExpression condition) {
super(action, condition);
}
// Implementação da operação que chama o Visitor
public void accept(ActionVisitor visitor) {
visitor.visit(this);
}
}
package visitor;
// Implementação de uma ação terminal que realiza um comando
public class CommandAction implements Action {
// Comando que será executado
private final Command command;
public CommandAction(Command command) {
this.command = command;
}
public Command getCommand() {
return command;
}
// Implementação da operação que chama pelo Visitor
public void accept(ActionVisitor visitor) {
visitor.visit(this);
}
}
package visitor;
// Ação que define uma sequência de duas ações no workflow
public class SequenceAction implements Action {
// Primeira ação que será executada
private final Action firstAction;
// Segunda ação que será executada
private final Action secondAction;
public SequenceAction(Action firstAction, Action secondAction) {
this.firstAction = firstAction;
this.secondAction = secondAction;
}
public Action getFirstAction() {
return firstAction;
}
public Action getSecondAction() {
return secondAction;
}
// Implementação da operação que chama o Visitor
public void accept(ActionVisitor visitor) {
visitor.visit(this);
}
}
package visitor;
// Ação de alternativa do workflow
public class AlternationAction extends ConditionalAction {
// Ação realizada caso a condição seja falsa (se for null, nenhuma ação é executada)
private Action falseAction=null;
public AlternationAction(Action action, BooleanExpression condition, Action falseAction) {
super(action, condition);
this.falseAction = falseAction;
}
public AlternationAction(Action action, BooleanExpression condition) {
super(action, condition);
}
public Action getFalseAction() {
return falseAction;
}
// Implementação da operação accept que chama o Visitor
public void accept(ActionVisitor visitor) {
visitor.visit(this);
}
}
package visitor;
import visitor.contexts.WaterPumperAgent;
// Implementação do Visitor de interpretação do workflow
public class InterpretingVisitor implements ActionVisitor {
// O contexto do Visitor é definido nos atributos
private WaterPumperAgent context;
public InterpretingVisitor(WaterPumperAgent context) {
this.context = context;
}
// Para uma ação, um elemento terminal, a ação é executada
public void visit(CommandAction node) {
node.getCommand().execute(this.context);
}
// Para uma sequência da ações, a operação que chama o visitor é chamad apara cada uma delas
public void visit(SequenceAction node) {
node.getFirstAction().accept(this);
node.getSecondAction().accept(this);
}
// Para uma alternação entre ações, uma ou outra ação é chamada, dependendo do resultado da condição
public void visit(AlternationAction node) {
if(node.getCondition().evaluate(this.context))
node.getAction().accept(this);
else {
Action falseAction = node.getFalseAction();
if(falseAction != null) falseAction.accept(this);
}
}
// Para um laço enquanto-faça, enquanto a condição for verdadeira a ação é chamada
public void visit(WhileRepetitionAction node){
while (node.getCondition().evaluate(this.context)){
node.getAction().accept(this);
}
}
// Para um laço faça-enquanto, enquanto a condição for falsa a ação é chamada
public void visit(UntilRepetitionAction node){
do {
node.getAction().accept(this);
} while (!node.getCondition().evaluate(this.context));
}
}
package visitor;
public class XMLGeneratingVisitor implements ActionVisitor {
// Para que a string seja acumulada, um atributo XMLString é definido
private String XMLString="";
public String getXMLString() {
return XMLString;
}
// Gera o XML para a ação terminal
public void visit(CommandAction node) {
XMLString = XMLString.concat("");
String commandName = node.getCommand().getClass().getSimpleName();
XMLString = XMLString.concat(
"<" + commandName + ">" +
node.getCommand().getValue().toString() +
"" + commandName + ">");
XMLString = XMLString.concat(" ");
}
// Gera o XML para as duas ações
public void visit(SequenceAction node) {
XMLString = XMLString.concat("");
node.getFirstAction().accept(this);
node.getSecondAction().accept(this);
XMLString = XMLString.concat(" ");
}
// Gera o XML para a condição, a ação caso verdadeira e a ação caso falsa
public void visit(AlternationAction node) {
XMLString = XMLString.concat("");
String conditionName = node.getCondition().getClass().getSimpleName();
XMLString = XMLString.concat(
"<" + conditionName + ">" +
node.getCondition().getParameter().toString() +
"" + conditionName + ">");
node.getAction().accept(this);
if(node.getFalseAction() != null) {
XMLString = XMLString.concat("");
node.getFalseAction().accept(this);
XMLString = XMLString.concat(" ");
}
XMLString= XMLString.concat(" ");
}
// Gera o XML para a condição e para a ação
public void visit(WhileRepetitionAction node){
XMLString = XMLString.concat("");
String conditionName = node.getCondition().getClass().getSimpleName();
XMLString = XMLString.concat(
"<" + conditionName + ">" +
node.getCondition().getParameter().toString() +
"" + conditionName + ">");
node.getAction().accept(this);
XMLString = XMLString.concat(" ");
}
// Gera o XML para a condição e para a ação
public void visit(UntilRepetitionAction node){
XMLString = XMLString.concat("");
String conditionName = node.getCondition().getClass().getSimpleName();
XMLString = XMLString.concat(
"<" + conditionName + ">" +
node.getCondition().getParameter().toString() +
"" + conditionName + ">");
node.getAction().accept(this);
XMLString = XMLString.concat(" ");
}
}
package visitor;
// Classe abstrata que define uma ação que possui uma condição
public abstract class ConditionalAction implements Action{
// Ação que será realizada
private final Action action;
// Condição que definirá se a ação será executada ou não
private final BooleanExpression condition;
public ConditionalAction(Action action, BooleanExpression condition) {
this.action = action;
this.condition = condition;
}
public Action getAction() {
return action;
}
public BooleanExpression getCondition() {
return condition;
}
}
package visitor;
// Implementação da ação que define um laço de repetição faça-enquanto
public class UntilRepetitionAction extends ConditionalAction {
public UntilRepetitionAction(Action action, BooleanExpression condition) {
super(action, condition);
}
// Implementação da operação que chama o Visitor
public void accept(ActionVisitor visitor) {
visitor.visit(this);
}
}
package visitor;
import visitor.contexts.WaterPumperAgent;
import visitor.commands.AddTemperatureCommand;
import visitor.commands.AddWaterLevelCommand;
import visitor.commands.SetPumperOnCommand;
import visitor.expressions.MaxTemperatureExpression;
import visitor.expressions.MaxWaterLevelExpression;
import visitor.expressions.PumperOnExpression;
public class Client {
public static void main(String[] args) {
// Retorna uma ação inicial de um workflow
Action workflow = defineWorkflow();
// Instancia o context (objeto alvo do workflow)
WaterPumperAgent agent = new WaterPumperAgent(false, 0, 20);
// Instância do Visitor que interpretará o workflow
InterpretingVisitor interpretingVisitor = new InterpretingVisitor(agent);
// Exibe as informações do context antes da interpretação
System.out.println("Agent status:\nPumper on: " + agent.isPumperOn() +
"\nWater Level: " + agent.getWaterLevel() +
"\nTemperature: " + agent.getTemperature() + "\n");
// Chamada do interpretingVisitor para interpretar o workflow
workflow.accept(interpretingVisitor);
// Exibe as informações do context após a interpretação
System.out.println("Agent status:\nPumper on: " + agent.isPumperOn() +
"\nWater Level: " + agent.getWaterLevel() +
"\nTemperature: " + agent.getTemperature() + "\n");
// Instância do Visitor que gerará o XML do workflow
XMLGeneratingVisitor xmlVisitor = new XMLGeneratingVisitor();
// Chamada do xmlVisitor que gerará o XML
workflow.accept(xmlVisitor);
System.out.println(xmlVisitor.getXMLString());
}
public static Action defineWorkflow() {
// Inicio do workflow
// Se a bomba estiver desligada, ligar bomba. Se não, fim do workflow.
// Enquanto o nivel da agua estiver abaixo de 10, aumentar o nivel da agua em 1 e a temperatura em 5
// Caso a temperatura seja superior a 50, desligar a bomba
// Fim do workflow
//commands
SetPumperOnCommand pumperOnCommand = new SetPumperOnCommand(true);
SetPumperOnCommand pumperOffCommand = new SetPumperOnCommand(false);
AddWaterLevelCommand addWaterLevelCommand = new AddWaterLevelCommand(1);
AddTemperatureCommand addTemperatureCommand = new AddTemperatureCommand(5);
//expressions
PumperOnExpression pumperNotOnExpression = new PumperOnExpression(false);
MaxWaterLevelExpression maxWaterLevelExpression = new MaxWaterLevelExpression(10);
MaxTemperatureExpression maxTemperatureExpression = new MaxTemperatureExpression(50);
// Definição do Workflow
// Elementos terminais
CommandAction turnOnPumper = new visitor.CommandAction(pumperOnCommand);
CommandAction turnOffPumper = new visitor.CommandAction(pumperOffCommand);
CommandAction addToTemperature = new visitor.CommandAction(addTemperatureCommand);
CommandAction addWaterLevel = new CommandAction(addWaterLevelCommand);
// Elementos não terminais
SequenceAction addWaterLevelThenAddTemperature = new visitor.SequenceAction(addWaterLevel, addToTemperature);
AlternationAction verifyTemperature = new visitor.AlternationAction(turnOffPumper, maxTemperatureExpression);
WhileRepetitionAction doUntilMaxWaterLevel = new WhileRepetitionAction(addWaterLevelThenAddTemperature, maxWaterLevelExpression);
SequenceAction startPumperProcess = new visitor.SequenceAction(doUntilMaxWaterLevel, verifyTemperature);
SequenceAction startWorkflow = new SequenceAction(turnOnPumper, startPumperProcess);
return new AlternationAction(startWorkflow, pumperNotOnExpression);
}
}
package visitor.commands;
import visitor.Command;
import visitor.contexts.WaterPumperAgent;
public class AddTemperatureCommand implements Command {
private float temperature;
public AddTemperatureCommand(float temperature) {
this.temperature = temperature;
}
public void execute(Object context) {
WaterPumperAgent agent = (WaterPumperAgent) context;
agent.setTemperature(agent.getTemperature() + this.temperature);
}
public Object getValue() {
return this.temperature;
}
}
package visitor.commands;
import visitor.Command;
import visitor.contexts.WaterPumperAgent;
public class AddWaterLevelCommand implements Command {
private int level;
public AddWaterLevelCommand(int level) {
this.level = level;
}
public void execute(Object context) {
WaterPumperAgent agent = (WaterPumperAgent) context;
agent.setWaterLevel(agent.getWaterLevel() + this.level);
}
public Object getValue() {
return this.level;
}
}
package visitor.commands;
import visitor.Command;
import visitor.contexts.WaterPumperAgent;
public class SetPumperOnCommand implements Command {
private boolean pumperOn;
public SetPumperOnCommand(boolean pumperOn) {
this.pumperOn = pumperOn;
}
public void execute(Object context) {
WaterPumperAgent agent = (WaterPumperAgent)context;
agent.setPumperOn(this.pumperOn);
}
public Object getValue() {
return this.pumperOn;
}
}
package visitor.expressions;
import visitor.BooleanExpression;
import visitor.contexts.WaterPumperAgent;
public class MaxTemperatureExpression implements BooleanExpression {
private final float temperature;
public MaxTemperatureExpression(float temperature) {
this.temperature = temperature;
}
public boolean evaluate(Object context) {
WaterPumperAgent agent = (WaterPumperAgent) context;
return agent.getTemperature() >= temperature;
}
public Object getParameter() {
return this.temperature;
}
}
package visitor.expressions;
import visitor.BooleanExpression;
import visitor.contexts.WaterPumperAgent;
public class MaxWaterLevelExpression implements BooleanExpression {
private final int waterLevel;
public MaxWaterLevelExpression(int waterLevel) {
this.waterLevel = waterLevel;
}
public boolean evaluate(Object context) {
WaterPumperAgent agent = (WaterPumperAgent) context;
return agent.getWaterLevel() < waterLevel;
}
public Object getParameter() {
return this.waterLevel;
}
}
package visitor.expressions;
import visitor.BooleanExpression;
import visitor.contexts.WaterPumperAgent;
public class PumperOnExpression implements BooleanExpression {
private final boolean pumperIsOn;
public PumperOnExpression(boolean pumperIsOn) {
this.pumperIsOn = pumperIsOn;
}
public boolean evaluate(Object context) {
WaterPumperAgent agent = (WaterPumperAgent)context;
return agent.isPumperOn() == pumperIsOn;
}
public Object getParameter() {
return this.pumperIsOn;
}
}
package visitor.contexts;
public class WaterPumperAgent {
private boolean pumperOn;
private int waterLevel;
private float temperature;
public WaterPumperAgent(boolean pumperOn, int waterLevel, float temperature) {
this.pumperOn = pumperOn;
this.waterLevel = waterLevel;
this.temperature = temperature;
}
public boolean isPumperOn() {
return pumperOn;
}
public void setPumperOn(boolean pumperOn) {
this.pumperOn = pumperOn;
}
public int getWaterLevel() {
return waterLevel;
}
public void setWaterLevel(int waterLevel) {
this.waterLevel = waterLevel;
}
public float getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
}
}