From 581a9023117ebb38200568adf8ddd1ba84c6fbbd Mon Sep 17 00:00:00 2001 From: Pierre-yves-monnet Date: Wed, 27 Nov 2024 20:38:31 -0800 Subject: [PATCH 1/5] with UnitTest implementation --- README.md | 29 +- doc/scenarioreference/C8CrawlUrl.bpmn | 570 +++++ doc/unittestscenario/SendUnitTestCommand.rest | 21 + .../resources/ScoreAcceptanceScn.json | 94 + pom.xml | 2 +- .../org/camunda/automator/AutomatorAPI.java | 238 +- .../automator/AutomatorApplication.java | 10 +- .../org/camunda/automator/AutomatorCLI.java | 438 ++-- .../org/camunda/automator/AutomatorRest.java | 271 +++ .../automator/bpmnengine/BpmnEngine.java | 483 ++-- .../BpmnEngineConfigurationInstance.java | 90 +- .../bpmnengine/BpmnEngineFactory.java | 76 +- .../camunda7/BpmnEngineCamunda7.java | 1029 ++++---- ...kCompleteJobExceptionHandlingStrategy.java | 32 +- ...hmarkStartPiExceptionHandlingStrategy.java | 38 +- .../camunda8/BpmnEngineCamunda8.java | 2079 ++++++++--------- .../camunda8/StatisticsCollector.java | 52 +- .../refactoring/RefactoredCommandWrapper.java | 122 +- .../bpmnengine/dummy/BpmnEngineDummy.java | 288 +-- .../configuration/BpmnEngineList.java | 1086 +++++---- .../ConfigurationServersEngine.java | 156 +- .../configuration/ConfigurationStartup.java | 260 +-- .../automator/content/ContentManager.java | 104 + .../content/ContentRestController.java | 40 + .../automator/definition/Scenario.java | 352 ++- .../definition/ScenarioDeployment.java | 30 +- .../definition/ScenarioExecution.java | 258 +- .../definition/ScenarioFlowControl.java | 76 +- .../automator/definition/ScenarioStep.java | 543 +++-- .../automator/definition/ScenarioTool.java | 40 +- .../definition/ScenarioVerification.java | 99 +- .../definition/ScenarioVerificationBasic.java | 2 +- .../ScenarioVerificationPerformance.java | 62 + .../definition/ScenarioVerificationTask.java | 66 +- .../ScenarioVerificationVariable.java | 18 +- .../definition/ScenarioWarmingUp.java | 34 +- .../automator/engine/AutomatorException.java | 38 +- .../automator/engine/RunParameters.java | 484 ++-- .../camunda/automator/engine/RunResult.java | 690 +++--- .../camunda/automator/engine/RunScenario.java | 423 ++-- .../automator/engine/RunZeebeOperation.java | 48 +- .../automator/engine/SchedulerExecution.java | 32 +- .../flow/CreateProcessInstanceThread.java | 362 +-- .../engine/flow/FixedBackoffSupplier.java | 16 +- .../automator/engine/flow/RunObjectives.java | 508 ++-- .../engine/flow/RunScenarioFlowBasic.java | 104 +- .../flow/RunScenarioFlowServiceTask.java | 428 ++-- .../flow/RunScenarioFlowStartEvent.java | 330 +-- .../engine/flow/RunScenarioFlowUserTask.java | 214 +- .../engine/flow/RunScenarioFlows.java | 704 +++--- .../engine/flow/RunScenarioWarmingUp.java | 556 ++--- .../engine/unit/RunScenarioUnit.java | 356 +-- .../unit/RunScenarioUnitServiceTask.java | 118 +- .../unit/RunScenarioUnitStartEvent.java | 64 +- .../engine/unit/RunScenarioUnitUserTask.java | 122 +- .../engine/unit/RunScenarioVerification.java | 264 ++- .../automator/services/AutomatorStartup.java | 435 ++-- .../automator/services/ServiceAccess.java | 30 +- .../services/ServiceDataOperation.java | 64 +- .../services/dataoperation/DataOperation.java | 92 +- .../DataOperationGenerateList.java | 64 +- .../DataOperationGenerateUniqueID.java | 62 +- .../dataoperation/DataOperationLoadFile.java | 70 +- .../DataOperationStringToDate.java | 106 +- src/main/resources/application.yaml | 5 +- 65 files changed, 8640 insertions(+), 7337 deletions(-) create mode 100644 doc/scenarioreference/C8CrawlUrl.bpmn create mode 100644 doc/unittestscenario/SendUnitTestCommand.rest create mode 100644 doc/unittestscenario/resources/ScoreAcceptanceScn.json create mode 100644 src/main/java/org/camunda/automator/AutomatorRest.java create mode 100644 src/main/java/org/camunda/automator/content/ContentManager.java create mode 100644 src/main/java/org/camunda/automator/content/ContentRestController.java create mode 100644 src/main/java/org/camunda/automator/definition/ScenarioVerificationPerformance.java diff --git a/README.md b/README.md index fbd8f57..62e0765 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ What Process-Automator do: Process-Automator do not * Execute service task in unit-scenario -* It is not expected to throw a BPMN Message in the flow: Process-Automator piloted a real system. +* It is not expected to catch a BPMN Message in the flow: Process-Automator piloted a real system. ## Requirement @@ -70,7 +70,6 @@ A scenario can be executed on a Camunda 7 or a Camunda 8 server. Process-Automat ## Different usages - ### Unit test and regression, unit performance test (unit-scenario) The unit scenario describes one process instance execution. Creation and user tasks are described. @@ -94,6 +93,32 @@ Visit [Load Test Scenario](doc/loadtestscenario/README.md) and the [Load test Tu ## Scenario +Process-Execution-Automator execute a scenario. + +A scenario define +* a list of robots. A robot + * Create process instances, + * Execute service task, + * Execute user tasks +* some objectives + * How many process instance must be created + * How many service task has to be executed + * Time to execute a section in the process +* a warm up section + +Due to the “backoff strategy”, workers need to be wake up + +Robots can be registered in the same pod +![C8CrawlUrl.png](doc/scenarioreference/C8CrawlUrl.png) + +Or multiple pods can be defined. Each pod run the process-execution-automator on the same scenario, but limit which robots are executed +![C8CrawlUrl-multiple-pods.png](doc/scenarioreference/C8CrawlUrl-multiple-pods.png) + + +For unit testing, the execution is different. The pod is started, but don't start immediately the scenario. + + + This section references all the information to build a scenario. Visit [Scenario reference](doc/scenarioreference/README.md) diff --git a/doc/scenarioreference/C8CrawlUrl.bpmn b/doc/scenarioreference/C8CrawlUrl.bpmn new file mode 100644 index 0000000..5d946a7 --- /dev/null +++ b/doc/scenarioreference/C8CrawlUrl.bpmn @@ -0,0 +1,570 @@ + + + + + + + + + + + + + + + + + + + + + + Flow_17n6ju8 + + + + + + Flow_17n6ju8 + Flow_03a245y + + + Flow_191cul5 + + + Flow_03a245y + Flow_191cul5 + + + + + + + Flow_0ue17j2 + + + + + Flow_06qswzj + + + + + + Flow_0ue17j2 + Flow_1dpfwyb + + + + + + Flow_0zrd1qi + Flow_0zhmx9x + + + + + + Flow_0zhmx9x + Flow_11raeuz + Flow_1llqly1 + + + + + + Flow_1llqly1 + Flow_06qswzj + + + + + + Flow_1dpfwyb + Flow_0zrd1qi + Flow_0af83lu + + + + =urlNotFound + + + Flow_124nqht + + + + Flow_0af83lu + Flow_0x26tsm + + + Flow_0x26tsm + Flow_124nqht + Flow_15yyn2a + + + + =processAcceptable + + + + + + Flow_15yyn2a + Flow_11raeuz + + + + 0 s + + + + 10 s + + + + + + 1 s + + + 1 s + + + 5 s + + + + + + + + + Flow_1q4xt3v + + PT20M + + + + Flow_1q4xt3v + + + + + Loop 20 + + + 2 s + + + 400PI/mn + + + + + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + DataObjectReference_16o9ddc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/unittestscenario/SendUnitTestCommand.rest b/doc/unittestscenario/SendUnitTestCommand.rest new file mode 100644 index 0000000..a41ae51 --- /dev/null +++ b/doc/unittestscenario/SendUnitTestCommand.rest @@ -0,0 +1,21 @@ +### POST Request +POST http://localhost:8381/api/unittest/run?name=ScoreAcceptanceScn&server=Camunda8Ruby&wait=false +Content-Type: application/json + +{ +} + + +### POST Request +GET http://localhost:8381/api/unittest/get?id=1732767184446 +Content-Type: application/json + +{ +} + +### GET LIST +GET http://localhost:8381/api/unittest/list +Content-Type: application/json + +{ +} diff --git a/doc/unittestscenario/resources/ScoreAcceptanceScn.json b/doc/unittestscenario/resources/ScoreAcceptanceScn.json new file mode 100644 index 0000000..cdd80e7 --- /dev/null +++ b/doc/unittestscenario/resources/ScoreAcceptanceScn.json @@ -0,0 +1,94 @@ +{ + "name": "ScoreAcceptance", + "processId": "ScoreAcceptance", + "serverType": "Camunda_8", + "typeScenario": "UNIT", + "executions": [ + { + "name": "ScoreAccepted", + "steps": [ + { + "type": "STARTEVENT", + "taskId": "ScoreApplication", + "processId": "ScoreAcceptance", + "variables": { + "score": 245 + } + } + ], + "verifications": { + "activities": [ + { + "type": "ActGetScore", + "taskId": "ActGetScore" + }, + { + "description": "we accepted the application with this amount", + "type": "TASK", + "taskId": "ActSendAcceptation" + }, + { + "type": "ENDEVENT", + "taskId": "EndAccepted" + } + ], + "variables": [ + { + "description": "we accepted the application with this amount", + "name": "accepted", + "value": true + } + ], + "performances": [ + { + "description": "From get Score to the End Event, less than 200 ms", + "fromFlowNode": "ActGetScore", + "fromMarker": "begin", + "toFlowNode": "EndAccepted", + "toMarker": "end", + "duration": "PT0.2S" + }, + { + "description": "GetScore must be performed in less than 100 ms", + "fromFlowNode": "ActGetScore", + "fromMarker": "begin", + "toFlowNode": "ActGetScore", + "toMarker": "end", + "duration": "PT0.1S" + } + ] + } + }, + { + "name": "ScoreRejected", + "steps": [ + { + "type": "STARTEVENT", + "taskId": "ScoreApplication", + "processId": "ScoreAcceptance", + "variables": { + "score": 67 + } + } + ], + "verifications": { + "activities": [ + { + "type": "TASK", + "taskId": "ActSendRejection" + }, + { + "type": "ENDEVENT", + "taskId": "EndRejected" + } + ], + "variables": [ + { + "name": "accepted", + "value": false + } + ] + } + } + ] +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8be44aa..3a78b70 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.camunda.community.automator process-execution-automator - 1.7.1 + 1.8.0 diff --git a/src/main/java/org/camunda/automator/AutomatorAPI.java b/src/main/java/org/camunda/automator/AutomatorAPI.java index e4f7da0..7683984 100644 --- a/src/main/java/org/camunda/automator/AutomatorAPI.java +++ b/src/main/java/org/camunda/automator/AutomatorAPI.java @@ -25,129 +25,129 @@ @Component public class AutomatorAPI { - static Logger logger = LoggerFactory.getLogger(AutomatorAPI.class); - - @Autowired - ServiceAccess serviceAccess; - - /** - * Create an empty scenario. - * The scenario can be created from scratch by the caller - * - * @return the scenario - * see scenario class to create from scratch a scenario - */ - public Scenario createScenario() { - return new Scenario(); - } - - /** - * Load the scenario from a file - * - * @param scenarioFile file to read the scenario - * @return the scenario - * @throws AutomatorException if scenario can't be read - */ - public Scenario loadFromFile(File scenarioFile) throws AutomatorException { - return Scenario.createFromFile(scenarioFile); - } - - /** - * Create from an input Stream. - * - * @param scenarioInputStream inputStream - * @param origin origin of inputStream - * @return scenario - * @throws AutomatorException if scenario can't be read - */ - public Scenario loadFromInputStream(InputStream scenarioInputStream, String origin) throws AutomatorException { - return Scenario.createFromInputStream(scenarioInputStream, origin); - } - - /** - * Search the engine from the scenario - * - * @param scenario scenario - * @param bpmnEngineList different engine configuration - * @return the engine, null if no engine exist, an exception if the connection is not possible - */ - public BpmnEngine getBpmnEngineFromScenario(Scenario scenario, BpmnEngineList bpmnEngineList) - throws AutomatorException { - try { - - if (scenario.getServerName() != null) { - return getBpmnEngine(bpmnEngineList.getByServerName(scenario.getServerName()), true); - } - - return null; - } catch (AutomatorException e) { - logger.error("Can't connect the engine for the scenario [{}] serverName[{}]: {}", scenario.getName(), - scenario.getServerName(), e.getMessage()); - throw e; + static Logger logger = LoggerFactory.getLogger(AutomatorAPI.class); + + @Autowired + ServiceAccess serviceAccess; + + /** + * Create an empty scenario. + * The scenario can be created from scratch by the caller + * + * @return the scenario + * see scenario class to create from scratch a scenario + */ + public Scenario createScenario() { + return new Scenario(); } - } - - /** - * Execute a scenario - * - * @param bpmnEngine Access the Camunda engine. if null, then the value in the scenario are used - * @param runParameters parameters use to run the scenario - * @param scenario the scenario to execute - */ - public RunResult executeScenario(BpmnEngine bpmnEngine, RunParameters runParameters, Scenario scenario) { - RunScenario runScenario = null; - - try { - runScenario = new RunScenario(scenario, bpmnEngine, runParameters, serviceAccess); - } catch (Exception e) { - RunResult result = new RunResult(runScenario); - result.addError(null, "Initialization error"); - return result; + /** + * Load the scenario from a file + * + * @param scenarioFile file to read the scenario + * @return the scenario + * @throws AutomatorException if scenario can't be read + */ + public Scenario loadFromFile(File scenarioFile) throws AutomatorException { + return Scenario.createFromFile(scenarioFile); } - RunResult runResult = new RunResult(runScenario); - runResult.add(runScenario.runScenario()); - - return runResult; - } - - - /* ******************************************************************** */ - /* */ - /* Additional tool */ - /* */ - /* Deploy Process */ - /* Deploy a process in the server */ - /* ******************************************************************** */ - - public BpmnEngine getBpmnEngine(BpmnEngineList.BpmnServerDefinition serverDefinition, boolean logDebug) - throws AutomatorException { - return BpmnEngineFactory.getInstance().getEngineFromConfiguration(serverDefinition, logDebug); - } - - /** - * Deploy a process, bpmEngine is given by the caller - * - * @param bpmnEngine Engine to deploy - * @param runParameters parameters used to deploy the version - * @param scenario scenario - * @return the result object - */ - public RunResult deployProcess(BpmnEngine bpmnEngine, RunParameters runParameters, Scenario scenario) { - RunScenario runScenario = null; - try { - long begin = System.currentTimeMillis(); - runScenario = new RunScenario(scenario, bpmnEngine, runParameters, serviceAccess); - RunResult runResult = new RunResult(runScenario); - runResult.add(runScenario.runDeployment()); - runResult.addTimeExecution(System.currentTimeMillis() - begin); - return runResult; - } catch (Exception e) { - RunResult result = new RunResult(runScenario); - result.addError(null, "Process deployment error error " + e.getMessage()); - return result; + /** + * Create from an input Stream. + * + * @param scenarioInputStream inputStream + * @param origin origin of inputStream + * @return scenario + * @throws AutomatorException if scenario can't be read + */ + public Scenario loadFromInputStream(InputStream scenarioInputStream, String origin) throws AutomatorException { + return Scenario.createFromInputStream(scenarioInputStream, origin); } - } + /** + * Search the engine from the scenario + * + * @param scenario scenario + * @param bpmnEngineList different engine configuration + * @return the engine, null if no engine exist, an exception if the connection is not possible + */ + public BpmnEngine getBpmnEngineFromScenario(Scenario scenario, BpmnEngineList bpmnEngineList) + throws AutomatorException { + try { + + if (scenario.getServerName() != null) { + return getBpmnEngine(bpmnEngineList.getByServerName(scenario.getServerName()), true); + } + + return null; + } catch (AutomatorException e) { + logger.error("Can't connect the engine for the scenario [{}] serverName[{}]: {}", scenario.getName(), + scenario.getServerName(), e.getMessage()); + throw e; + } + + } + + /** + * Execute a scenario + * + * @param bpmnEngine Access the Camunda engine. if null, then the value in the scenario are used + * @param runParameters parameters use to run the scenario + * @param scenario the scenario to execute + */ + public RunResult executeScenario(BpmnEngine bpmnEngine, RunParameters runParameters, Scenario scenario) { + RunScenario runScenario = null; + + try { + runScenario = new RunScenario(scenario, bpmnEngine, runParameters, serviceAccess); + } catch (Exception e) { + RunResult result = new RunResult(runScenario); + result.addError(null, "Initialization error"); + return result; + } + + RunResult runResult = new RunResult(runScenario); + runResult.merge(runScenario.runScenario()); + + return runResult; + } + + + /* ******************************************************************** */ + /* */ + /* Additional tool */ + /* */ + /* Deploy Process */ + /* Deploy a process in the server */ + /* ******************************************************************** */ + + public BpmnEngine getBpmnEngine(BpmnEngineList.BpmnServerDefinition serverDefinition, boolean logDebug) + throws AutomatorException { + return BpmnEngineFactory.getInstance().getEngineFromConfiguration(serverDefinition, logDebug); + } + + /** + * Deploy a process, bpmEngine is given by the caller + * + * @param bpmnEngine Engine to deploy + * @param runParameters parameters used to deploy the version + * @param scenario scenario + * @return the result object + */ + public RunResult deployProcess(BpmnEngine bpmnEngine, RunParameters runParameters, Scenario scenario) { + RunScenario runScenario = null; + try { + long begin = System.currentTimeMillis(); + runScenario = new RunScenario(scenario, bpmnEngine, runParameters, serviceAccess); + RunResult runResult = new RunResult(runScenario); + runResult.merge(runScenario.runDeployment()); + runResult.addTimeExecution(System.currentTimeMillis() - begin); + return runResult; + } catch (Exception e) { + RunResult result = new RunResult(runScenario); + result.addError(null, "Process deployment error error " + e.getMessage()); + return result; + } + + } } diff --git a/src/main/java/org/camunda/automator/AutomatorApplication.java b/src/main/java/org/camunda/automator/AutomatorApplication.java index af39f83..7805309 100644 --- a/src/main/java/org/camunda/automator/AutomatorApplication.java +++ b/src/main/java/org/camunda/automator/AutomatorApplication.java @@ -9,11 +9,11 @@ public class AutomatorApplication { - public static void main(String[] args) { + public static void main(String[] args) { - SpringApplication.run(AutomatorApplication.class, args); - // thanks to Spring, the class AutomatorStartup.init() is active. - } - // https://docs.camunda.io/docs/components/best-practices/development/writing-good-workers/ + SpringApplication.run(AutomatorApplication.class, args); + // thanks to Spring, the class AutomatorStartup.init() is active. + } + // https://docs.camunda.io/docs/components/best-practices/development/writing-good-workers/ } diff --git a/src/main/java/org/camunda/automator/AutomatorCLI.java b/src/main/java/org/camunda/automator/AutomatorCLI.java index 479ac89..257785a 100644 --- a/src/main/java/org/camunda/automator/AutomatorCLI.java +++ b/src/main/java/org/camunda/automator/AutomatorCLI.java @@ -24,230 +24,230 @@ @Component public class AutomatorCLI implements CommandLineRunner { - public static boolean isRunningCLI = false; - static Logger logger = LoggerFactory.getLogger(AutomatorCLI.class); - @Autowired - AutomatorAPI automatorAPI; - @Autowired - BpmnEngineList engineConfiguration; - - @Autowired - ConfigurationStartup configurationStartup; - - public static void main(String[] args) { - isRunningCLI = true; - SpringApplication app = new SpringApplication(AutomatorCLI.class); - app.setBannerMode(Banner.Mode.OFF); - System.exit(SpringApplication.exit(app.run(args))); - } - - /* ******************************************************************** */ - /* */ - /* Usage */ - /* -c, --conf */ - /* configuration file contains connection to engine */ - /* */ - /* -e, --engine ConnectionUrlString */ - /* CAMUNDA7; */ - /* CAMUNDA8;CLOUD;;;; */ - /* CAMUNDA8;LOCAL;; */ - /* */ - /* -d, --debug */ - /* logs all steps */ - /* -n, --numberofexecution <number> */ - /* override the number of execution for the scenario */ - /* -r, --recursive */ - /* all *.json in the folder and sub-folder are monitored and executed*/ - /* */ - /* run <scenarioFile> */ - /* */ - /* ******************************************************************** */ - - private static void printUsage() { - logOutLn("Usage: <option> <action> <parameter>"); - logOutLn(" -s, --server <serverName>"); - logOutLn(" Which server to use in the configuration"); - logOutLn(" -e, --engine ConnectionUrlString"); - logOutLn(" CAMUNDA7;<URL>"); - logOutLn(" CAMUNDA8;CLOUD;<region>;<clusterId>;<clientIs>;<clientSecret>"); - logOutLn(" CAMUNDA8;LOCAL;<gateway>;<plaintext>"); - logOutLn(" -l, --level <DEBUG|COMPLETE|MONITORING|MAIN|NOTHING>"); - logOutLn(" Define the level of log (MONITORING is the default)"); - logOutLn(" -n, --numberofexecution <number>"); - logOutLn(" override the number of execution for the scenario"); - logOutLn(" -d, --deploy <TRUE|FALSE>"); - logOutLn(" Allow deployment of process is defined in the scenario (default is TRUE)"); - - logOutLn(" -x, --execute"); - logOutLn(" execute the scenario"); - logOutLn(" -v, --verification"); - logOutLn(" verify the scenario"); - logOutLn(" -f, --fullreport"); - logOutLn(" Full report"); - - logOutLn(""); - logOutLn("ACTIONS: "); - logOutLn(" run <scenarioFile>"); - logOutLn(" execute one scenario"); - logOutLn(" recursive <folder>"); - logOutLn(" all *.json in the folder and sub-folder are monitored and executed"); - - } - - private static BpmnEngineList decodeConfiguration(String propertiesFileName) throws Exception { - throw new Exception("Not yet implemented"); - } - - private static List<File> detectRecursiveScenario(File folderRecursive) { - List<File> listFiles = new ArrayList<>(); - for (File file : folderRecursive.listFiles()) { - if (file.isDirectory()) { - listFiles.addAll(detectRecursiveScenario(file)); - } else if (file.getName().endsWith(".json")) { - listFiles.add(file); - } + public static boolean isRunningCLI = false; + static Logger logger = LoggerFactory.getLogger(AutomatorCLI.class); + @Autowired + AutomatorAPI automatorAPI; + @Autowired + BpmnEngineList engineConfiguration; + + @Autowired + ConfigurationStartup configurationStartup; + + public static void main(String[] args) { + isRunningCLI = true; + SpringApplication app = new SpringApplication(AutomatorCLI.class); + app.setBannerMode(Banner.Mode.OFF); + System.exit(SpringApplication.exit(app.run(args))); } - return listFiles; - } - - /** - * To reduce the number of warning - * - * @param message message to log out - */ - private static void logOutLn(String message) { - System.out.println(message); - } - - public void run(String[] args) { - if (!isRunningCLI) - return; - File scenarioFile = null; - File folderRecursive = null; - - RunParameters runParameters = new RunParameters(); - runParameters.setExecution(true) - .setServerName(configurationStartup.getServerName()) - .setLogLevel(configurationStartup.getLogLevelEnum()) - .setCreation(configurationStartup.isPolicyExecutionCreation()) - .setServiceTask(configurationStartup.isPolicyExecutionServiceTask()) - .setUserTask(configurationStartup.isPolicyExecutionUserTask()) - .setWarmingUp(configurationStartup.isPolicyExecutionWarmingUp()) - .setDeploymentProcess(configurationStartup.isPolicyDeployProcess()) - .setDeepTracking(configurationStartup.deepTracking()) - .setStartEventNbThreads(configurationStartup.getStartEventNbThreads()); - List<String> filterService = configurationStartup.getFilterService(); - if (filterService != null) { - runParameters.setFilterExecutionServiceTask(filterService); + + /* ******************************************************************** */ + /* */ + /* Usage */ + /* -c, --conf <file> */ + /* configuration file contains connection to engine */ + /* */ + /* -e, --engine ConnectionUrlString */ + /* CAMUNDA7;<URL> */ + /* CAMUNDA8;CLOUD;<region>;<clusterId>;<clientIs>;<clientSecret> */ + /* CAMUNDA8;LOCAL;<gateway>;<plaintext> */ + /* */ + /* -d, --debug */ + /* logs all steps */ + /* -n, --numberofexecution <number> */ + /* override the number of execution for the scenario */ + /* -r, --recursive */ + /* all *.json in the folder and sub-folder are monitored and executed*/ + /* */ + /* run <scenarioFile> */ + /* */ + /* ******************************************************************** */ + + private static void printUsage() { + logOutLn("Usage: <option> <action> <parameter>"); + logOutLn(" -s, --server <serverName>"); + logOutLn(" Which server to use in the configuration"); + logOutLn(" -e, --engine ConnectionUrlString"); + logOutLn(" CAMUNDA7;<URL>"); + logOutLn(" CAMUNDA8;CLOUD;<region>;<clusterId>;<clientIs>;<clientSecret>"); + logOutLn(" CAMUNDA8;LOCAL;<gateway>;<plaintext>"); + logOutLn(" -l, --level <DEBUG|COMPLETE|MONITORING|MAIN|NOTHING>"); + logOutLn(" Define the level of log (MONITORING is the default)"); + logOutLn(" -n, --numberofexecution <number>"); + logOutLn(" override the number of execution for the scenario"); + logOutLn(" -d, --deploy <TRUE|FALSE>"); + logOutLn(" Allow deployment of process is defined in the scenario (default is TRUE)"); + + logOutLn(" -x, --execute"); + logOutLn(" execute the scenario"); + logOutLn(" -v, --verification"); + logOutLn(" verify the scenario"); + logOutLn(" -f, --fullreport"); + logOutLn(" Full report"); + + logOutLn(""); + logOutLn("ACTIONS: "); + logOutLn(" run <scenarioFile>"); + logOutLn(" execute one scenario"); + logOutLn(" recursive <folder>"); + logOutLn(" all *.json in the folder and sub-folder are monitored and executed"); + } - Integer overrideNumberOfExecution = null; - int i = 0; - ACTION action = null; - String serverName = null; - try { - while (i < args.length) { - if ("-h".equals(args[i]) || "--help".equals(args[i])) { - printUsage(); - return; - } else if ("-s".equals(args[i]) || "--server".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : -c <ServerName>"); - serverName = args[i + 1]; - i++; - } else if ("-e".equals(args[i]) || "--engine".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : -e <ConnectionUrlString>"); - engineConfiguration = decodeConfiguration(args[i + 1]); - i++; - } else if ("-l".equals(args[i]) || "--level".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : -l <DEBUG|MONITORING|MAIN|NOTHING>"); - runParameters.setLogLevel(RunParameters.LOGLEVEL.valueOf(args[i + 1])); - i++; - } else if ("-n".equals(args[i]) || "--numberofexecution".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : n <numberofexecution>"); - overrideNumberOfExecution = Integer.parseInt(args[i + 1]); - i++; - } else if ("-d".equals(args[i]) || "--deploy".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : -d TRUE|FALSE"); - runParameters.setDeploymentProcess("TRUE".equalsIgnoreCase(args[i + 1])); - i++; - } else if ("-x".equals(args[i]) || "--execute".equals(args[i])) { - runParameters.setExecution(true); - } else if ("-v".equals(args[i]) || "--verification".equals(args[i])) { - runParameters.setVerification(true); - } else if ("-f".equals(args[i]) || "--fullreport".equals(args[i])) { - runParameters.setFullDetailsSynthesis(true); - } else if ("run".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : run <scenarioFile>"); - action = ACTION.RUN; - scenarioFile = new File(args[i + 1]); - i++; - } else if ("recursive".equals(args[i])) { - if (args.length < i + 1) - throw new AutomatorException("Bad usage : recursive <folder>"); - action = ACTION.RECURSIVE; - folderRecursive = new File(args[i + 1]); - i++; - } else { - printUsage(); - throw new AutomatorException("Bad usage : unknown parameters [" + args[i] + "]"); - } - i++; - } - - if (action == null) { - throw new AutomatorException("Bad usage : missing action (" + ACTION.RUN + ")"); - } - if (!runParameters.isExecution() && !runParameters.isVerification()) { - runParameters.setExecution(true); // default - } - - // get the correct server configuration - BpmnEngineList.BpmnServerDefinition serverDefinition = null; - - serverDefinition = engineConfiguration.getByServerName(serverName); - if (serverDefinition == null) { - throw new AutomatorException("Check configuration: Server name (from parameter)[" + serverName - + "] does not exist in the list of servers in application.yaml file"); - } - - long beginTime = System.currentTimeMillis(); - BpmnEngine bpmnEngine = automatorAPI.getBpmnEngine(serverDefinition, true); - - switch (action) { - case RUN -> { - Scenario scenario = automatorAPI.loadFromFile(scenarioFile); - BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngine(serverDefinition, true); - - // execution - RunResult scenarioExecutionResult = automatorAPI.executeScenario( - bpmnEngineScenario == null ? bpmnEngine : bpmnEngineScenario, runParameters, scenario); - - logger.info(scenarioExecutionResult.getSynthesis(runParameters.isFullDetailsSynthesis())); - } - case RECURSIVE -> { - List<File> listScenario = detectRecursiveScenario(folderRecursive); - for (File scenarioFileIndex : listScenario) { - Scenario scenario = automatorAPI.loadFromFile(scenarioFileIndex); - BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngine(serverDefinition, true); - RunResult scenarioExecutionResult = automatorAPI.executeScenario( - bpmnEngineScenario == null ? bpmnEngine : bpmnEngineScenario, runParameters, scenario); - - logger.info(scenarioExecutionResult.getSynthesis(false)); + + private static BpmnEngineList decodeConfiguration(String propertiesFileName) throws Exception { + throw new Exception("Not yet implemented"); + } + + private static List<File> detectRecursiveScenario(File folderRecursive) { + List<File> listFiles = new ArrayList<>(); + for (File file : folderRecursive.listFiles()) { + if (file.isDirectory()) { + listFiles.addAll(detectRecursiveScenario(file)); + } else if (file.getName().endsWith(".json")) { + listFiles.add(file); + } } - } - } - logger.info("That's all folks! " + (System.currentTimeMillis() - beginTime) + " ms."); + return listFiles; + } - } catch (Exception e) { - logger.error("Error during execution " + e); + /** + * To reduce the number of warning + * + * @param message message to log out + */ + private static void logOutLn(String message) { + System.out.println(message); } - } + public void run(String[] args) { + if (!isRunningCLI) + return; + File scenarioFile = null; + File folderRecursive = null; + + RunParameters runParameters = new RunParameters(); + runParameters.setExecution(true) + .setServerName(configurationStartup.getServerName()) + .setLogLevel(configurationStartup.getLogLevelEnum()) + .setCreation(configurationStartup.isPolicyExecutionCreation()) + .setServiceTask(configurationStartup.isPolicyExecutionServiceTask()) + .setUserTask(configurationStartup.isPolicyExecutionUserTask()) + .setWarmingUp(configurationStartup.isPolicyExecutionWarmingUp()) + .setDeploymentProcess(configurationStartup.isPolicyDeployProcess()) + .setDeepTracking(configurationStartup.deepTracking()) + .setStartEventNbThreads(configurationStartup.getStartEventNbThreads()); + List<String> filterService = configurationStartup.getFilterService(); + if (filterService != null) { + runParameters.setFilterExecutionServiceTask(filterService); + } + Integer overrideNumberOfExecution = null; + int i = 0; + ACTION action = null; + String serverName = null; + try { + while (i < args.length) { + if ("-h".equals(args[i]) || "--help".equals(args[i])) { + printUsage(); + return; + } else if ("-s".equals(args[i]) || "--server".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : -c <ServerName>"); + serverName = args[i + 1]; + i++; + } else if ("-e".equals(args[i]) || "--engine".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : -e <ConnectionUrlString>"); + engineConfiguration = decodeConfiguration(args[i + 1]); + i++; + } else if ("-l".equals(args[i]) || "--level".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : -l <DEBUG|MONITORING|MAIN|NOTHING>"); + runParameters.setLogLevel(RunParameters.LOGLEVEL.valueOf(args[i + 1])); + i++; + } else if ("-n".equals(args[i]) || "--numberofexecution".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : n <numberofexecution>"); + overrideNumberOfExecution = Integer.parseInt(args[i + 1]); + i++; + } else if ("-d".equals(args[i]) || "--deploy".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : -d TRUE|FALSE"); + runParameters.setDeploymentProcess("TRUE".equalsIgnoreCase(args[i + 1])); + i++; + } else if ("-x".equals(args[i]) || "--execute".equals(args[i])) { + runParameters.setExecution(true); + } else if ("-v".equals(args[i]) || "--verification".equals(args[i])) { + runParameters.setVerification(true); + } else if ("-f".equals(args[i]) || "--fullreport".equals(args[i])) { + runParameters.setFullDetailsSynthesis(true); + } else if ("run".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : run <scenarioFile>"); + action = ACTION.RUN; + scenarioFile = new File(args[i + 1]); + i++; + } else if ("recursive".equals(args[i])) { + if (args.length < i + 1) + throw new AutomatorException("Bad usage : recursive <folder>"); + action = ACTION.RECURSIVE; + folderRecursive = new File(args[i + 1]); + i++; + } else { + printUsage(); + throw new AutomatorException("Bad usage : unknown parameters [" + args[i] + "]"); + } + i++; + } + + if (action == null) { + throw new AutomatorException("Bad usage : missing action (" + ACTION.RUN + ")"); + } + if (!runParameters.isExecution() && !runParameters.isVerification()) { + runParameters.setExecution(true); // default + } + + // get the correct server configuration + BpmnEngineList.BpmnServerDefinition serverDefinition = null; + + serverDefinition = engineConfiguration.getByServerName(serverName); + if (serverDefinition == null) { + throw new AutomatorException("Check configuration: Server name (from parameter)[" + serverName + + "] does not exist in the list of servers in application.yaml file"); + } + + long beginTime = System.currentTimeMillis(); + BpmnEngine bpmnEngine = automatorAPI.getBpmnEngine(serverDefinition, true); + + switch (action) { + case RUN -> { + Scenario scenario = automatorAPI.loadFromFile(scenarioFile); + BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngine(serverDefinition, true); + + // execution + RunResult scenarioExecutionResult = automatorAPI.executeScenario( + bpmnEngineScenario == null ? bpmnEngine : bpmnEngineScenario, runParameters, scenario); + + logger.info(scenarioExecutionResult.getSynthesis(runParameters.isFullDetailsSynthesis())); + } + case RECURSIVE -> { + List<File> listScenario = detectRecursiveScenario(folderRecursive); + for (File scenarioFileIndex : listScenario) { + Scenario scenario = automatorAPI.loadFromFile(scenarioFileIndex); + BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngine(serverDefinition, true); + RunResult scenarioExecutionResult = automatorAPI.executeScenario( + bpmnEngineScenario == null ? bpmnEngine : bpmnEngineScenario, runParameters, scenario); + + logger.info(scenarioExecutionResult.getSynthesis(false)); + } + } + } + logger.info("That's all folks! " + (System.currentTimeMillis() - beginTime) + " ms."); + + } catch (Exception e) { + logger.error("Error during execution " + e); + } + + } - public enum ACTION {RUN, RECURSIVE, VERIFY, RUNVERIFY, RECURSIVVERIFY} + public enum ACTION {RUN, RECURSIVE, VERIFY, RUNVERIFY, RECURSIVVERIFY} } diff --git a/src/main/java/org/camunda/automator/AutomatorRest.java b/src/main/java/org/camunda/automator/AutomatorRest.java new file mode 100644 index 0000000..a1b62a2 --- /dev/null +++ b/src/main/java/org/camunda/automator/AutomatorRest.java @@ -0,0 +1,271 @@ +package org.camunda.automator; + +import org.camunda.automator.bpmnengine.BpmnEngine; +import org.camunda.automator.configuration.BpmnEngineList; +import org.camunda.automator.configuration.ConfigurationStartup; +import org.camunda.automator.content.ContentManager; +import org.camunda.automator.definition.Scenario; +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunParameters; +import org.camunda.automator.engine.RunResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +public class AutomatorRest { + public static final String JSON_SCENARIO_NAME = "scenarioName"; + public static final String JSON_SERVER_NAME = "serverName"; + public static final String JSON_ID = "id"; + public static final String JSON_STATUS = "status"; + public static final String JSON_GENERAL_STATUS = "generalStatus"; + public static final String JSON_NAME = "name"; + public static final String JSON_DETAIL = "detail"; + public static final String JSON_MESSAGE = "message"; + public static final String JSON_INFO = "info"; + private static final Logger logger = LoggerFactory.getLogger(ContentManager.class.getName()); + private final ConfigurationStartup configurationStartup; + private final ContentManager contentManager; + private final AutomatorAPI automatorAPI; + BpmnEngineList engineConfiguration; + HashMap<String, Map<String, Object>> cacheExecution = new HashMap<>(); + + public AutomatorRest(ConfigurationStartup configurationStartup, ContentManager contentManager, AutomatorAPI automatorAPI, BpmnEngineList engineConfiguration) { + this.configurationStartup = configurationStartup; + this.contentManager = contentManager; + this.automatorAPI = automatorAPI; + this.engineConfiguration = engineConfiguration; + } + + @PostMapping(value = "/api/unittest/run", produces = "application/json") + public Map<String, Object> runUnitTest(@RequestParam(name = "name") String scenarioName, @RequestParam(name = "server", required = false) String serverName, @RequestParam(name = "wait", required = false) Boolean wait) { + + Map<String, Object> resultMap = new HashMap<>(); + resultMap.put("senarioName", scenarioName); + + String unitTestId = String.valueOf(System.currentTimeMillis()); + resultMap.put("id", unitTestId); + + if (Boolean.TRUE.equals(wait)) { + startTest(scenarioName, serverName, unitTestId); + resultMap = cacheExecution.get(unitTestId); + return resultMap; + + } else { + Thread thread = new Thread(() -> startTest(scenarioName, serverName, unitTestId)); + thread.start(); + } + + return resultMap; + } + + @GetMapping(value = "/api/unittest/get", produces = "application/json") + public Map<String, Object> getUnitTest(@RequestParam(name = "id") String unitTestId) { + Map<String, Object> resultTest = cacheExecution.get(unitTestId); + if (resultTest != null) { + return resultTest; + } else { + return Map.of("status", "NOTEXIST"); + } + } + + @GetMapping(value = "/api/unittest/list", produces = "application/json") + public List<Map<String, Object>> getListUnitTest() { + List<Map<String, Object>> listUnitTest = new ArrayList<>(); + for (Map.Entry entryTest : cacheExecution.entrySet()) { + if (entryTest.getValue() instanceof Map<?, ?> resultMap) { + listUnitTest.add(Map.of("id", entryTest.getKey(), + JSON_SCENARIO_NAME, getSecureValue(resultMap.get(JSON_SCENARIO_NAME)))); + } else { + listUnitTest.add(Map.of(JSON_ID, entryTest.getKey(), + JSON_SCENARIO_NAME, "")); + } + } + return listUnitTest; + } + + + /** + * Start a test + */ + private void startTest(String scenarioName, String serverName, String unitTestId) { + Map<String, Object> resultMap = new HashMap<>(); + cacheExecution.put(unitTestId, resultMap); + + RunParameters runParameters = new RunParameters(); + runParameters.setExecution(true) + .setServerName(serverName == null || scenarioName.isEmpty() ? configurationStartup.getServerName() : serverName) + .setLogLevel(configurationStartup.getLogLevelEnum()); + resultMap.put(JSON_ID, unitTestId); + resultMap.put(JSON_SERVER_NAME, runParameters.getServerName()); + resultMap.put(JSON_SCENARIO_NAME, scenarioName); + + logger.info( + "Unit Test parameters serverName[{}] ", + runParameters.getServerName()); + + // now proceed the scenario + try { + Scenario scenario = null; + File scenarioFile = contentManager.getFromName(scenarioName); + try { + scenario = automatorAPI.loadFromFile(scenarioFile); + } catch (Exception e) { + logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.getAbsolutePath(), + e.getMessage()); + } + if (scenario == null) { + resultMap.put(JSON_STATUS, "NOTEXIST"); + return; + } + + logger.info("Start scenario [{}] on (1)ScenarioServerName[{}] (2)ConfigurationServerName[{}]", + scenario.getName(), scenario.getServerName(), runParameters.getServerName()); + + // BpmnEngine: find the correct one referenced in the scenario + String message = ""; + BpmnEngine bpmnEngine = connectToEngine(scenario, runParameters, resultMap); + if (bpmnEngine == null) { + logger.error("Scenario [{}] file[{}] Server {} No BPM ENGINE running.", scenario.getName(), + scenario.getName(), message); + return; + } + + bpmnEngine.turnHighFlowMode(true); + logger.info("Scenario [{}] file[{}] use BpmnEngine {}", scenario.getName(), scenario.getName(), + bpmnEngine.getSignature()); + RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); + logger.info("AutomatorRest: end scenario [{}] in {} ms", scenario.getName(), + scenarioExecutionResult.getTimeExecution()); + bpmnEngine.turnHighFlowMode(false); + resultMap.put(JSON_STATUS, "EXECUTED"); + resultMap.putAll(resultToJson(scenarioExecutionResult)); + + } catch (Exception e) { + logger.error("During execute unit Test", e.getMessage()); + resultMap.put("error", e.getMessage()); + } + } + + /** + * @param runResult + * @return + */ + private Map<String, Object> resultToJson(RunResult runResult) { + Map<String, Object> resultMap = new HashMap<>(); + + resultMap.put(JSON_GENERAL_STATUS, runResult.isSuccess() ? StatusTest.SUCCESS : StatusTest.FAIL); + List<Map<String, Object>> listVerificationsJson = new ArrayList<>(); + for (RunResult runResultUnit : runResult.getListRunResults()) { + if (runResultUnit.getScnExecution() == null) { + continue; + } + Map<String, Object> recordResult = new HashMap<>(); + listVerificationsJson.add(recordResult); + recordResult.put(JSON_NAME, runResultUnit.getScnExecution().getName()); + recordResult.put(JSON_STATUS, runResultUnit.isSuccess() ? StatusTest.SUCCESS : StatusTest.FAIL); + recordResult.put(JSON_DETAIL, runResultUnit.getListVerifications().stream() + .map(t -> { // + return Map.of(JSON_STATUS, t.isSuccess ? StatusTest.SUCCESS : StatusTest.FAIL, // + JSON_MESSAGE, getSecureValue(t.message), // + JSON_INFO, getSecureValue(t.verification.getSynthesis())); + })// + .toList()); + } + resultMap.put("tests", listVerificationsJson); + return resultMap; + } + + private Map<String, Object> completeMessage(Map<String, Object> result, StatusTest status, String complement) { + result.put("status", status.toString()); + result.put("complement", complement); + return result; + } + + /** + * Connect to the BPM Engine + * + * @param scenario scenario to use to connect + * @param runParameters + * @param result + * @return + */ + private BpmnEngine connectToEngine(Scenario scenario, RunParameters runParameters, Map<String, Object> result) { + BpmnEngine bpmnEngine = null; + boolean pleaseTryAgain; + int countEngineIsNotReady = 0; + String message = ""; + + do { + pleaseTryAgain = false; + countEngineIsNotReady++; + try { + if (scenario.getServerName() != null && !scenario.getServerName().isEmpty()) { + message += "ScenarioServerName[" + scenario.getServerName() + "];"; + bpmnEngine = automatorAPI.getBpmnEngineFromScenario(scenario, engineConfiguration); + } else { + if (runParameters.getServerName() == null) { + result = completeMessage(result, StatusTest.ENGINE_NOT_EXIST, "Engine [" + runParameters.getServerName() + "] does not exist in the list"); + return null; + } + + + message += "ConfigurationServerName[" + runParameters.getServerName() + "];"; + BpmnEngineList.BpmnServerDefinition serverDefinition = engineConfiguration.getByServerName( + runParameters.getServerName()); + if (serverDefinition == null) { + result = completeMessage(result, StatusTest.ENGINE_NOT_EXIST, "Engine [" + runParameters.getServerName() + "] does not exist in the list"); + return null; + } + + if (runParameters.showLevelMonitoring()) { + logger.info("Run scenario with Server {}", serverDefinition.getSynthesis()); + } + bpmnEngine = automatorAPI.getBpmnEngine(serverDefinition, true); + } + if (runParameters.showLevelDashboard()) { + logger.info("Scenario [{}] Connect to BpmnEngine {}", scenario.getName(), message); + } + + if (!bpmnEngine.isReady()) { + bpmnEngine.connection(); + } + } catch (AutomatorException e) { + pleaseTryAgain = true; + message += "EXCEPT " + e.getMessage(); + } + if (pleaseTryAgain && countEngineIsNotReady < 10) { + logger.info( + "Scenario [{}] file[{}] No BPM ENGINE running [{}] tentative:{}/10. Sleep 30s. Scenario reference serverName[{}]", + message, countEngineIsNotReady, scenario.getName(), scenario.getName(), scenario.getServerName()); + try { + Thread.sleep(((long) 1000) * 30); + } catch (InterruptedException e) { + // nothing to do + } + } + } while (pleaseTryAgain && countEngineIsNotReady < 10); + return bpmnEngine; + } + + /** + * Return "" if info is null: in a Map.of(), a null pointer return an exception + * + * @param info value to return + * @return info or "" + */ + private Object getSecureValue(Object info) { + return info == null ? "" : info; + } + + public enum StatusTest {SCENARIO_NOT_EXIST, ENGINE_NOT_EXIST, SUCCESS, FAIL} +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java index c0cedd6..1ccc67c 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java @@ -11,254 +11,257 @@ import java.io.File; import java.time.Duration; +import java.util.Date; import java.util.List; import java.util.Map; public interface BpmnEngine { - /** - * init the engine. This method will - * - * @throws Exception in case of error - */ - void init(); - - void connection() throws AutomatorException; - - void disconnection() throws AutomatorException; - - /** - * Engine is ready. If not, a connection() method must be call - * - * @return true if the engine is ready - */ - boolean isReady(); - - /* ******************************************************************** */ - /* */ - /* Manage process instance */ - /* */ - /* ******************************************************************** */ - void turnHighFlowMode(boolean hightFlowMode); - - /** - * @param processId Process ID (BPMN ID : ExpenseNode) - * @param starterEventId BPMN ID (startEvent) - * @param variables List of variables to create the process instance - * @return a processInstanceId - * @throws AutomatorException in case of error - */ - String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) - throws AutomatorException; - - /** - * we finish with this processinstanceid, engine can clean it - * - * @param processInstanceId Process instance Id to clean - * @param cleanAll if true, the process instance must be clean. - * @throws AutomatorException in case of error - */ - void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException; - - - /* ******************************************************************** */ - /* */ - /* User task */ - /* */ - /* ******************************************************************** */ - - /** - * @param processInstanceId Process Instance Id - * @param userTaskId BPMN Id (Review) - * @param maxResult maximum result to return. - * @return list of taskId - * @throws AutomatorException in case of error - */ - List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) - throws AutomatorException; - - /** - * Return a list of task - * - * @param userTaskId userTaskId - * @param maxResult maxResult returned - * @return list of TaskId - * @throws AutomatorException in case of error - */ - List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException; - - /** - * @param userTaskId BPMN Id (Review) - * @param userId User id who executes the task - * @param variables variable to update - * @throws AutomatorException in case of error - */ - void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) throws AutomatorException; - - - /* ******************************************************************** */ - /* */ - /* Service tasks */ - /* */ - /* ******************************************************************** */ - - /** - * @param workerId workerId - * @param topic topic to register - * @param streamEnabled true if the stream enable is open - * @param lockTime lock time for the job - * @param jobHandler C7: must implement ExternalTaskHandler. C8: must implement JobHandler - * @param backoffSupplier backOffStrategy - * @return list of Service Task - */ - RegisteredTask registerServiceTask(String workerId, - String topic, - boolean streamEnabled, - Duration lockTime, - Object jobHandler, - FixedBackoffSupplier backoffSupplier); - - /** - * @param processInstanceId process instance ID - * @param serviceTaskId BPMN IP (Review) - * @param topic topic to search to execute the service task - * @param maxResult maximum result - * @return list of taskId - * @throws AutomatorException in case of error - */ - List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) - throws AutomatorException; - - /** - * Execute a service task - * - * @param serviceTaskId BPMN ID (Review) - * @param workerId Worker who execute the task - * @param variables variable to updates - * @throws AutomatorException in case of error - */ - void executeServiceTask(String serviceTaskId, String workerId, Map<String, Object> variables) - throws AutomatorException; - - /** - * Search task. - * - * @param processInstanceId filter on the processInstanceId. may be null - * @param taskId filter on the taskId - * @param maxResult maximum Result - * @return List of task description - * @throws AutomatorException in case of error - */ - List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) - throws AutomatorException; - - /* ******************************************************************** */ - /* */ - /* Generic tasks */ - /* */ - /* ******************************************************************** */ - - /** - * Search process instance by a variable content - * - * @param processId BPMN Process ID - * @param filterVariables Variable name - * @param maxResult maxResult - * @return list of ProcessInstance which match the filter - * @throws AutomatorException in case of error - */ - List<ProcessDescription> searchProcessInstanceByVariable(String processId, - Map<String, Object> filterVariables, - int maxResult) throws AutomatorException; - - /** - * Get variables of a process instanceId - * - * @param processInstanceId the process instance ID - * @return variables attached to the process instance ID - * @throws AutomatorException in case of error - */ - Map<String, Object> getVariables(String processInstanceId) throws AutomatorException; - - /* ******************************************************************** */ - /* */ - /* CountInformation */ - /* */ - /* ******************************************************************** */ - long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) - throws AutomatorException; - - long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) - throws AutomatorException; - - long countNumberOfTasks(String processId, String taskId) throws AutomatorException; - - /** - * Deploy a BPMN file (may contains multiple processes) - * - * @param processFile process to deploy - * @param policy policy to deploy the process - * @return the deploymentId - * @throws AutomatorException in case of error - */ - String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException; - - /* ******************************************************************** */ - /* */ - /* Deployment */ - /* */ - /* ******************************************************************** */ - - BpmnEngineList.CamundaEngine getTypeCamundaEngine(); - - - /* ******************************************************************** */ - /* */ - /* get server definition */ - /* */ - /* ******************************************************************** */ - - /** - * return the signature of the engine, to log it for example - * - * @return signature of the engine - */ - String getSignature(); - - int getWorkerExecutionThreads(); - - class RegisteredTask { - public TopicSubscription topicSubscription; - public JobWorker jobWorker; - - public boolean isNull() { - return topicSubscription == null && jobWorker == null; + /** + * init the engine. This method will + * + * @throws Exception in case of error + */ + void init(); + + void connection() throws AutomatorException; + + void disconnection() throws AutomatorException; + + /** + * Engine is ready. If not, a connection() method must be call + * + * @return true if the engine is ready + */ + boolean isReady(); + + /* ******************************************************************** */ + /* */ + /* Manage process instance */ + /* */ + /* ******************************************************************** */ + void turnHighFlowMode(boolean hightFlowMode); + + /** + * @param processId Process ID (BPMN ID : ExpenseNode) + * @param starterEventId BPMN ID (startEvent) + * @param variables List of variables to create the process instance + * @return a processInstanceId + * @throws AutomatorException in case of error + */ + String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) + throws AutomatorException; + + /** + * we finish with this processinstanceid, engine can clean it + * + * @param processInstanceId Process instance Id to clean + * @param cleanAll if true, the process instance must be clean. + * @throws AutomatorException in case of error + */ + void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException; + + + /* ******************************************************************** */ + /* */ + /* User task */ + /* */ + /* ******************************************************************** */ + + /** + * @param processInstanceId Process Instance Id + * @param filterTaskId If not null, list if filtered to return only this task + * @param maxResult maximum result to return. + * @return list of taskId + * @throws AutomatorException in case of error + */ + List<String> searchUserTasksByProcessInstance(String processInstanceId, String filterTaskId, int maxResult) + throws AutomatorException; + + /** + * Return a list of task + * + * @param userTaskId userTaskId + * @param maxResult maxResult returned + * @return list of TaskId + * @throws AutomatorException in case of error + */ + List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException; + + /** + * @param userTaskId BPMN Id (Review) + * @param userId User id who executes the task + * @param variables variable to update + * @throws AutomatorException in case of error + */ + void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) throws AutomatorException; + + + /* ******************************************************************** */ + /* */ + /* Service tasks */ + /* */ + /* ******************************************************************** */ + + /** + * @param workerId workerId + * @param topic topic to register + * @param streamEnabled true if the stream enable is open + * @param lockTime lock time for the job + * @param jobHandler C7: must implement ExternalTaskHandler. C8: must implement JobHandler + * @param backoffSupplier backOffStrategy + * @return list of Service Task + */ + RegisteredTask registerServiceTask(String workerId, + String topic, + boolean streamEnabled, + Duration lockTime, + Object jobHandler, + FixedBackoffSupplier backoffSupplier); + + /** + * @param processInstanceId process instance ID + * @param serviceTaskId BPMN IP (Review) + * @param topic topic to search to execute the service task + * @param maxResult maximum result + * @return list of taskId + * @throws AutomatorException in case of error + */ + List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + throws AutomatorException; + + /** + * Execute a service task + * + * @param serviceTaskId BPMN ID (Review) + * @param workerId Worker who execute the task + * @param variables variable to updates + * @throws AutomatorException in case of error + */ + void executeServiceTask(String serviceTaskId, String workerId, Map<String, Object> variables) + throws AutomatorException; + + /** + * Search task. + * + * @param processInstanceId filter on the processInstanceId. may be null + * @param taskId filter on the taskId + * @param maxResult maximum Result + * @return List of task description + * @throws AutomatorException in case of error + */ + List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) + throws AutomatorException; + + /* ******************************************************************** */ + /* */ + /* Generic tasks */ + /* */ + /* ******************************************************************** */ + + /** + * Search process instance by a variable content + * + * @param processId BPMN Process ID + * @param filterVariables Variable name + * @param maxResult maxResult + * @return list of ProcessInstance which match the filter + * @throws AutomatorException in case of error + */ + List<ProcessDescription> searchProcessInstanceByVariable(String processId, + Map<String, Object> filterVariables, + int maxResult) throws AutomatorException; + + /** + * Get variables of a process instanceId + * + * @param processInstanceId the process instance ID + * @return variables attached to the process instance ID + * @throws AutomatorException in case of error + */ + Map<String, Object> getVariables(String processInstanceId) throws AutomatorException; + + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException; + + long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException; + + long countNumberOfTasks(String processId, String taskId) throws AutomatorException; + + /** + * Deploy a BPMN file (may contains multiple processes) + * + * @param processFile process to deploy + * @param policy policy to deploy the process + * @return the deploymentId + * @throws AutomatorException in case of error + */ + String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException; + + /* ******************************************************************** */ + /* */ + /* Deployment */ + /* */ + /* ******************************************************************** */ + + BpmnEngineList.CamundaEngine getTypeCamundaEngine(); + + + /* ******************************************************************** */ + /* */ + /* get server definition */ + /* */ + /* ******************************************************************** */ + + /** + * return the signature of the engine, to log it for example + * + * @return signature of the engine + */ + String getSignature(); + + int getWorkerExecutionThreads(); + + class RegisteredTask { + public TopicSubscription topicSubscription; + public JobWorker jobWorker; + + public boolean isNull() { + return topicSubscription == null && jobWorker == null; + } + + public boolean isClosed() { + if (jobWorker != null) + return jobWorker.isClosed(); + return topicSubscription == null; + } + + public void close() { + if (jobWorker != null) + jobWorker.close(); + if (topicSubscription != null) { + topicSubscription.close(); + topicSubscription = null; + } + } } - public boolean isClosed() { - if (jobWorker != null) - return jobWorker.isClosed(); - return topicSubscription == null; + class TaskDescription { + public String processInstanceId; + public String taskId; + public ScenarioStep.Step type; + public boolean isCompleted; + public Date startDate; + public Date endDate; } - public void close() { - if (jobWorker != null) - jobWorker.close(); - if (topicSubscription != null) { - topicSubscription.close(); - topicSubscription = null; - } + class ProcessDescription { + public String processInstanceId; } - } - - class TaskDescription { - public String processInstanceId; - public String taskId; - public ScenarioStep.Step type; - public boolean isCompleted; - } - - class ProcessDescription { - public String processInstanceId; - } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java index 578fab0..89af914 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java @@ -7,68 +7,68 @@ */ public class BpmnEngineConfigurationInstance { - public static BpmnEngineList getZeebeSaas(String zeebeGatewayAddress, Boolean zeebePlainText) { - BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); + public static BpmnEngineList getZeebeSaas(String zeebeGatewayAddress, Boolean zeebePlainText) { + BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); - BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; - serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; - serverDefinition.zeebePlainText = zeebePlainText; + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; + serverDefinition.zeebePlainText = zeebePlainText; - bpmEngineConfiguration.addExplicitServer(serverDefinition); - return bpmEngineConfiguration; - } + bpmEngineConfiguration.addExplicitServer(serverDefinition); + return bpmEngineConfiguration; + } - public static BpmnEngineList getCamunda7(String serverUrl) { - BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); + public static BpmnEngineList getCamunda7(String serverUrl) { + BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); - BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_7; - serverDefinition.camunda7ServerUrl = serverUrl; + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_7; + serverDefinition.camunda7ServerUrl = serverUrl; - bpmEngineConfiguration.addExplicitServer(serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); - return bpmEngineConfiguration; - } + return bpmEngineConfiguration; + } - public static BpmnEngineList getCamunda8(String zeebeGatewayAddress) { - BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); + public static BpmnEngineList getCamunda8(String zeebeGatewayAddress) { + BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); - BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; - serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; - bpmEngineConfiguration.addExplicitServer(serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); - return bpmEngineConfiguration; - } + return bpmEngineConfiguration; + } - public static BpmnEngineList getCamundaSaas8(String zeebeCloudRegister, - String zeebeCloudRegion, - String zeebeCloudClusterId, - String zeebeCloudClientId) { - BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); + public static BpmnEngineList getCamundaSaas8(String zeebeCloudRegister, + String zeebeCloudRegion, + String zeebeCloudClusterId, + String zeebeCloudClientId) { + BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); - BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; - serverDefinition.zeebeSaasRegion = zeebeCloudRegion; - serverDefinition.zeebeSaasClusterId = zeebeCloudClusterId; - serverDefinition.zeebeClientId = zeebeCloudClientId; + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + serverDefinition.zeebeSaasRegion = zeebeCloudRegion; + serverDefinition.zeebeSaasClusterId = zeebeCloudClusterId; + serverDefinition.zeebeClientId = zeebeCloudClientId; - bpmEngineConfiguration.addExplicitServer(serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); - return bpmEngineConfiguration; - } + return bpmEngineConfiguration; + } - public static BpmnEngineList getDummy() { - BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); + public static BpmnEngineList getDummy() { + BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); - BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - serverDefinition.serverType = BpmnEngineList.CamundaEngine.DUMMY; + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.DUMMY; - bpmEngineConfiguration.addExplicitServer(serverDefinition); + bpmEngineConfiguration.addExplicitServer(serverDefinition); - return bpmEngineConfiguration; - } + return bpmEngineConfiguration; + } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java index 6cc16f4..5fe7525 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineFactory.java @@ -21,54 +21,54 @@ */ public class BpmnEngineFactory { - private static final BpmnEngineFactory bpmnEngineFactory = new BpmnEngineFactory(); - Map<BpmnEngineList.CamundaEngine, BpmnEngine> cacheEngine = new EnumMap<>(BpmnEngineList.CamundaEngine.class); - BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy = null; + private static final BpmnEngineFactory bpmnEngineFactory = new BpmnEngineFactory(); + Map<BpmnEngineList.CamundaEngine, BpmnEngine> cacheEngine = new EnumMap<>(BpmnEngineList.CamundaEngine.class); + BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy = null; - private BpmnEngineFactory() { - // use the getInstance() method - } + private BpmnEngineFactory() { + // use the getInstance() method + } - public static BpmnEngineFactory getInstance() { - return bpmnEngineFactory; - } + public static BpmnEngineFactory getInstance() { + return bpmnEngineFactory; + } - public static BpmnEngineFactory getInstance(BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { - bpmnEngineFactory.benchmarkStartPiExceptionHandlingStrategy = benchmarkStartPiExceptionHandlingStrategy; - return bpmnEngineFactory; - } + public static BpmnEngineFactory getInstance(BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { + bpmnEngineFactory.benchmarkStartPiExceptionHandlingStrategy = benchmarkStartPiExceptionHandlingStrategy; + return bpmnEngineFactory; + } - public BpmnEngine getEngineFromConfiguration(BpmnEngineList.BpmnServerDefinition serverDefinition, boolean logDebug) - throws AutomatorException { - BpmnEngine engine = cacheEngine.get(serverDefinition.serverType); - if (engine != null) - return engine; + public BpmnEngine getEngineFromConfiguration(BpmnEngineList.BpmnServerDefinition serverDefinition, boolean logDebug) + throws AutomatorException { + BpmnEngine engine = cacheEngine.get(serverDefinition.serverType); + if (engine != null) + return engine; - // instantiate and initialize the engine now - synchronized (this) { - engine = cacheEngine.get(serverDefinition.serverType); - if (engine != null) - return engine; + // instantiate and initialize the engine now + synchronized (this) { + engine = cacheEngine.get(serverDefinition.serverType); + if (engine != null) + return engine; - engine = switch (serverDefinition.serverType) { - case CAMUNDA_7 -> new BpmnEngineCamunda7(serverDefinition, logDebug); + engine = switch (serverDefinition.serverType) { + case CAMUNDA_7 -> new BpmnEngineCamunda7(serverDefinition, logDebug); - case CAMUNDA_8 -> - BpmnEngineCamunda8.getFromServerDefinition(serverDefinition, benchmarkStartPiExceptionHandlingStrategy, - logDebug); + case CAMUNDA_8 -> + BpmnEngineCamunda8.getFromServerDefinition(serverDefinition, benchmarkStartPiExceptionHandlingStrategy, + logDebug); - case CAMUNDA_8_SAAS -> - BpmnEngineCamunda8.getFromServerDefinition(serverDefinition, benchmarkStartPiExceptionHandlingStrategy, - logDebug); + case CAMUNDA_8_SAAS -> + BpmnEngineCamunda8.getFromServerDefinition(serverDefinition, benchmarkStartPiExceptionHandlingStrategy, + logDebug); - case DUMMY -> new BpmnEngineDummy(serverDefinition); + case DUMMY -> new BpmnEngineDummy(serverDefinition); - }; + }; - engine.init(); - engine.connection(); - cacheEngine.put(serverDefinition.serverType, engine); + engine.init(); + engine.connection(); + cacheEngine.put(serverDefinition.serverType, engine); + } + return engine; } - return engine; - } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java index 570e0ea..33e20d7 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java @@ -10,31 +10,8 @@ import org.camunda.bpm.client.ExternalTaskClient; import org.camunda.bpm.client.backoff.ExponentialBackoffStrategy; import org.camunda.bpm.client.task.ExternalTaskHandler; -import org.camunda.community.rest.client.api.DeploymentApi; -import org.camunda.community.rest.client.api.EngineApi; -import org.camunda.community.rest.client.api.ExternalTaskApi; -import org.camunda.community.rest.client.api.ProcessDefinitionApi; -import org.camunda.community.rest.client.api.ProcessInstanceApi; -import org.camunda.community.rest.client.api.TaskApi; -import org.camunda.community.rest.client.api.VariableInstanceApi; -import org.camunda.community.rest.client.dto.CompleteExternalTaskDto; -import org.camunda.community.rest.client.dto.CompleteTaskDto; -import org.camunda.community.rest.client.dto.DeploymentWithDefinitionsDto; -import org.camunda.community.rest.client.dto.ExternalTaskDto; -import org.camunda.community.rest.client.dto.ExternalTaskQueryDto; -import org.camunda.community.rest.client.dto.LockExternalTaskDto; -import org.camunda.community.rest.client.dto.ProcessInstanceDto; -import org.camunda.community.rest.client.dto.ProcessInstanceQueryDto; -import org.camunda.community.rest.client.dto.ProcessInstanceQueryDtoSorting; -import org.camunda.community.rest.client.dto.ProcessInstanceWithVariablesDto; -import org.camunda.community.rest.client.dto.StartProcessInstanceDto; -import org.camunda.community.rest.client.dto.TaskDto; -import org.camunda.community.rest.client.dto.TaskQueryDto; -import org.camunda.community.rest.client.dto.TaskQueryDtoSorting; -import org.camunda.community.rest.client.dto.UserIdDto; -import org.camunda.community.rest.client.dto.VariableInstanceDto; -import org.camunda.community.rest.client.dto.VariableInstanceQueryDto; -import org.camunda.community.rest.client.dto.VariableValueDto; +import org.camunda.community.rest.client.api.*; +import org.camunda.community.rest.client.dto.*; import org.camunda.community.rest.client.invoker.ApiCallback; import org.camunda.community.rest.client.invoker.ApiClient; import org.camunda.community.rest.client.invoker.ApiException; @@ -43,11 +20,7 @@ import java.io.File; import java.time.Duration; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * connection to a Camunda 7 server. This is one object created by the engine, and then one "init() " call. @@ -55,579 +28,579 @@ */ public class BpmnEngineCamunda7 implements BpmnEngine { - public static final int SEARCH_MAX_SIZE = 100; - private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda7.class); - private final String serverUrl; - private final String userName; - private final String password; - private final int workerMaxJobsActive; - private final boolean logDebug; - ApiClient apiClient = null; - ProcessDefinitionApi processDefinitionApi; - TaskApi taskApi; - ExternalTaskApi externalTaskApi; - ProcessInstanceApi processInstanceApi; - VariableInstanceApi variableInstanceApi; - DeploymentApi deploymentApi; - EngineApi engineApi; - private int count = 0; - - /** - * @param serverDefinition definition to connect to the server - * @param logDebug if true, operation will be log as debug level - */ - public BpmnEngineCamunda7(BpmnEngineList.BpmnServerDefinition serverDefinition, boolean logDebug) { - this.serverUrl = serverDefinition.camunda7ServerUrl; - this.userName = serverDefinition.camunda7UserName; - this.password = serverDefinition.camunda7Password; - this.workerMaxJobsActive = serverDefinition.workerMaxJobsActive; - this.logDebug = logDebug; - init(); - } - - /** - * P - * - * @param serverUrl is "http://localhost:8080/engine-rest" - */ - public BpmnEngineCamunda7(String serverUrl, String userName, String password, boolean logDebug) { - this.serverUrl = serverUrl; - this.userName = userName; - this.password = password; - this.workerMaxJobsActive = 1; - this.logDebug = logDebug; - init(); - } - - @Override - public void init() { - apiClient = new ApiClient(); - apiClient.setBasePath(serverUrl); - if (!userName.trim().isEmpty()) { - apiClient.setUsername(userName); - apiClient.setPassword(password); - } else { + public static final int SEARCH_MAX_SIZE = 100; + private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda7.class); + private final String serverUrl; + private final String userName; + private final String password; + private final int workerMaxJobsActive; + private final boolean logDebug; + ApiClient apiClient = null; + ProcessDefinitionApi processDefinitionApi; + TaskApi taskApi; + ExternalTaskApi externalTaskApi; + ProcessInstanceApi processInstanceApi; + VariableInstanceApi variableInstanceApi; + DeploymentApi deploymentApi; + EngineApi engineApi; + private int count = 0; + + /** + * @param serverDefinition definition to connect to the server + * @param logDebug if true, operation will be log as debug level + */ + public BpmnEngineCamunda7(BpmnEngineList.BpmnServerDefinition serverDefinition, boolean logDebug) { + this.serverUrl = serverDefinition.camunda7ServerUrl; + this.userName = serverDefinition.camunda7UserName; + this.password = serverDefinition.camunda7Password; + this.workerMaxJobsActive = serverDefinition.workerMaxJobsActive; + this.logDebug = logDebug; + init(); } - processDefinitionApi = new ProcessDefinitionApi(apiClient); + /** + * P + * + * @param serverUrl is "http://localhost:8080/engine-rest" + */ + public BpmnEngineCamunda7(String serverUrl, String userName, String password, boolean logDebug) { + this.serverUrl = serverUrl; + this.userName = userName; + this.password = password; + this.workerMaxJobsActive = 1; + this.logDebug = logDebug; + init(); + } - taskApi = new TaskApi(apiClient); + @Override + public void init() { + apiClient = new ApiClient(); + apiClient.setBasePath(serverUrl); + if (!userName.trim().isEmpty()) { + apiClient.setUsername(userName); + apiClient.setPassword(password); + } else { + } - externalTaskApi = new ExternalTaskApi(apiClient); + processDefinitionApi = new ProcessDefinitionApi(apiClient); - processInstanceApi = new ProcessInstanceApi(apiClient); + taskApi = new TaskApi(apiClient); - variableInstanceApi = new VariableInstanceApi(apiClient); + externalTaskApi = new ExternalTaskApi(apiClient); - deploymentApi = new DeploymentApi(apiClient); + processInstanceApi = new ProcessInstanceApi(apiClient); - engineApi = new EngineApi(apiClient); - } + variableInstanceApi = new VariableInstanceApi(apiClient); - public void connection() throws AutomatorException { - count++; - // we verify if we have the connection - // logger.info("Connection to Camunda7 server[{}] User[{}] password[***]", serverUrl, userName); - if (count > 2) - return; - try { - engineApi.getProcessEngineNames(); - logger.info("Connection successfully to Camunda7 [{}] ", apiClient.getBasePath()); - } catch (ApiException e) { - logger.error("Can't connect Camunda7 server[{}] User[{}]: {}", apiClient.getBasePath(), userName, e.toString()); - throw new AutomatorException("Can't connect to Camunda7 [" + apiClient.getBasePath() + "] : " + e); - } - } - - public void disconnection() throws AutomatorException { - // nothing to do here - } - - /** - * Engine is ready. If not, a connection() method must be call - * - * @return true if the engine is ready - */ - public boolean isReady() { - if (count > 2) - return true; - - try { - engineApi.getProcessEngineNames(); - } catch (ApiException e) { - // no need to log, connect will be called - return false; + deploymentApi = new DeploymentApi(apiClient); + + engineApi = new EngineApi(apiClient); } - return true; - } - - /* ******************************************************************** */ - /* */ - /* Process Instance */ - /* */ - /* ******************************************************************** */ - - @Override - public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) - throws AutomatorException { - if (logDebug) { - logger.info("BpmnEngine7.CreateProcessInstance: Process[" + processId + "] StartEvent[" + starterEventId + "]"); + + public void connection() throws AutomatorException { + count++; + // we verify if we have the connection + // logger.info("Connection to Camunda7 server[{}] User[{}] password[***]", serverUrl, userName); + if (count > 2) + return; + try { + engineApi.getProcessEngineNames(); + logger.info("Connection successfully to Camunda7 [{}] ", apiClient.getBasePath()); + } catch (ApiException e) { + logger.error("Can't connect Camunda7 server[{}] User[{}]: {}", apiClient.getBasePath(), userName, e.toString()); + throw new AutomatorException("Can't connect to Camunda7 [" + apiClient.getBasePath() + "] : " + e); + } } - String dateString = dateToString(new Date()); - Map<String, VariableValueDto> variablesApi = new HashMap<>(); - for (Map.Entry<String, Object> entry : variables.entrySet()) { - variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); + public void disconnection() throws AutomatorException { + // nothing to do here } - try { - ProcessInstanceWithVariablesDto processInstanceDto = processDefinitionApi.startProcessInstanceByKey(processId, - new StartProcessInstanceDto().variables(variablesApi).businessKey(dateString)); - return processInstanceDto.getId(); - } catch (ApiException e) { - throw new AutomatorException( - "Can't create process instance in [" + processId + "] StartEvent[" + starterEventId + "]", e); + + /** + * Engine is ready. If not, a connection() method must be call + * + * @return true if the engine is ready + */ + public boolean isReady() { + if (count > 2) + return true; + + try { + engineApi.getProcessEngineNames(); + } catch (ApiException e) { + // no need to log, connect will be called + return false; + } + return true; } - } - @Override - public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { - // To nothing at this moment - } + /* ******************************************************************** */ + /* */ + /* Process Instance */ + /* */ + /* ******************************************************************** */ + @Override + public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) + throws AutomatorException { + if (logDebug) { + logger.info("BpmnEngine7.CreateProcessInstance: Process[" + processId + "] StartEvent[" + starterEventId + "]"); + } + String dateString = dateToString(new Date()); - /* ******************************************************************** */ - /* */ - /* User task */ - /* */ - /* ******************************************************************** */ + Map<String, VariableValueDto> variablesApi = new HashMap<>(); + for (Map.Entry<String, Object> entry : variables.entrySet()) { + variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); + } + try { + ProcessInstanceWithVariablesDto processInstanceDto = processDefinitionApi.startProcessInstanceByKey(processId, + new StartProcessInstanceDto().variables(variablesApi).businessKey(dateString)); + return processInstanceDto.getId(); + } catch (ApiException e) { + throw new AutomatorException( + "Can't create process instance in [" + processId + "] StartEvent[" + starterEventId + "]", e); + } + } - @Override - public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) - throws AutomatorException { - if (logDebug) { - logger.info("BpmnEngine7.searchForActivity: Process[" + processInstanceId + "] taskName[" + userTaskId + "]"); + @Override + public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { + // To nothing at this moment } - // get the list of all sub process instance - List<String> listProcessInstance = getListSubProcessInstance(processInstanceId); - TaskQueryDto taskQueryDto = new TaskQueryDto(); - taskQueryDto.addProcessInstanceIdInItem(processInstanceId); - for (String subProcessInstance : listProcessInstance) { - taskQueryDto.addProcessInstanceIdInItem(subProcessInstance); + /* ******************************************************************** */ + /* */ + /* User task */ + /* */ + /* ******************************************************************** */ - } - taskQueryDto.addTaskDefinitionKeyInItem(userTaskId); - List<TaskDto> taskDtos = null; - try { - taskDtos = taskApi.queryTasks(0, maxResult, taskQueryDto); - } catch (ApiException e) { - throw new AutomatorException("Can't searchTask", e); - } - return taskDtos.stream().map(TaskDto::getId).toList(); - } + @Override + public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) + throws AutomatorException { + if (logDebug) { + logger.info("BpmnEngine7.searchForActivity: Process[" + processInstanceId + "] taskName[" + userTaskId + "]"); + } - @Override - public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { - if (logDebug) { - logger.info("BpmnEngine7.searchForActivity: taskName[{}]", userTaskId); - } + // get the list of all sub process instance + List<String> listProcessInstance = getListSubProcessInstance(processInstanceId); + + TaskQueryDto taskQueryDto = new TaskQueryDto(); + taskQueryDto.addProcessInstanceIdInItem(processInstanceId); + for (String subProcessInstance : listProcessInstance) { + taskQueryDto.addProcessInstanceIdInItem(subProcessInstance); - TaskQueryDto taskQueryDto = new TaskQueryDto(); - taskQueryDto.addTaskDefinitionKeyInItem(userTaskId); - List<TaskDto> taskDtos = null; - try { - taskDtos = taskApi.queryTasks(0, maxResult, taskQueryDto); - } catch (ApiException e) { - throw new AutomatorException("Can't searchTask", e); + } + taskQueryDto.addTaskDefinitionKeyInItem(userTaskId); + List<TaskDto> taskDtos = null; + try { + taskDtos = taskApi.queryTasks(0, maxResult, taskQueryDto); + } catch (ApiException e) { + throw new AutomatorException("Can't searchTask", e); + } + return taskDtos.stream().map(TaskDto::getId).toList(); } - return taskDtos.stream().map(TaskDto::getId).toList(); - } - @Override - public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) - throws AutomatorException { + @Override + public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { + if (logDebug) { + logger.info("BpmnEngine7.searchForActivity: taskName[{}]", userTaskId); + } - if (logDebug) { - logger.info("BpmnEngine7.executeUserTask: activityId[{}]", userTaskId); - } - try { - UserIdDto userIdDto = new UserIdDto(); - userIdDto.setUserId(userId == null ? "automator" : userId); - taskApi.claim(userTaskId, userIdDto); - Map<String, VariableValueDto> variablesApi = new HashMap<>(); - for (Map.Entry<String, Object> entry : variables.entrySet()) { - variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); - } - - taskApi.complete(userTaskId, new CompleteTaskDto().variables(variablesApi)); - } catch (ApiException e) { - throw new AutomatorException("Can't execute taskId[" + userTaskId + "] with userId[" + userId + "]", e); - } - } - - /* ******************************************************************** */ - /* */ - /* Service task */ - /* */ - /* ******************************************************************** */ - @Override - public RegisteredTask registerServiceTask(String workerId, - String topic, - boolean streamEnable, - Duration lockTime, - Object jobHandler, - FixedBackoffSupplier backoffSupplier) { - - if (!(jobHandler instanceof ExternalTaskHandler)) { - logger.error("handler is not a externalTaskHandler implementation, can't register the worker [{}], topic [{}]", - workerId, topic); - return null; - } - RegisteredTask registeredTask = new RegisteredTask(); - - ExternalTaskClient client = ExternalTaskClient.create() - .baseUrl(serverUrl) - .workerId(workerId) - .maxTasks(workerMaxJobsActive < 1 ? 1 : workerMaxJobsActive) - .lockDuration(lockTime.toMillis()) - .asyncResponseTimeout(20000) - .backoffStrategy(new ExponentialBackoffStrategy()) - .build(); - - registeredTask.topicSubscription = client.subscribe(topic) - .lockDuration(10000) - .handler((ExternalTaskHandler) jobHandler) - .open(); - return registeredTask; - - } - - /** - * Search service task - * - * @param processInstanceId processInstance - * @param serviceTaskId task name - * @param topic topic to search the task - * @param maxResult number of result - * @return the list of TaskId found according the criteria - * @throws AutomatorException any error during search - */ - @Override - public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) - throws AutomatorException { - if (logDebug) { - logger.info("BpmnEngine7.searchForActivity: Process[{}] taskName[{}]", processInstanceId, serviceTaskId); + TaskQueryDto taskQueryDto = new TaskQueryDto(); + taskQueryDto.addTaskDefinitionKeyInItem(userTaskId); + List<TaskDto> taskDtos = null; + try { + taskDtos = taskApi.queryTasks(0, maxResult, taskQueryDto); + } catch (ApiException e) { + throw new AutomatorException("Can't searchTask", e); + } + return taskDtos.stream().map(TaskDto::getId).toList(); } - // get the list of all sub process instance - List<String> listProcessInstance = getListSubProcessInstance(processInstanceId); - - ExternalTaskQueryDto externalTaskQueryDto = new ExternalTaskQueryDto(); - externalTaskQueryDto.addProcessInstanceIdInItem(processInstanceId); - for (String subProcessInstance : listProcessInstance) { - externalTaskQueryDto.addProcessInstanceIdInItem(subProcessInstance); + @Override + public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) + throws AutomatorException { + if (logDebug) { + logger.info("BpmnEngine7.executeUserTask: activityId[{}]", userTaskId); + } + try { + UserIdDto userIdDto = new UserIdDto(); + userIdDto.setUserId(userId == null ? "automator" : userId); + taskApi.claim(userTaskId, userIdDto); + Map<String, VariableValueDto> variablesApi = new HashMap<>(); + for (Map.Entry<String, Object> entry : variables.entrySet()) { + variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); + } + + taskApi.complete(userTaskId, new CompleteTaskDto().variables(variablesApi)); + } catch (ApiException e) { + throw new AutomatorException("Can't execute taskId[" + userTaskId + "] with userId[" + userId + "]", e); + } } - externalTaskQueryDto.activityId(serviceTaskId); - List<ExternalTaskDto> taskDtos; - try { - taskDtos = externalTaskApi.queryExternalTasks(0, 100, externalTaskQueryDto); - } catch (ApiException e) { - throw new AutomatorException("Can't searchTask", e); + /* ******************************************************************** */ + /* */ + /* Service task */ + /* */ + /* ******************************************************************** */ + @Override + public RegisteredTask registerServiceTask(String workerId, + String topic, + boolean streamEnable, + Duration lockTime, + Object jobHandler, + FixedBackoffSupplier backoffSupplier) { + + if (!(jobHandler instanceof ExternalTaskHandler)) { + logger.error("handler is not a externalTaskHandler implementation, can't register the worker [{}], topic [{}]", + workerId, topic); + return null; + } + RegisteredTask registeredTask = new RegisteredTask(); + + ExternalTaskClient client = ExternalTaskClient.create() + .baseUrl(serverUrl) + .workerId(workerId) + .maxTasks(workerMaxJobsActive < 1 ? 1 : workerMaxJobsActive) + .lockDuration(lockTime.toMillis()) + .asyncResponseTimeout(20000) + .backoffStrategy(new ExponentialBackoffStrategy()) + .build(); + + registeredTask.topicSubscription = client.subscribe(topic) + .lockDuration(10000) + .handler((ExternalTaskHandler) jobHandler) + .open(); + return registeredTask; + } - return taskDtos.stream().map(ExternalTaskDto::getId).toList(); - } - @Override - public void executeServiceTask(String serviceTaskId, String userId, Map<String, Object> variables) - throws AutomatorException { + /** + * Search service task + * + * @param processInstanceId processInstance + * @param serviceTaskId task name + * @param topic topic to search the task + * @param maxResult number of result + * @return the list of TaskId found according the criteria + * @throws AutomatorException any error during search + */ + @Override + public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + throws AutomatorException { + if (logDebug) { + logger.info("BpmnEngine7.searchForActivity: Process[{}] taskName[{}]", processInstanceId, serviceTaskId); + } - if (logDebug) { - logger.info("BpmnEngine7.executeUserTask: activityId[{}]", serviceTaskId); - } - try { + // get the list of all sub process instance + List<String> listProcessInstance = getListSubProcessInstance(processInstanceId); - // Fetch and lock - String workerId = getUniqWorkerId(); - externalTaskApi.lock(serviceTaskId, new LockExternalTaskDto().workerId(workerId).lockDuration(10000L)); + ExternalTaskQueryDto externalTaskQueryDto = new ExternalTaskQueryDto(); + externalTaskQueryDto.addProcessInstanceIdInItem(processInstanceId); + for (String subProcessInstance : listProcessInstance) { + externalTaskQueryDto.addProcessInstanceIdInItem(subProcessInstance); - Map<String, VariableValueDto> variablesApi = new HashMap<>(); - for (Map.Entry<String, Object> entry : variables.entrySet()) { - variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); - } + } - ExternalCallBack externalCallBack = new ExternalCallBack(); - externalTaskApi.completeExternalTaskResourceAsync(serviceTaskId, - new CompleteExternalTaskDto().variables(variablesApi).workerId(workerId), externalCallBack); + externalTaskQueryDto.activityId(serviceTaskId); + List<ExternalTaskDto> taskDtos; + try { + taskDtos = externalTaskApi.queryExternalTasks(0, 100, externalTaskQueryDto); + } catch (ApiException e) { + throw new AutomatorException("Can't searchTask", e); + } + return taskDtos.stream().map(ExternalTaskDto::getId).toList(); + } - int counter = 0; - while (ExternalCallBack.STATUS.WAIT.equals(externalCallBack.status) && counter < 200) { - counter++; + @Override + public void executeServiceTask(String serviceTaskId, String userId, Map<String, Object> variables) + throws AutomatorException { + + if (logDebug) { + logger.info("BpmnEngine7.executeUserTask: activityId[{}]", serviceTaskId); + } try { - Thread.sleep(200); - } catch (InterruptedException e) { - // we don't care + + // Fetch and lock + String workerId = getUniqWorkerId(); + externalTaskApi.lock(serviceTaskId, new LockExternalTaskDto().workerId(workerId).lockDuration(10000L)); + + Map<String, VariableValueDto> variablesApi = new HashMap<>(); + for (Map.Entry<String, Object> entry : variables.entrySet()) { + variablesApi.put(entry.getKey(), new VariableValueDto().value(entry.getValue())); + } + + ExternalCallBack externalCallBack = new ExternalCallBack(); + externalTaskApi.completeExternalTaskResourceAsync(serviceTaskId, + new CompleteExternalTaskDto().variables(variablesApi).workerId(workerId), externalCallBack); + + int counter = 0; + while (ExternalCallBack.STATUS.WAIT.equals(externalCallBack.status) && counter < 200) { + counter++; + try { + Thread.sleep(200); + } catch (InterruptedException e) { + // we don't care + } + } + if (!ExternalCallBack.STATUS.SUCCESS.equals(externalCallBack.status)) { + throw new AutomatorException( + "Can't execute taskId[" + serviceTaskId + "] - answer[" + externalCallBack.status + "]"); + } + } catch (ApiException e) { + throw new AutomatorException("Can't execute taskId[" + serviceTaskId + "] with userId[" + userId + "]", e); } - } - if (!ExternalCallBack.STATUS.SUCCESS.equals(externalCallBack.status)) { - throw new AutomatorException( - "Can't execute taskId[" + serviceTaskId + "] - answer[" + externalCallBack.status + "]"); - } - } catch (ApiException e) { - throw new AutomatorException("Can't execute taskId[" + serviceTaskId + "] with userId[" + userId + "]", e); } - } - /* ******************************************************************** */ - /* */ - /* Generic task */ - /* */ - /* ******************************************************************** */ + /* ******************************************************************** */ + /* */ + /* Generic task */ + /* */ + /* ******************************************************************** */ - @Override - public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) - throws AutomatorException { - // get the list of all sub process instance - List<String> listProcessInstance = getListSubProcessInstance(processInstanceId); + @Override + public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) + throws AutomatorException { + // get the list of all sub process instance + List<String> listProcessInstance = getListSubProcessInstance(processInstanceId); - TaskQueryDto taskQueryDto = new TaskQueryDto(); - taskQueryDto.addProcessInstanceIdInItem(processInstanceId); - for (String subProcessInstance : listProcessInstance) { - taskQueryDto.addProcessInstanceIdInItem(subProcessInstance); + TaskQueryDto taskQueryDto = new TaskQueryDto(); + taskQueryDto.addProcessInstanceIdInItem(processInstanceId); + for (String subProcessInstance : listProcessInstance) { + taskQueryDto.addProcessInstanceIdInItem(subProcessInstance); + } + taskQueryDto.addTaskDefinitionKeyInItem(taskId); + List<TaskDto> taskDtos = null; + try { + taskDtos = taskApi.queryTasks(0, maxResult, taskQueryDto); + } catch (ApiException e) { + throw new AutomatorException("Can't searchTask", e); + } + return taskDtos.stream().map(t -> { + TaskDescription taskDescription = new TaskDescription(); + taskDescription.taskId = t.getName(); + taskDescription.type = ScenarioStep.Step.USERTASK; + taskDescription.isCompleted = true; + return taskDescription; + }).toList(); } - taskQueryDto.addTaskDefinitionKeyInItem(taskId); - List<TaskDto> taskDtos = null; - try { - taskDtos = taskApi.queryTasks(0, maxResult, taskQueryDto); - } catch (ApiException e) { - throw new AutomatorException("Can't searchTask", e); + + @Override + public List<ProcessDescription> searchProcessInstanceByVariable(String processId, + Map<String, Object> filterVariables, + int maxResult) throws AutomatorException { + return Collections.emptyList(); } - return taskDtos.stream().map(t -> { - TaskDescription taskDescription = new TaskDescription(); - taskDescription.taskId = t.getName(); - taskDescription.type = ScenarioStep.Step.USERTASK; - taskDescription.isCompleted = true; - return taskDescription; - }).toList(); - } - - @Override - public List<ProcessDescription> searchProcessInstanceByVariable(String processId, - Map<String, Object> filterVariables, - int maxResult) throws AutomatorException { - return Collections.emptyList(); - } - - @Override - public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { - - VariableInstanceQueryDto variableQuery = new VariableInstanceQueryDto(); - variableQuery.processInstanceIdIn(List.of(processInstanceId)); - try { - List<VariableInstanceDto> variableInstanceDtos = variableInstanceApi.queryVariableInstances(0, 1000, true, - variableQuery); - - Map<String, Object> variables = new HashMap<>(); - for (VariableInstanceDto variable : variableInstanceDtos) { - variables.put(variable.getName(), variable.getValue()); - } - return variables; - } catch (ApiException e) { - throw new AutomatorException("Can't searchVariables", e); + + @Override + public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { + + VariableInstanceQueryDto variableQuery = new VariableInstanceQueryDto(); + variableQuery.processInstanceIdIn(List.of(processInstanceId)); + try { + List<VariableInstanceDto> variableInstanceDtos = variableInstanceApi.queryVariableInstances(0, 1000, true, + variableQuery); + + Map<String, Object> variables = new HashMap<>(); + for (VariableInstanceDto variable : variableInstanceDtos) { + variables.put(variable.getName(), variable.getValue()); + } + return variables; + } catch (ApiException e) { + throw new AutomatorException("Can't searchVariables", e); + } } - } - - /* ******************************************************************** */ - /* */ - /* CountInformation */ - /* */ - /* ******************************************************************** */ - - @Override - public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) - throws AutomatorException { - - try { - int cumul = 0; - ProcessInstanceQueryDto processInstanceQuery = new ProcessInstanceQueryDto(); - processInstanceQuery = processInstanceQuery.addProcessDefinitionKeyInItem(processName); - processInstanceQuery.addSortingItem( - new ProcessInstanceQueryDtoSorting().sortBy(ProcessInstanceQueryDtoSorting.SortByEnum.INSTANCEID) - .sortOrder(ProcessInstanceQueryDtoSorting.SortOrderEnum.ASC)); - - int maxLoop = 0; - int firstResult = 0; - List<ProcessInstanceDto> processInstanceDtos; - do { - maxLoop++; - processInstanceDtos = processInstanceApi.queryProcessInstances(firstResult, SEARCH_MAX_SIZE, - processInstanceQuery); - firstResult += processInstanceDtos.size(); - cumul += processInstanceDtos.stream().filter(t -> { - Date datePI = stringToDate(t.getBusinessKey()); - if (datePI == null) - return false; - return datePI.after(startDate.getDate()); - }).count(); - } while (processInstanceDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ - } catch (Exception e) { - throw new AutomatorException("Error during countNumberOfProcessInstancesCreated"); + @Override + public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { - } - } - - @Override - public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) - throws AutomatorException { - throw new AutomatorException("Not yet implemented"); - } - - public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { - try { - int cumul = 0; - TaskQueryDto taskQueryDto = new TaskQueryDto(); - taskQueryDto = taskQueryDto.addProcessDefinitionKeyInItem(processId); - taskQueryDto.addSortingItem(new TaskQueryDtoSorting().sortBy(TaskQueryDtoSorting.SortByEnum.INSTANCEID) - .sortOrder(TaskQueryDtoSorting.SortOrderEnum.ASC)); - - int maxLoop = 0; - int firstResult = 0; - List<TaskDto> taskDtos; - do { - maxLoop++; - taskDtos = taskApi.queryTasks(firstResult, SEARCH_MAX_SIZE, taskQueryDto); - - firstResult += taskDtos.size(); - cumul += taskDtos.size(); - - } while (taskDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - - } catch (Exception e) { - throw new AutomatorException("Error during countNumberOfTasks"); + try { + int cumul = 0; + ProcessInstanceQueryDto processInstanceQuery = new ProcessInstanceQueryDto(); + processInstanceQuery = processInstanceQuery.addProcessDefinitionKeyInItem(processName); + processInstanceQuery.addSortingItem( + new ProcessInstanceQueryDtoSorting().sortBy(ProcessInstanceQueryDtoSorting.SortByEnum.INSTANCEID) + .sortOrder(ProcessInstanceQueryDtoSorting.SortOrderEnum.ASC)); + + int maxLoop = 0; + int firstResult = 0; + List<ProcessInstanceDto> processInstanceDtos; + do { + maxLoop++; + processInstanceDtos = processInstanceApi.queryProcessInstances(firstResult, SEARCH_MAX_SIZE, + processInstanceQuery); + firstResult += processInstanceDtos.size(); + cumul += processInstanceDtos.stream().filter(t -> { + Date datePI = stringToDate(t.getBusinessKey()); + if (datePI == null) + return false; + return datePI.after(startDate.getDate()); + }).count(); + + } while (processInstanceDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + + } catch (Exception e) { + throw new AutomatorException("Error during countNumberOfProcessInstancesCreated"); + } } - } - - /* ******************************************************************** */ - /* */ - /* Deployment */ - /* */ - /* ******************************************************************** */ - - @Override - public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { - try { - DeploymentWithDefinitionsDto deploymentSource = deploymentApi.createDeployment(null, // tenantId - null, // deploymentSource - ScenarioDeployment.Policy.ONLYNOTEXIST.equals(policy), // deployChangedOnly, - Boolean.TRUE, // enableDuplicateFiltering, - processFile.getName(), // String deploymentName, - new Date(), //deploymentActivationTime, - processFile); - return deploymentSource.getId(); - } catch (ApiException e) { - throw new AutomatorException("Can't deploy process ", e); + + @Override + public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + throw new AutomatorException("Not yet implemented"); } - } - - - - /* ******************************************************************** */ - /* */ - /* get server definition */ - /* */ - /* ******************************************************************** */ - - @Override - public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { - return BpmnEngineList.CamundaEngine.CAMUNDA_7; - } - - @Override - public String getSignature() { - return BpmnEngineList.CamundaEngine.CAMUNDA_7 + " " + "serverUrl[" + serverUrl + "]"; - } - - @Override - public int getWorkerExecutionThreads() { - return workerMaxJobsActive; - } - - public void turnHighFlowMode(boolean hightFlowMode) { - } - - private String getUniqWorkerId() { - return Thread.currentThread().getName() + "-" + System.currentTimeMillis(); - } - - /** - * Collect all subprocess for a process instance - * - * @param rootProcessInstance root process instance - * @return list of SubProcess ID - * @throws AutomatorException if any errors arrive - */ - private List<String> getListSubProcessInstance(String rootProcessInstance) throws AutomatorException { - ProcessInstanceQueryDto processInstanceQueryDto = new ProcessInstanceQueryDto(); - processInstanceQueryDto.superProcessInstance(rootProcessInstance); - List<ProcessInstanceDto> processInstanceDtos; - try { - processInstanceDtos = processInstanceApi.queryProcessInstances(0, 100000, processInstanceQueryDto); - } catch (ApiException e) { - throw new AutomatorException("Can't searchSubProcess", e); + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + try { + int cumul = 0; + TaskQueryDto taskQueryDto = new TaskQueryDto(); + taskQueryDto = taskQueryDto.addProcessDefinitionKeyInItem(processId); + taskQueryDto.addSortingItem(new TaskQueryDtoSorting().sortBy(TaskQueryDtoSorting.SortByEnum.INSTANCEID) + .sortOrder(TaskQueryDtoSorting.SortOrderEnum.ASC)); + + int maxLoop = 0; + int firstResult = 0; + List<TaskDto> taskDtos; + do { + maxLoop++; + taskDtos = taskApi.queryTasks(firstResult, SEARCH_MAX_SIZE, taskQueryDto); + + firstResult += taskDtos.size(); + cumul += taskDtos.size(); + + } while (taskDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + + } catch (Exception e) { + throw new AutomatorException("Error during countNumberOfTasks"); + + } } - return processInstanceDtos.stream().map(ProcessInstanceDto::getId).toList(); - } - private String dateToString(Date date) { - return String.valueOf(date.getTime()); - } + /* ******************************************************************** */ + /* */ + /* Deployment */ + /* */ + /* ******************************************************************** */ + + @Override + public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { + try { + DeploymentWithDefinitionsDto deploymentSource = deploymentApi.createDeployment(null, // tenantId + null, // deploymentSource + ScenarioDeployment.Policy.ONLYNOTEXIST.equals(policy), // deployChangedOnly, + Boolean.TRUE, // enableDuplicateFiltering, + processFile.getName(), // String deploymentName, + new Date(), //deploymentActivationTime, + processFile); + return deploymentSource.getId(); + } catch (ApiException e) { + throw new AutomatorException("Can't deploy process ", e); + } - private Date stringToDate(String dateSt) { - if (dateSt == null) - return null; - return new Date(Long.valueOf(dateSt)); - } + } - /** - * Call back asynchronous - */ - public static class ExternalCallBack implements ApiCallback { - public STATUS status = STATUS.WAIT; - public ApiException e; + + /* ******************************************************************** */ + /* */ + /* get server definition */ + /* */ + /* ******************************************************************** */ @Override - public void onFailure(ApiException e, int i, Map map) { - this.status = STATUS.FAILURE; - this.e = e; + public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { + return BpmnEngineList.CamundaEngine.CAMUNDA_7; } @Override - public void onSuccess(Object o, int i, Map map) { - this.status = STATUS.SUCCESS; + public String getSignature() { + return BpmnEngineList.CamundaEngine.CAMUNDA_7 + " " + "serverUrl[" + serverUrl + "]"; } @Override - public void onUploadProgress(long l, long l1, boolean b) { + public int getWorkerExecutionThreads() { + return workerMaxJobsActive; + } + public void turnHighFlowMode(boolean hightFlowMode) { } - @Override - public void onDownloadProgress(long l, long l1, boolean b) { + private String getUniqWorkerId() { + return Thread.currentThread().getName() + "-" + System.currentTimeMillis(); + } + + /** + * Collect all subprocess for a process instance + * + * @param rootProcessInstance root process instance + * @return list of SubProcess ID + * @throws AutomatorException if any errors arrive + */ + private List<String> getListSubProcessInstance(String rootProcessInstance) throws AutomatorException { + ProcessInstanceQueryDto processInstanceQueryDto = new ProcessInstanceQueryDto(); + processInstanceQueryDto.superProcessInstance(rootProcessInstance); + List<ProcessInstanceDto> processInstanceDtos; + try { + processInstanceDtos = processInstanceApi.queryProcessInstances(0, 100000, processInstanceQueryDto); + } catch (ApiException e) { + throw new AutomatorException("Can't searchSubProcess", e); + } + return processInstanceDtos.stream().map(ProcessInstanceDto::getId).toList(); + } + + private String dateToString(Date date) { + return String.valueOf(date.getTime()); + } + private Date stringToDate(String dateSt) { + if (dateSt == null) + return null; + return new Date(Long.valueOf(dateSt)); } - public enum STATUS {WAIT, FAILURE, SUCCESS} - } + /** + * Call back asynchronous + */ + public static class ExternalCallBack implements ApiCallback { + + public STATUS status = STATUS.WAIT; + public ApiException e; + + @Override + public void onFailure(ApiException e, int i, Map map) { + this.status = STATUS.FAILURE; + this.e = e; + } + + @Override + public void onSuccess(Object o, int i, Map map) { + this.status = STATUS.SUCCESS; + } + + @Override + public void onUploadProgress(long l, long l1, boolean b) { + + } + + @Override + public void onDownloadProgress(long l, long l1, boolean b) { + + } + + public enum STATUS {WAIT, FAILURE, SUCCESS} + } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java index f8a5584..0668c93 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkCompleteJobExceptionHandlingStrategy.java @@ -14,28 +14,28 @@ @Component public class BenchmarkCompleteJobExceptionHandlingStrategy extends DefaultCommandExceptionHandlingStrategy { - @Autowired - private StatisticsCollector stats; + @Autowired + private StatisticsCollector stats; - public BenchmarkCompleteJobExceptionHandlingStrategy(@Autowired BackoffSupplier backoffSupplier) { - super(backoffSupplier, Executors.newScheduledThreadPool(1)); - } + public BenchmarkCompleteJobExceptionHandlingStrategy(@Autowired BackoffSupplier backoffSupplier) { + super(backoffSupplier, Executors.newScheduledThreadPool(1)); + } - @Override - public void handleCommandError(CommandWrapper command, Throwable throwable) { - if (StatusRuntimeException.class.isAssignableFrom(throwable.getClass())) { - StatusRuntimeException exception = (StatusRuntimeException) throwable; - stats.incCompletedJobsException(exception.getStatus().getCode().name()); + @Override + public void handleCommandError(CommandWrapper command, Throwable throwable) { + if (StatusRuntimeException.class.isAssignableFrom(throwable.getClass())) { + StatusRuntimeException exception = (StatusRuntimeException) throwable; + stats.incCompletedJobsException(exception.getStatus().getCode().name()); /* Backpressure on Job completion cannot happen at the moment (whitelisted) if (Status.Code.RESOURCE_EXHAUSTED == exception.getStatus().getCode()) { stats.getBackpressureOnJobCompleteMeter().mark(); return; }*/ - } else { - stats.incCompletedJobsException(throwable.getMessage()); - } + } else { + stats.incCompletedJobsException(throwable.getMessage()); + } - // use normal behavior, e.g. increasing back-off for backpressure - super.handleCommandError(command, throwable); - } + // use normal behavior, e.g. increasing back-off for backpressure + super.handleCommandError(command, throwable); + } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java index 585b02e..a54983a 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BenchmarkStartPiExceptionHandlingStrategy.java @@ -13,26 +13,26 @@ @Component public class BenchmarkStartPiExceptionHandlingStrategy extends DefaultCommandExceptionHandlingStrategy { - @Autowired - private StatisticsCollector stats; + @Autowired + private StatisticsCollector stats; - public BenchmarkStartPiExceptionHandlingStrategy(@Autowired BackoffSupplier backoffSupplier) { - super(backoffSupplier, Executors.newScheduledThreadPool(1)); - } + public BenchmarkStartPiExceptionHandlingStrategy(@Autowired BackoffSupplier backoffSupplier) { + super(backoffSupplier, Executors.newScheduledThreadPool(1)); + } - @Override - public void handleCommandError(CommandWrapper command, Throwable throwable) { - if (StatusRuntimeException.class.isAssignableFrom(throwable.getClass())) { - StatusRuntimeException exception = (StatusRuntimeException) throwable; - stats.incStartedProcessInstancesException(exception.getStatus().getCode().name()); - if (Status.Code.RESOURCE_EXHAUSTED == exception.getStatus().getCode()) { - stats.incStartedProcessInstancesBackpressure(); - return; // ignore backpressure, as we don't want to add a big wave of retries - } - } else { - stats.incStartedProcessInstancesException(throwable.getMessage()); + @Override + public void handleCommandError(CommandWrapper command, Throwable throwable) { + if (StatusRuntimeException.class.isAssignableFrom(throwable.getClass())) { + StatusRuntimeException exception = (StatusRuntimeException) throwable; + stats.incStartedProcessInstancesException(exception.getStatus().getCode().name()); + if (Status.Code.RESOURCE_EXHAUSTED == exception.getStatus().getCode()) { + stats.incStartedProcessInstancesBackpressure(); + return; // ignore backpressure, as we don't want to add a big wave of retries + } + } else { + stats.incStartedProcessInstancesException(throwable.getMessage()); + } + // use normal behavior + super.handleCommandError(command, throwable); } - // use normal behavior - super.handleCommandError(command, throwable); - } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java index 7d3447e..c79dc65 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java @@ -1,14 +1,6 @@ package org.camunda.automator.bpmnengine.camunda8; -import io.camunda.common.auth.Authentication; -import io.camunda.common.auth.JwtConfig; -import io.camunda.common.auth.JwtCredential; -import io.camunda.common.auth.Product; -import io.camunda.common.auth.SaaSAuthentication; -import io.camunda.common.auth.SaaSAuthenticationBuilder; -import io.camunda.common.auth.SimpleAuthentication; -import io.camunda.common.auth.SimpleConfig; -import io.camunda.common.auth.SimpleCredential; +import io.camunda.common.auth.*; import io.camunda.common.auth.identity.IdentityConfig; import io.camunda.common.auth.identity.IdentityContainer; import io.camunda.common.json.SdkObjectMapper; @@ -17,35 +9,18 @@ import io.camunda.operate.CamundaOperateClient; import io.camunda.operate.CamundaOperateClientBuilder; import io.camunda.operate.exception.OperateException; -import io.camunda.operate.model.FlowNodeInstance; -import io.camunda.operate.model.FlowNodeInstanceState; -import io.camunda.operate.model.ProcessInstance; -import io.camunda.operate.model.ProcessInstanceState; -import io.camunda.operate.model.SearchResult; +import io.camunda.operate.model.*; import io.camunda.operate.search.DateFilter; -import io.camunda.operate.search.FlowNodeInstanceFilter; -import io.camunda.operate.search.ProcessInstanceFilter; -import io.camunda.operate.search.SearchQuery; -import io.camunda.operate.search.Sort; -import io.camunda.operate.search.SortOrder; -import io.camunda.operate.search.VariableFilter; +import io.camunda.operate.search.*; import io.camunda.tasklist.CamundaTaskListClient; import io.camunda.tasklist.CamundaTaskListClientBuilder; -import io.camunda.tasklist.dto.Pagination; -import io.camunda.tasklist.dto.Task; -import io.camunda.tasklist.dto.TaskList; -import io.camunda.tasklist.dto.TaskSearch; -import io.camunda.tasklist.dto.TaskState; import io.camunda.tasklist.dto.Variable; +import io.camunda.tasklist.dto.*; import io.camunda.tasklist.exception.TaskListException; import io.camunda.zeebe.client.ZeebeClient; import io.camunda.zeebe.client.ZeebeClientBuilder; import io.camunda.zeebe.client.api.command.FinalCommandStep; -import io.camunda.zeebe.client.api.response.ActivateJobsResponse; -import io.camunda.zeebe.client.api.response.ActivatedJob; -import io.camunda.zeebe.client.api.response.DeploymentEvent; -import io.camunda.zeebe.client.api.response.ProcessInstanceEvent; -import io.camunda.zeebe.client.api.response.Topology; +import io.camunda.zeebe.client.api.response.*; import io.camunda.zeebe.client.api.worker.JobHandler; import io.camunda.zeebe.client.api.worker.JobWorkerBuilderStep1; import io.camunda.zeebe.client.impl.oauth.OAuthCredentialsProvider; @@ -64,1102 +39,1112 @@ import java.net.URI; import java.net.URL; import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class BpmnEngineCamunda8 implements BpmnEngine { - public static final String THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME = "ThisIsACompleteImpossibleVariableName"; - public static final int SEARCH_MAX_SIZE = 100; - public static final String SAAS_AUTHENTICATE_URL = "https://login.cloud.camunda.io/oauth/token"; - private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda8.class); - private final BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy; - boolean hightFlowMode = false; - /** - * It is not possible to search user task for a specfic processInstance. So, to realize this, a marker is created in each process instance. Retrieving the user task, - * the process instance can be found and correction can be done - */ - Map<String, Long> cacheProcessInstanceMarker = new HashMap<>(); - Random random = new Random(System.currentTimeMillis()); - private BpmnEngineList.BpmnServerDefinition serverDefinition; - private ZeebeClient zeebeClient; - private CamundaOperateClient operateClient; - private CamundaTaskListClient taskClient; - // Default - private BpmnEngineList.CamundaEngine typeCamundaEngine = BpmnEngineList.CamundaEngine.CAMUNDA_8; - - private BpmnEngineCamunda8(BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy) { - this.exceptionHandlingStrategy = exceptionHandlingStrategy; - } - - /** - * Constructor from existing object - * - * @param serverDefinition server definition - * @param logDebug if true, operation will be logged as debug level - */ - public static BpmnEngineCamunda8 getFromServerDefinition(BpmnEngineList.BpmnServerDefinition serverDefinition, - BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy, - boolean logDebug) { - BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); - bpmnEngineCamunda8.serverDefinition = serverDefinition; - return bpmnEngineCamunda8; - - } - - /** - * Constructor to specify a Self Manage Zeebe Address por a Zeebe Saas - * - * @param zeebeSelfGatewayAddress Self Manage : zeebe address - * @param zeebePlainText Self Manage: Plain text - * @param operateUrl URL to access Operate - * @param operateUserName Operate user name - * @param operateUserPassword Operate password - * @param tasklistUrl Url to access TaskList - */ - public static BpmnEngineCamunda8 getFromCamunda8(String zeebeSelfGatewayAddress, - Boolean zeebePlainText, - String operateUrl, - String operateUserName, - String operateUserPassword, - String tasklistUrl, - BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { - BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); - bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; - bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - bpmnEngineCamunda8.serverDefinition.zeebeGatewayAddress = zeebeSelfGatewayAddress; - bpmnEngineCamunda8.serverDefinition.zeebePlainText = zeebePlainText; - - - /* - * Connection to Operate + public static final String THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME = "ThisIsACompleteImpossibleVariableName"; + public static final int SEARCH_MAX_SIZE = 100; + public static final String SAAS_AUTHENTICATE_URL = "https://login.cloud.camunda.io/oauth/token"; + private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda8.class); + private final BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy; + boolean hightFlowMode = false; + /** + * It is not possible to search user task for a specfic processInstance. So, to realize this, a marker is created in each process instance. Retrieving the user task, + * the process instance can be found and correction can be done */ - bpmnEngineCamunda8.serverDefinition.operateUserName = operateUserName; - bpmnEngineCamunda8.serverDefinition.operateUserPassword = operateUserPassword; - bpmnEngineCamunda8.serverDefinition.operateUrl = operateUrl; - bpmnEngineCamunda8.serverDefinition.taskListUrl = tasklistUrl; - return bpmnEngineCamunda8; - - } - - /** - * Constructor to specify a Self Manage Zeebe Address por a Zeebe Saas - * - * @param zeebeSaasCloudRegion Saas Cloud region - * @param zeebeSaasCloudClusterId Saas Cloud ClusterID - * @param zeebeSaasCloudClientId Saas Cloud ClientID - * @param zeebeSaasClientSecret Saas Cloud Client Secret - * @param operateUrl URL to access Operate - * @param operateUserName Operate user name - * @param operateUserPassword Operate password - * @param tasklistUrl Url to access TaskList - */ - public static BpmnEngineCamunda8 getFromCamunda8SaaS(String zeebeSaasCloudRegion, - String zeebeSaasCloudClusterId, - String zeebeSaasAudience, - String zeebeSaasCloudClientId, - String zeebeSaasClientSecret, - String zeebeSaasAuthenticationUrl, - String operateUrl, - String operateUserName, - String operateUserPassword, - String tasklistUrl, - BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { - BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); - bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; - - - /* - * SaaS Zeebe - */ - bpmnEngineCamunda8.serverDefinition.zeebeSaasRegion = zeebeSaasCloudRegion; - bpmnEngineCamunda8.serverDefinition.zeebeSaasClusterId = zeebeSaasCloudClusterId; - bpmnEngineCamunda8.serverDefinition.zeebeClientId = zeebeSaasCloudClientId; - bpmnEngineCamunda8.serverDefinition.zeebeClientSecret = zeebeSaasClientSecret; - bpmnEngineCamunda8.serverDefinition.authenticationUrl = zeebeSaasAuthenticationUrl; - bpmnEngineCamunda8.serverDefinition.zeebeAudience = zeebeSaasAudience; - - /* - * Connection to Operate - */ - bpmnEngineCamunda8.serverDefinition.operateUserName = operateUserName; - bpmnEngineCamunda8.serverDefinition.operateUserPassword = operateUserPassword; - bpmnEngineCamunda8.serverDefinition.operateUrl = operateUrl; - bpmnEngineCamunda8.serverDefinition.taskListUrl = tasklistUrl; - return bpmnEngineCamunda8; - } - - @Override - public void init() { - // nothing to do there - } - - public void connection() throws AutomatorException { - - this.typeCamundaEngine = this.serverDefinition.serverType; - StringBuilder analysis = new StringBuilder(); - try { - connectZeebe(analysis); - connectOperate(analysis); - connectTaskList(analysis); - logger.info("Zeebe: OK, Operate: OK, TaskList:OK {}", analysis); - - } catch (AutomatorException e) { - zeebeClient = null; - throw e; + Map<String, Long> cacheProcessInstanceMarker = new HashMap<>(); + Random random = new Random(System.currentTimeMillis()); + private BpmnEngineList.BpmnServerDefinition serverDefinition; + private ZeebeClient zeebeClient; + private CamundaOperateClient operateClient; + private CamundaTaskListClient taskClient; + // Default + private BpmnEngineList.CamundaEngine typeCamundaEngine = BpmnEngineList.CamundaEngine.CAMUNDA_8; + + private BpmnEngineCamunda8(BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy) { + this.exceptionHandlingStrategy = exceptionHandlingStrategy; } - } - - public void disconnection() { - // nothing to do here - } - - /** - * Engine is ready. If not, a connection() method must be call - * - * @return true if the engine is ready - */ - public boolean isReady() { - return zeebeClient != null; - } - - /* ******************************************************************** */ - /* */ - /* Manage process instance */ - /* */ - /* ******************************************************************** */ - - /** - * HighFlowMode: when true, the class does not save anything, to reduce the footprint - * - * @param highFlowMode true or false - */ - public void turnHighFlowMode(boolean highFlowMode) { - this.hightFlowMode = highFlowMode; - } - - @Override - public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) - throws AutomatorException { - try { - String marker = null; - if (!hightFlowMode) { - marker = getUniqueMarker(processId, starterEventId); - variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); - } - FinalCommandStep createCommand = zeebeClient.newCreateInstanceCommand() - .bpmnProcessId(processId) - .latestVersion() - .variables(variables); - RefactoredCommandWrapper command = new RefactoredCommandWrapper(createCommand, - System.currentTimeMillis() + 5 * 60 * 1000, - // 5 minutes - "CreatePi" + processId, exceptionHandlingStrategy); - - ProcessInstanceEvent workflowInstanceEvent = (ProcessInstanceEvent) command.executeSync(); - Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); - if (!hightFlowMode) { - cacheProcessInstanceMarker.put(marker, processInstanceId); - } - return String.valueOf(processInstanceId); - } catch (Exception e) { - throw new AutomatorException("CreateProcessInstance Error[" + processId + "] :" + e.getMessage()); - } - } - - public String createProcessInstanceDirect(String processId, String starterEventId, Map<String, Object> variables) - throws AutomatorException { - try { - String marker = null; - if (!hightFlowMode) { - marker = getUniqueMarker(processId, starterEventId); - variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); - } + /** + * Constructor from existing object + * + * @param serverDefinition server definition + * @param logDebug if true, operation will be logged as debug level + */ + public static BpmnEngineCamunda8 getFromServerDefinition(BpmnEngineList.BpmnServerDefinition serverDefinition, + BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy, + boolean logDebug) { + BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); + bpmnEngineCamunda8.serverDefinition = serverDefinition; + return bpmnEngineCamunda8; - ProcessInstanceEvent workflowInstanceEvent = zeebeClient.newCreateInstanceCommand() - .bpmnProcessId(processId) - .latestVersion() - .variables(variables) - .send() - .join(); - Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); - if (!hightFlowMode) { - cacheProcessInstanceMarker.put(marker, processInstanceId); - } - return String.valueOf(processInstanceId); - } catch (Exception e) { - throw new AutomatorException("Can't create in process [" + processId + "] :" + e.getMessage()); } - } - - - /* ******************************************************************** */ - /* */ - /* User tasks */ - /* */ - /* ******************************************************************** */ - - @Override - public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { - // clean in the cache - List<String> markers = cacheProcessInstanceMarker.entrySet() - .stream() - .filter(t -> t.getValue().equals(Long.valueOf(processInstanceId))) - .map(Map.Entry::getKey) - .toList(); - markers.forEach(t -> cacheProcessInstanceMarker.remove(t)); - - } - - @Override - public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) - throws AutomatorException { - try { - // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one - Long processInstanceIdLong = Long.valueOf(processInstanceId); - - TaskSearch taskSearch = new TaskSearch(); - taskSearch.setState(TaskState.CREATED); - taskSearch.setAssigned(Boolean.FALSE); - taskSearch.setWithVariables(true); - taskSearch.setPagination(new Pagination().setPageSize(maxResult)); - - TaskList tasksList = taskClient.getTasks(taskSearch); - List<String> listTasksResult = new ArrayList<>(); - do { - listTasksResult.addAll(tasksList.getItems().stream().filter(t -> { - List<Variable> listVariables = t.getVariables(); - Optional<Variable> markerTask = listVariables.stream() - .filter(v -> v.getName().equals(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME)) - .findFirst(); - - if (markerTask.isEmpty()) - return false; - Long processInstanceIdTask = cacheProcessInstanceMarker.get(markerTask.get().getValue()); - return (processInstanceIdLong.equals(processInstanceIdTask)); - }).map(Task::getId) // Task to ID - .toList()); - - if (tasksList.size() > 0) - tasksList = taskClient.after(tasksList); - } while (tasksList.size() > 0); - return listTasksResult; + /** + * Constructor to specify a Self Manage Zeebe Address por a Zeebe Saas + * + * @param zeebeSelfGatewayAddress Self Manage : zeebe address + * @param zeebePlainText Self Manage: Plain text + * @param operateUrl URL to access Operate + * @param operateUserName Operate user name + * @param operateUserPassword Operate password + * @param tasklistUrl Url to access TaskList + */ + public static BpmnEngineCamunda8 getFromCamunda8(String zeebeSelfGatewayAddress, + Boolean zeebePlainText, + String operateUrl, + String operateUserName, + String operateUserPassword, + String tasklistUrl, + BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { + BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); + bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + bpmnEngineCamunda8.serverDefinition.zeebeGatewayAddress = zeebeSelfGatewayAddress; + bpmnEngineCamunda8.serverDefinition.zeebePlainText = zeebePlainText; + + + /* + * Connection to Operate + */ + bpmnEngineCamunda8.serverDefinition.operateUserName = operateUserName; + bpmnEngineCamunda8.serverDefinition.operateUserPassword = operateUserPassword; + bpmnEngineCamunda8.serverDefinition.operateUrl = operateUrl; + bpmnEngineCamunda8.serverDefinition.taskListUrl = tasklistUrl; + return bpmnEngineCamunda8; - } catch (TaskListException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); } - } - - @Override - public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { - try { - // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one - - TaskSearch taskSearch = new TaskSearch(); - taskSearch.setState(TaskState.CREATED); - taskSearch.setAssigned(Boolean.FALSE); - taskSearch.setWithVariables(true); - taskSearch.setPagination(new Pagination().setPageSize(maxResult)); - - TaskList tasksList = taskClient.getTasks(taskSearch); - List<String> listTasksResult = new ArrayList<>(); - do { - listTasksResult.addAll(tasksList.getItems().stream().map(Task::getId) // Task to ID - .toList()); - - if (tasksList.size() > 0) - tasksList = taskClient.after(tasksList); - } while (tasksList.size() > 0); - return listTasksResult; - - } catch (TaskListException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); - } - } - - /* ******************************************************************** */ - /* */ - /* Service tasks */ - /* */ - /* ******************************************************************** */ - @Override - public RegisteredTask registerServiceTask(String workerId, - String topic, - boolean streamEnabled, - Duration lockTime, - Object jobHandler, - FixedBackoffSupplier backoffSupplier) { - if (!(jobHandler instanceof JobHandler)) { - logger.error("handler is not a JobHandler implementation, can't register the worker [{}], topic [{}]", workerId, - topic); - return null; + /** + * Constructor to specify a Self Manage Zeebe Address por a Zeebe Saas + * + * @param zeebeSaasCloudRegion Saas Cloud region + * @param zeebeSaasCloudClusterId Saas Cloud ClusterID + * @param zeebeSaasCloudClientId Saas Cloud ClientID + * @param zeebeSaasClientSecret Saas Cloud Client Secret + * @param operateUrl URL to access Operate + * @param operateUserName Operate user name + * @param operateUserPassword Operate password + * @param tasklistUrl Url to access TaskList + */ + public static BpmnEngineCamunda8 getFromCamunda8SaaS(String zeebeSaasCloudRegion, + String zeebeSaasCloudClusterId, + String zeebeSaasAudience, + String zeebeSaasCloudClientId, + String zeebeSaasClientSecret, + String zeebeSaasAuthenticationUrl, + String operateUrl, + String operateUserName, + String operateUserPassword, + String tasklistUrl, + BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { + BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); + bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + + + /* + * SaaS Zeebe + */ + bpmnEngineCamunda8.serverDefinition.zeebeSaasRegion = zeebeSaasCloudRegion; + bpmnEngineCamunda8.serverDefinition.zeebeSaasClusterId = zeebeSaasCloudClusterId; + bpmnEngineCamunda8.serverDefinition.zeebeClientId = zeebeSaasCloudClientId; + bpmnEngineCamunda8.serverDefinition.zeebeClientSecret = zeebeSaasClientSecret; + bpmnEngineCamunda8.serverDefinition.authenticationUrl = zeebeSaasAuthenticationUrl; + bpmnEngineCamunda8.serverDefinition.zeebeAudience = zeebeSaasAudience; + + /* + * Connection to Operate + */ + bpmnEngineCamunda8.serverDefinition.operateUserName = operateUserName; + bpmnEngineCamunda8.serverDefinition.operateUserPassword = operateUserPassword; + bpmnEngineCamunda8.serverDefinition.operateUrl = operateUrl; + bpmnEngineCamunda8.serverDefinition.taskListUrl = tasklistUrl; + return bpmnEngineCamunda8; } - if (topic == null) { - logger.error("topic must not be null, can't register the worker [{}]", workerId); - return null; + @Override + public void init() { + // nothing to do there } - RegisteredTask registeredTask = new RegisteredTask(); - - logger.info("Create worker[{}] Topic[{}] StreamEnabled[{}] LockTime[{}] WorkerExecutionThreads[{}] MaxJobsActive[{}]", // label - workerId,topic,streamEnabled,lockTime, - serverDefinition.workerExecutionThreads, - serverDefinition.workerMaxJobsActive); + public void connection() throws AutomatorException { - JobWorkerBuilderStep1.JobWorkerBuilderStep3 step3 = zeebeClient.newWorker() - .jobType(topic) - .handler((JobHandler) jobHandler) - .timeout(lockTime) - .streamEnabled(streamEnabled) - .name(workerId); + this.typeCamundaEngine = this.serverDefinition.serverType; + StringBuilder analysis = new StringBuilder(); + try { + connectZeebe(analysis); + connectOperate(analysis); + connectTaskList(analysis); + logger.info("Zeebe: OK, Operate: OK, TaskList:OK {}", analysis); + + } catch (AutomatorException e) { + zeebeClient = null; + throw e; + } + } - if (backoffSupplier != null) { - step3.backoffSupplier(backoffSupplier); + public void disconnection() { + // nothing to do here } - registeredTask.jobWorker = step3.open(); - return registeredTask; - } - @Override - public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) - throws AutomatorException { - try { - taskClient.claim(userTaskId, serverDefinition.operateUserName); - taskClient.completeTask(userTaskId, variables); - } catch (TaskListException e) { - throw new AutomatorException("Can't execute task [" + userTaskId + "]"); + /** + * Engine is ready. If not, a connection() method must be call + * + * @return true if the engine is ready + */ + public boolean isReady() { + return zeebeClient != null; } - } - @Override - public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) - throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - long processInstanceIdLong = Long.parseLong(processInstanceId); + /* ******************************************************************** */ + /* */ + /* Manage process instance */ + /* */ + /* ******************************************************************** */ - ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder() - .parentKey(processInstanceIdLong) - .build(); + /** + * HighFlowMode: when true, the class does not save anything, to reduce the footprint + * + * @param highFlowMode true or false + */ + public void turnHighFlowMode(boolean highFlowMode) { + this.hightFlowMode = highFlowMode; + } - SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter).size(100).build(); - - List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); - Set<Long> setProcessInstances = listProcessInstances.stream() - .map(ProcessInstance::getKey) - .collect(Collectors.toSet()); - setProcessInstances.add(processInstanceIdLong); - - ActivateJobsResponse jobsResponse = zeebeClient.newActivateJobsCommand() - .jobType(topic) - .maxJobsToActivate(10000) - .workerName(Thread.currentThread().getName()) - .send() - .join(); - List<String> listJobsId = new ArrayList<>(); - - for (ActivatedJob job : jobsResponse.getJobs()) { - if (setProcessInstances.contains(job.getProcessInstanceKey())) - listJobsId.add(String.valueOf(job.getKey())); - else { - zeebeClient.newFailCommand(job.getKey()).retries(2).send().join(); + @Override + public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) + throws AutomatorException { + try { + String marker = null; + if (!hightFlowMode) { + marker = getUniqueMarker(processId, starterEventId); + variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); + } + + FinalCommandStep createCommand = zeebeClient.newCreateInstanceCommand() + .bpmnProcessId(processId) + .latestVersion() + .variables(variables); + RefactoredCommandWrapper command = new RefactoredCommandWrapper(createCommand, + System.currentTimeMillis() + 5 * 60 * 1000, + // 5 minutes + "CreatePi" + processId, exceptionHandlingStrategy); + + ProcessInstanceEvent workflowInstanceEvent = (ProcessInstanceEvent) command.executeSync(); + Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); + if (!hightFlowMode) { + cacheProcessInstanceMarker.put(marker, processInstanceId); + } + return String.valueOf(processInstanceId); + } catch (Exception e) { + throw new AutomatorException("CreateProcessInstance Error[" + processId + "] :" + e.getMessage()); } - } - return listJobsId; - - } catch (Exception e) { - throw new AutomatorException("Can't search service task topic[" + topic + "] : " + e.getMessage()); } - } - + public String createProcessInstanceDirect(String processId, String starterEventId, Map<String, Object> variables) + throws AutomatorException { + try { + String marker = null; + if (!hightFlowMode) { + marker = getUniqueMarker(processId, starterEventId); + variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); + } + + ProcessInstanceEvent workflowInstanceEvent = zeebeClient.newCreateInstanceCommand() + .bpmnProcessId(processId) + .latestVersion() + .variables(variables) + .send() + .join(); + Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); + if (!hightFlowMode) { + cacheProcessInstanceMarker.put(marker, processInstanceId); + } + return String.valueOf(processInstanceId); + } catch (Exception e) { + throw new AutomatorException("Can't create in process [" + processId + "] :" + e.getMessage()); + } + } - /* ******************************************************************** */ - /* */ - /* generic search */ - /* */ - /* ******************************************************************** */ + /* ******************************************************************** */ + /* */ + /* User tasks */ + /* */ + /* ******************************************************************** */ - @Override - public void executeServiceTask(String serviceTaskId, String workerId, Map<String, Object> variables) - throws AutomatorException { - try { - zeebeClient.newCompleteCommand(Long.valueOf(serviceTaskId)).variables(variables).send().join(); - } catch (Exception e) { - throw new AutomatorException("Can't execute service task " + e.getMessage()); - } - } + @Override + public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { + // clean in the cache + List<String> markers = cacheProcessInstanceMarker.entrySet() + .stream() + .filter(t -> t.getValue().equals(Long.valueOf(processInstanceId))) + .map(Map.Entry::getKey) + .toList(); + markers.forEach(t -> cacheProcessInstanceMarker.remove(t)); - public void throwBpmnServiceTask(String serviceTaskId, - String workerId, - String errorCode, - String errorMessage, - Map<String, Object> variables) throws AutomatorException { - try { - zeebeClient.newThrowErrorCommand(Long.valueOf(serviceTaskId)) - .errorCode(errorCode) - .errorMessage(errorMessage) - .variables(variables) - .send() - .join(); - } catch (Exception e) { - throw new AutomatorException("Can't execute service task " + e.getMessage()); } - } - @Override - public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) - throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee - FlowNodeInstanceFilter flownodeFilter = FlowNodeInstanceFilter.builder() - .processInstanceKey(Long.valueOf(processInstanceId)) - .build(); - - SearchQuery flownodeQuery = new SearchQuery.Builder().filter(flownodeFilter).size(maxResult).build(); - List<FlowNodeInstance> flownodes = operateClient.searchFlowNodeInstances(flownodeQuery); - return flownodes.stream().filter(t -> taskId.equals(t.getFlowNodeId())).map(t -> { - TaskDescription taskDescription = new TaskDescription(); - taskDescription.taskId = t.getFlowNodeId(); - taskDescription.type = getTaskType(t.getType()); // to implement - taskDescription.isCompleted = FlowNodeInstanceState.COMPLETED.equals(t.getState()); // to implement - return taskDescription; - }).toList(); - - } catch (OperateException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); + @Override + public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) + throws AutomatorException { + try { + // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one + Long processInstanceIdLong = Long.valueOf(processInstanceId); + + TaskSearch taskSearch = new TaskSearch(); + taskSearch.setState(TaskState.CREATED); + taskSearch.setAssigned(Boolean.FALSE); + taskSearch.setWithVariables(true); + taskSearch.setPagination(new Pagination().setPageSize(maxResult)); + + TaskList tasksList = taskClient.getTasks(taskSearch); + List<String> listTasksResult = new ArrayList<>(); + do { + listTasksResult.addAll(tasksList.getItems().stream().filter(t -> { + List<Variable> listVariables = t.getVariables(); + Optional<Variable> markerTask = listVariables.stream() + .filter(v -> v.getName().equals(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME)) + .findFirst(); + + if (markerTask.isEmpty()) + return false; + Long processInstanceIdTask = cacheProcessInstanceMarker.get(markerTask.get().getValue()); + return (processInstanceIdLong.equals(processInstanceIdTask)); + }).map(Task::getId) // Task to ID + .toList()); + + if (tasksList.size() > 0) + tasksList = taskClient.after(tasksList); + } while (tasksList.size() > 0); + + return listTasksResult; + + } catch (TaskListException e) { + throw new AutomatorException("Can't search users task " + e.getMessage()); + } } - } - public List<ProcessDescription> searchProcessInstanceByVariable(String processId, - Map<String, Object> filterVariables, - int maxResult) throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } + @Override + public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { + try { + // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one - // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee - ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder().bpmnProcessId(processId).build(); + TaskSearch taskSearch = new TaskSearch(); + taskSearch.setState(TaskState.CREATED); + taskSearch.setAssigned(Boolean.FALSE); + taskSearch.setWithVariables(true); + taskSearch.setPagination(new Pagination().setPageSize(maxResult)); - SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter) - .size(maxResult) - .build(); - List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); + TaskList tasksList = taskClient.getTasks(taskSearch); + List<String> listTasksResult = new ArrayList<>(); + do { + listTasksResult.addAll(tasksList.getItems().stream().map(Task::getId) // Task to ID + .toList()); - List<ProcessDescription> listProcessInstanceFind = new ArrayList<>(); - // now, we have to filter based on variableName/value + if (tasksList.size() > 0) + tasksList = taskClient.after(tasksList); + } while (tasksList.size() > 0); - for (ProcessInstance processInstance : listProcessInstances) { - Map<String, Object> processVariables = getVariables(processInstance.getKey().toString()); - List<Map.Entry<String, Object>> entriesNotFiltered = filterVariables.entrySet() - .stream() - .filter( - t -> processVariables.containsKey(t.getKey()) && processVariables.get(t.getKey()).equals(t.getValue())) - .toList(); + return listTasksResult; - if (entriesNotFiltered.isEmpty()) { + } catch (TaskListException e) { + throw new AutomatorException("Can't search users task " + e.getMessage()); + } + } - ProcessDescription processDescription = new ProcessDescription(); - processDescription.processInstanceId = processInstance.getKey().toString(); + /* ******************************************************************** */ + /* */ + /* Service tasks */ + /* */ + /* ******************************************************************** */ + @Override + public RegisteredTask registerServiceTask(String workerId, + String topic, + boolean streamEnabled, + Duration lockTime, + Object jobHandler, + FixedBackoffSupplier backoffSupplier) { + if (!(jobHandler instanceof JobHandler)) { + logger.error("handler is not a JobHandler implementation, can't register the worker [{}], topic [{}]", workerId, + topic); + return null; + } + if (topic == null) { + logger.error("topic must not be null, can't register the worker [{}]", workerId); + return null; - listProcessInstanceFind.add(processDescription); } - } - return listProcessInstanceFind; - } catch (OperateException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); - } - } - - private ScenarioStep.Step getTaskType(String taskTypeC8) { - if (taskTypeC8.equals("SERVICE_TASK")) - return ScenarioStep.Step.SERVICETASK; - else if (taskTypeC8.equals("USER_TASK")) - return ScenarioStep.Step.USERTASK; - else if (taskTypeC8.equals("START_EVENT")) - return ScenarioStep.Step.STARTEVENT; - else if (taskTypeC8.equals("END_EVENT")) - return ScenarioStep.Step.ENDEVENT; - else if (taskTypeC8.equals("EXCLUSIVE_GATEWAY")) - return ScenarioStep.Step.EXCLUSIVEGATEWAY; - else if (taskTypeC8.equals("PARALLEL_GATEWAY")) - return ScenarioStep.Step.PARALLELGATEWAY; - - return null; - } - - @Override - public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } + RegisteredTask registeredTask = new RegisteredTask(); - // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee - VariableFilter variableFilter = VariableFilter.builder() - .processInstanceKey(Long.valueOf(processInstanceId)) - .build(); + logger.info("Create worker[{}] Topic[{}] StreamEnabled[{}] LockTime[{}] WorkerExecutionThreads[{}] MaxJobsActive[{}]", // label + workerId, topic, streamEnabled, lockTime, + serverDefinition.workerExecutionThreads, + serverDefinition.workerMaxJobsActive); - SearchQuery variableQuery = new SearchQuery.Builder().filter(variableFilter).build(); - List<io.camunda.operate.model.Variable> listVariables = operateClient.searchVariables(variableQuery); - Map<String, Object> variables = new HashMap<>(); - listVariables.forEach(t -> variables.put(t.getName(), t.getValue())); + JobWorkerBuilderStep1.JobWorkerBuilderStep3 step3 = zeebeClient.newWorker() + .jobType(topic) + .handler((JobHandler) jobHandler) + .timeout(lockTime) + .streamEnabled(streamEnabled) + .name(workerId); - return variables; - } catch (OperateException e) { - throw new AutomatorException("Can't search variables task " + e.getMessage()); - } - } - - /* ******************************************************************** */ - /* */ - /* CountInformation */ - /* */ - /* ******************************************************************** */ - public long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) - throws AutomatorException { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); + if (backoffSupplier != null) { + step3.backoffSupplier(backoffSupplier); + } + registeredTask.jobWorker = step3.open(); + return registeredTask; } - SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); - try { - int cumul = 0; - SearchResult<ProcessInstance> searchResult = null; - queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId).build()); - queryBuilder.sort(new Sort("key", SortOrder.ASC)); - int maxLoop = 0; - do { - maxLoop++; - if (searchResult != null && !searchResult.getItems().isEmpty()) { - queryBuilder.searchAfter(searchResult.getSortValues()); + @Override + public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) + throws AutomatorException { + try { + taskClient.claim(userTaskId, serverDefinition.operateUserName); + taskClient.completeTask(userTaskId, variables); + } catch (TaskListException e) { + throw new AutomatorException("Can't execute task [" + userTaskId + "]"); } - SearchQuery searchQuery = queryBuilder.build(); - searchQuery.setSize(SEARCH_MAX_SIZE); - searchResult = operateClient.searchProcessInstanceResults(searchQuery); - - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); - - } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - } catch (Exception e) { - throw new AutomatorException("Search countNumberProcessInstanceCreated " + e.getMessage()); } - } - public long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) - throws AutomatorException { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } + @Override + public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + long processInstanceIdLong = Long.parseLong(processInstanceId); + + ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder() + .parentKey(processInstanceIdLong) + .build(); + + SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter).size(100).build(); + + List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); + Set<Long> setProcessInstances = listProcessInstances.stream() + .map(ProcessInstance::getKey) + .collect(Collectors.toSet()); + setProcessInstances.add(processInstanceIdLong); + + ActivateJobsResponse jobsResponse = zeebeClient.newActivateJobsCommand() + .jobType(topic) + .maxJobsToActivate(10000) + .workerName(Thread.currentThread().getName()) + .send() + .join(); + List<String> listJobsId = new ArrayList<>(); + + for (ActivatedJob job : jobsResponse.getJobs()) { + if (setProcessInstances.contains(job.getProcessInstanceKey())) + listJobsId.add(String.valueOf(job.getKey())); + else { + zeebeClient.newFailCommand(job.getKey()).retries(2).send().join(); + } + } + return listJobsId; - SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); - try { - int cumul = 0; - SearchResult<ProcessInstance> searchResult = null; - - queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId) - // .startDate(startDate) - // .endDate(endDate) - .state(ProcessInstanceState.COMPLETED).build()); - - queryBuilder.sort(new Sort("key", SortOrder.ASC)); - int maxLoop = 0; - do { - maxLoop++; - if (searchResult != null && !searchResult.getItems().isEmpty()) { - queryBuilder.searchAfter(searchResult.getSortValues()); + } catch (Exception e) { + throw new AutomatorException("Can't search service task topic[" + topic + "] : " + e.getMessage()); } - SearchQuery searchQuery = queryBuilder.build(); - searchQuery.setSize(SEARCH_MAX_SIZE); - searchResult = operateClient.searchProcessInstanceResults(searchQuery); - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + } - } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - } catch (Exception e) { - throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); - } - } - /* ******************************************************************** */ - /* */ - /* Deployment */ - /* */ - /* ******************************************************************** */ - - public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - try { - int cumul = 0; - SearchResult<FlowNodeInstance> searchResult = null; - int maxLoop = 0; - do { - maxLoop++; + /* ******************************************************************** */ + /* */ + /* generic search */ + /* */ + /* ******************************************************************** */ - SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); - queryBuilder = queryBuilder.filter(FlowNodeInstanceFilter.builder().flowNodeId(taskId).build()); - queryBuilder.sort(new Sort("key", SortOrder.ASC)); - if (searchResult != null && !searchResult.getItems().isEmpty()) { - queryBuilder.searchAfter(searchResult.getSortValues()); + @Override + public void executeServiceTask(String serviceTaskId, String workerId, Map<String, Object> variables) + throws AutomatorException { + try { + zeebeClient.newCompleteCommand(Long.valueOf(serviceTaskId)).variables(variables).send().join(); + } catch (Exception e) { + throw new AutomatorException("Can't execute service task " + e.getMessage()); } - SearchQuery searchQuery = queryBuilder.build(); - searchQuery.setSize(SEARCH_MAX_SIZE); - searchResult = operateClient.searchFlowNodeInstanceResults(searchQuery); - cumul += (long) searchResult.getItems().size(); - } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - } catch (Exception e) { - throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); } - } - - - /* ******************************************************************** */ - /* */ - /* get server definition */ - /* */ - /* ******************************************************************** */ - @Override - public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { - try { - DeploymentEvent event = zeebeClient.newDeployResourceCommand() - .addResourceFile(processFile.getAbsolutePath()) - .send() - .join(); + public void throwBpmnServiceTask(String serviceTaskId, + String workerId, + String errorCode, + String errorMessage, + Map<String, Object> variables) throws AutomatorException { + try { + zeebeClient.newThrowErrorCommand(Long.valueOf(serviceTaskId)) + .errorCode(errorCode) + .errorMessage(errorMessage) + .variables(variables) + .send() + .join(); + } catch (Exception e) { + throw new AutomatorException("Can't execute service task " + e.getMessage()); + } + } - return String.valueOf(event.getKey()); - } catch (Exception e) { - throw new AutomatorException("Can't deploy " + e.getMessage()); + /** + * @param processInstanceId filter on the processInstanceId. may be null + * @param filterTaskId filter on the taskId + * @param maxResult maximum Result + * @return list of Task + * @throws AutomatorException + */ + @Override + public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String filterTaskId, int maxResult) + throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee + FlowNodeInstanceFilter flownodeFilter = FlowNodeInstanceFilter.builder() + .processInstanceKey(Long.valueOf(processInstanceId)) + .build(); + + SearchQuery flowNodeQuery = new SearchQuery.Builder().filter(flownodeFilter).size(maxResult).build(); + List<FlowNodeInstance> flowNodes = operateClient.searchFlowNodeInstances(flowNodeQuery); + return flowNodes.stream() + .filter(t -> filterTaskId == null || filterTaskId.equals(t.getFlowNodeId())) // Filter by name + .map(t -> { + TaskDescription taskDescription = new TaskDescription(); + taskDescription.taskId = t.getFlowNodeId(); + taskDescription.processInstanceId = String.valueOf(t.getProcessInstanceKey()); + taskDescription.startDate = t.getStartDate(); + taskDescription.endDate = t.getEndDate(); + taskDescription.type = getTaskType(t.getType()); // to implement + taskDescription.isCompleted = FlowNodeInstanceState.COMPLETED.equals(t.getState()); // to implement + return taskDescription; + }).toList(); + + } catch (OperateException e) { + throw new AutomatorException("Can't search users task " + e.getMessage()); + } } - } - @Override - public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { - return typeCamundaEngine; - } + public List<ProcessDescription> searchProcessInstanceByVariable(String processId, + Map<String, Object> filterVariables, + int maxResult) throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee + ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder().bpmnProcessId(processId).build(); + + SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter) + .size(maxResult) + .build(); + List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); + + List<ProcessDescription> listProcessInstanceFind = new ArrayList<>(); + // now, we have to filter based on variableName/value + + for (ProcessInstance processInstance : listProcessInstances) { + Map<String, Object> processVariables = getVariables(processInstance.getKey().toString()); + List<Map.Entry<String, Object>> entriesNotFiltered = filterVariables.entrySet() + .stream() + .filter( + t -> processVariables.containsKey(t.getKey()) && processVariables.get(t.getKey()).equals(t.getValue())) + .toList(); + + if (entriesNotFiltered.isEmpty()) { + + ProcessDescription processDescription = new ProcessDescription(); + processDescription.processInstanceId = processInstance.getKey().toString(); + + listProcessInstanceFind.add(processDescription); + } + } + return listProcessInstanceFind; + } catch (OperateException e) { + throw new AutomatorException("Can't search users task " + e.getMessage()); + } + } - @Override - public String getSignature() { - String signature = typeCamundaEngine.toString() + " "; - if (typeCamundaEngine.equals(BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS)) - signature += - "Cloud ClientId[" + serverDefinition.zeebeClientId + "] ClusterId[" + serverDefinition.zeebeSaasClusterId - + "]"; - else - signature += "Address[" + serverDefinition.zeebeGatewayAddress + "]"; - signature += " numJobWorkerExecutionThreads[" + serverDefinition.workerExecutionThreads + "] workerMaxJobsActive[" - + serverDefinition.workerMaxJobsActive + "]"; - return signature; - } + private ScenarioStep.Step getTaskType(String taskTypeC8) { + if (taskTypeC8.equals("SERVICE_TASK")) + return ScenarioStep.Step.SERVICETASK; + else if (taskTypeC8.equals("USER_TASK")) + return ScenarioStep.Step.USERTASK; + else if (taskTypeC8.equals("START_EVENT")) + return ScenarioStep.Step.STARTEVENT; + else if (taskTypeC8.equals("END_EVENT")) + return ScenarioStep.Step.ENDEVENT; + else if (taskTypeC8.equals("EXCLUSIVE_GATEWAY")) + return ScenarioStep.Step.EXCLUSIVEGATEWAY; + else if (taskTypeC8.equals("PARALLEL_GATEWAY")) + return ScenarioStep.Step.PARALLELGATEWAY; + else if (taskTypeC8.equals("TASK")) + return ScenarioStep.Step.TASK; + else if (taskTypeC8.equals("SCRIPT_TASK")) + return ScenarioStep.Step.SCRIPTTASK; + + return null; + } - @Override - public int getWorkerExecutionThreads() { - return serverDefinition != null ? serverDefinition.workerExecutionThreads : 0; - } + @Override + public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } - private String getUniqueMarker(String processId, String starterEventId) { - return processId + "-" + random.nextInt(1000000); - } + // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee + VariableFilter variableFilter = VariableFilter.builder() + .processInstanceKey(Long.valueOf(processInstanceId)) + .build(); - public ZeebeClient getZeebeClient() { - return zeebeClient; - } + SearchQuery variableQuery = new SearchQuery.Builder().filter(variableFilter).build(); + List<io.camunda.operate.model.Variable> listVariables = operateClient.searchVariables(variableQuery); + Map<String, Object> variables = new HashMap<>(); + listVariables.forEach(t -> variables.put(t.getName(), t.getValue())); + return variables; + } catch (OperateException e) { + throw new AutomatorException("Can't search variables task " + e.getMessage()); + } + } - /* ******************************************************************** */ - /* */ - /* Connection to each component */ - /* */ - /* ******************************************************************** */ + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + public long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } - private void connectZeebe(StringBuilder analysis) throws AutomatorException { + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + try { + int cumul = 0; + SearchResult<ProcessInstance> searchResult = null; + queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + int maxLoop = 0; + do { + maxLoop++; + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchProcessInstanceResults(searchQuery); + + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessInstanceCreated " + e.getMessage()); + } + } - // connection is critical, so let build the analysis + public long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } - boolean isOk = true; + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + try { + int cumul = 0; + SearchResult<ProcessInstance> searchResult = null; + + queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId) + // .startDate(startDate) + // .endDate(endDate) + .state(ProcessInstanceState.COMPLETED).build()); + + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + int maxLoop = 0; + do { + maxLoop++; + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchProcessInstanceResults(searchQuery); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; - isOk = stillOk(serverDefinition.name, "ZeebeConnection", analysis, false, true, isOk); - this.typeCamundaEngine = this.serverDefinition.serverType; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); + } + } + /* ******************************************************************** */ + /* */ + /* Deployment */ + /* */ + /* ******************************************************************** */ + + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } - ZeebeClientBuilder clientBuilder; + try { - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { - analysis.append("SaaS;"); + int cumul = 0; + SearchResult<FlowNodeInstance> searchResult = null; + int maxLoop = 0; + do { + maxLoop++; + + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + queryBuilder = queryBuilder.filter(FlowNodeInstanceFilter.builder().flowNodeId(taskId).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchFlowNodeInstanceResults(searchQuery); + cumul += (long) searchResult.getItems().size(); + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); + } + } - String gatewayAddressCloud = - serverDefinition.zeebeSaasClusterId + "." + serverDefinition.zeebeSaasRegion + ".zeebe.camunda.io:443"; - isOk = stillOk(gatewayAddressCloud, "GatewayAddress", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientId, "ClientId", analysis, true, true, isOk); - /* Connect to Camunda Cloud Cluster, assumes that credentials are set in environment variables. - * See JavaDoc on class level for details - */ - isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeAudience, "zeebeAudience", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientId, "ClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientSecret, "ClientSecret", analysis, true, true, isOk); + /* ******************************************************************** */ + /* */ + /* get server definition */ + /* */ + /* ******************************************************************** */ - try { + @Override + public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { + try { + DeploymentEvent event = zeebeClient.newDeployResourceCommand() + .addResourceFile(processFile.getAbsolutePath()) + .send() + .join(); - OAuthCredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() // formatting - .authorizationServerUrl( - serverDefinition.authenticationUrl != null ? serverDefinition.authenticationUrl : SAAS_AUTHENTICATE_URL) - .audience(serverDefinition.zeebeAudience) - .clientId(serverDefinition.zeebeClientId) - .clientSecret(serverDefinition.zeebeClientSecret) - .build(); + return String.valueOf(event.getKey()); + } catch (Exception e) { + throw new AutomatorException("Can't deploy " + e.getMessage()); + } + } - clientBuilder = ZeebeClient.newClientBuilder() - .gatewayAddress(gatewayAddressCloud) - .credentialsProvider(credentialsProvider); + @Override + public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { + return typeCamundaEngine; + } - } catch (Exception e) { - zeebeClient = null; - throw new AutomatorException( - "BadCredential[" + serverDefinition.name + "] Analysis:" + analysis + " : " + e.getMessage()); - } + @Override + public String getSignature() { + String signature = typeCamundaEngine.toString() + " "; + if (typeCamundaEngine.equals(BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS)) + signature += + "Cloud ClientId[" + serverDefinition.zeebeClientId + "] ClusterId[" + serverDefinition.zeebeSaasClusterId + + "]"; + else + signature += "Address[" + serverDefinition.zeebeGatewayAddress + "]"; + signature += " numJobWorkerExecutionThreads[" + serverDefinition.workerExecutionThreads + "] workerMaxJobsActive[" + + serverDefinition.workerMaxJobsActive + "]"; + return signature; } - //---------------------------- Camunda 8 Self Manage - else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { - analysis.append("SelfManage;"); - isOk = stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); - if (serverDefinition.isAuthenticationUrl()) { - analysis.append("WithAuthentication;"); - isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeAudience, "zeebeAudience", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientId, "zeebeClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientSecret, "zeebeClientSecret", analysis, true, false, isOk); - isOk = stillOk(serverDefinition.zeebePlainText, "zeebePlainText", analysis, true, true, isOk); + @Override + public int getWorkerExecutionThreads() { + return serverDefinition != null ? serverDefinition.workerExecutionThreads : 0; + } - try { - OAuthCredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() // builder - .authorizationServerUrl(serverDefinition.authenticationUrl) - .audience(serverDefinition.zeebeAudience) - .clientId(serverDefinition.zeebeClientId) - .clientSecret(serverDefinition.zeebeClientSecret) - .build(); - clientBuilder = ZeebeClient.newClientBuilder() - .gatewayAddress(serverDefinition.zeebeGatewayAddress) - .defaultTenantId(serverDefinition.zeebeTenantId == null ? "<default>" : serverDefinition.zeebeTenantId) - .credentialsProvider(credentialsProvider); - if (Boolean.TRUE.equals(serverDefinition.zeebePlainText)) - clientBuilder.usePlaintext(); + private String getUniqueMarker(String processId, String starterEventId) { + return processId + "-" + random.nextInt(1000000); + } - } catch (Exception e) { - zeebeClient = null; - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "BadCredential[" + serverDefinition.name + "] Analysis:" + analysis + " : " + e.getMessage()); - } - } else { - analysis.append("NoAuthentication;"); - // connect to local deployment; assumes that authentication is disabled - clientBuilder = ZeebeClient.newClientBuilder() - .gatewayAddress(serverDefinition.zeebeGatewayAddress) - .usePlaintext(); - } - } else - throw new AutomatorException("Invalid configuration"); + public ZeebeClient getZeebeClient() { + return zeebeClient; + } - // ---------------- connection - try { - isOk = stillOk(serverDefinition.workerExecutionThreads, "ExecutionThread", analysis, false, true, isOk); - - analysis.append(" ExecutionThread["); - analysis.append(serverDefinition.workerExecutionThreads); - analysis.append("] MaxJobsActive["); - analysis.append(serverDefinition.workerMaxJobsActive); - analysis.append("] "); - if (serverDefinition.workerMaxJobsActive == -1) { - serverDefinition.workerMaxJobsActive = serverDefinition.workerExecutionThreads; - analysis.append("No workerMaxJobsActive defined, align to ExecutionThread["); - analysis.append(serverDefinition.workerExecutionThreads); - analysis.append("]"); - } - if (serverDefinition.workerExecutionThreads > serverDefinition.workerMaxJobsActive) { - logger.error( - "Camunda8 [{}] Incorrect definition: the workerExecutionThreads {} must be <= workerMaxJobsActive {} , else ZeebeClient will not fetch enough jobs to feed threads", - serverDefinition.name, serverDefinition.workerExecutionThreads, serverDefinition.workerMaxJobsActive); - } - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); - clientBuilder.numJobWorkerExecutionThreads(serverDefinition.workerExecutionThreads); - clientBuilder.defaultJobWorkerMaxJobsActive(serverDefinition.workerMaxJobsActive); + /* ******************************************************************** */ + /* */ + /* Connection to each component */ + /* */ + /* ******************************************************************** */ - analysis.append("Zeebe connection..."); - zeebeClient = clientBuilder.build(); + private void connectZeebe(StringBuilder analysis) throws AutomatorException { - // simple test - Topology join = zeebeClient.newTopologyRequest().send().join(); + // connection is critical, so let build the analysis - // Actually, if an error arrived, an exception is thrown + boolean isOk = true; - analysis.append(join != null ? "successfully, " : "error, "); + isOk = stillOk(serverDefinition.name, "ZeebeConnection", analysis, false, true, isOk); + this.typeCamundaEngine = this.serverDefinition.serverType; - } catch (Exception e) { - zeebeClient = null; - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } - } - - /** - * Connect Operate - * - * @param analysis to cpmplete the analysis - * @throws AutomatorException in case of error - */ - private void connectOperate(StringBuilder analysis) throws AutomatorException { - if (!serverDefinition.isOperate()) { - analysis.append("No operate connection required, "); - return; - } - analysis.append("Operate connection..."); + ZeebeClientBuilder clientBuilder; - boolean isOk = true; - isOk = stillOk(serverDefinition.operateUrl, "operateUrl", analysis, true, true, isOk); + // ---------------------------- Camunda Saas + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { + analysis.append("SaaS;"); - CamundaOperateClientBuilder camundaOperateClientBuilder = new CamundaOperateClientBuilder(); - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { + String gatewayAddressCloud = + serverDefinition.zeebeSaasClusterId + "." + serverDefinition.zeebeSaasRegion + ".zeebe.camunda.io:443"; + isOk = stillOk(gatewayAddressCloud, "GatewayAddress", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientId, "ClientId", analysis, true, true, isOk); - try { - isOk = stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientId, "zeebeClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientSecret, "zeebeClientSecret", analysis, true, false, isOk); - - URL operateUrl = URI.create("https://" + serverDefinition.zeebeSaasRegion + ".operate.camunda.io/" - + serverDefinition.zeebeSaasClusterId).toURL(); - - SaaSAuthenticationBuilder saaSAuthenticationBuilder = SaaSAuthentication.builder(); - JwtConfig jwtConfig = new JwtConfig(); - jwtConfig.addProduct(Product.OPERATE, - new JwtCredential(serverDefinition.zeebeClientId, serverDefinition.zeebeClientSecret, - serverDefinition.operateAudience != null ? serverDefinition.operateAudience : "operate.camunda.io", - serverDefinition.authenticationUrl != null ? - serverDefinition.authenticationUrl : - SAAS_AUTHENTICATE_URL)); - - Authentication saasAuthentication = SaaSAuthentication.builder() - .withJwtConfig(jwtConfig) - .withJsonMapper(new SdkObjectMapper()) - .build(); - - camundaOperateClientBuilder.authentication(saasAuthentication) - .operateUrl(serverDefinition.operateUrl) - .setup() - .build(); + /* Connect to Camunda Cloud Cluster, assumes that credentials are set in environment variables. + * See JavaDoc on class level for details + */ + isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeAudience, "zeebeAudience", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientId, "ClientId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientSecret, "ClientSecret", analysis, true, true, isOk); - } catch (Exception e) { - zeebeClient = null; - logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } + try { - //---------------------------- Camunda 8 Self Manage - } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + OAuthCredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() // formatting + .authorizationServerUrl( + serverDefinition.authenticationUrl != null ? serverDefinition.authenticationUrl : SAAS_AUTHENTICATE_URL) + .audience(serverDefinition.zeebeAudience) + .clientId(serverDefinition.zeebeClientId) + .clientSecret(serverDefinition.zeebeClientSecret) + .build(); - isOk = stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); + clientBuilder = ZeebeClient.newClientBuilder() + .gatewayAddress(gatewayAddressCloud) + .credentialsProvider(credentialsProvider); - try { - if (serverDefinition.isAuthenticationUrl()) { - isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.operateClientId, "operateClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.operateClientSecret, "operateClientSecret", analysis, true, false, isOk); - - IdentityConfiguration identityConfiguration = new IdentityConfiguration.Builder().withBaseUrl( - serverDefinition.identityUrl) - .withIssuer(serverDefinition.authenticationUrl) - .withIssuerBackendUrl(serverDefinition.authenticationUrl) - .withClientId(serverDefinition.operateClientId) - .withClientSecret(serverDefinition.operateClientSecret) - .withAudience(serverDefinition.operateAudience) - .build(); - Identity identity = new Identity(identityConfiguration); - - IdentityConfig identityConfig = new IdentityConfig(); - identityConfig.addProduct(Product.OPERATE, new IdentityContainer(identity, identityConfiguration)); - - JwtConfig jwtConfig = new JwtConfig(); - jwtConfig.addProduct(Product.OPERATE, new JwtCredential(serverDefinition.operateClientId, // clientId - serverDefinition.operateClientSecret, // clientSecret - "zeebe-api", // audience - serverDefinition.authenticationUrl)); - - io.camunda.common.auth.SelfManagedAuthenticationBuilder identityAuthenticationBuilder = io.camunda.common.auth.SelfManagedAuthentication.builder(); - identityAuthenticationBuilder.withJwtConfig(jwtConfig); - identityAuthenticationBuilder.withIdentityConfig(identityConfig); - - Authentication identityAuthentication = identityAuthenticationBuilder.build(); - camundaOperateClientBuilder.authentication(identityAuthentication) - .operateUrl(serverDefinition.operateUrl) - .setup() - .build(); - - } else { - // Simple authentication - isOk = stillOk(serverDefinition.operateUserName, "operateUserName", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.operateUserPassword, "operateUserPassword", analysis, true, false, isOk); - - SimpleCredential simpleCredential = new SimpleCredential(serverDefinition.operateUrl, - serverDefinition.operateUserName, serverDefinition.operateUserPassword); - - SimpleConfig jwtConfig = new io.camunda.common.auth.SimpleConfig(); - jwtConfig.addProduct(Product.OPERATE, simpleCredential); - - io.camunda.common.auth.SimpleAuthenticationBuilder simpleAuthenticationBuilder = SimpleAuthentication.builder(); - simpleAuthenticationBuilder.withSimpleConfig(jwtConfig); - - Authentication simpleAuthentication = simpleAuthenticationBuilder.build(); - camundaOperateClientBuilder.authentication(simpleAuthentication) - .operateUrl(serverDefinition.operateUrl) - .setup() - .build(); + } catch (Exception e) { + zeebeClient = null; + throw new AutomatorException( + "BadCredential[" + serverDefinition.name + "] Analysis:" + analysis + " : " + e.getMessage()); + } } - } catch (Exception e) { - logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } - } else - throw new AutomatorException("Invalid configuration"); + //---------------------------- Camunda 8 Self Manage + else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + analysis.append("SelfManage;"); + isOk = stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); + if (serverDefinition.isAuthenticationUrl()) { + analysis.append("WithAuthentication;"); + isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeAudience, "zeebeAudience", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientId, "zeebeClientId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientSecret, "zeebeClientSecret", analysis, true, false, isOk); + isOk = stillOk(serverDefinition.zeebePlainText, "zeebePlainText", analysis, true, true, isOk); + + try { + OAuthCredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() // builder + .authorizationServerUrl(serverDefinition.authenticationUrl) + .audience(serverDefinition.zeebeAudience) + .clientId(serverDefinition.zeebeClientId) + .clientSecret(serverDefinition.zeebeClientSecret) + .build(); + clientBuilder = ZeebeClient.newClientBuilder() + .gatewayAddress(serverDefinition.zeebeGatewayAddress) + .defaultTenantId(serverDefinition.zeebeTenantId == null ? "<default>" : serverDefinition.zeebeTenantId) + .credentialsProvider(credentialsProvider); + if (Boolean.TRUE.equals(serverDefinition.zeebePlainText)) + clientBuilder.usePlaintext(); + + } catch (Exception e) { + zeebeClient = null; + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "BadCredential[" + serverDefinition.name + "] Analysis:" + analysis + " : " + e.getMessage()); + } + } else { + analysis.append("NoAuthentication;"); + // connect to local deployment; assumes that authentication is disabled + clientBuilder = ZeebeClient.newClientBuilder() + .gatewayAddress(serverDefinition.zeebeGatewayAddress) + .usePlaintext(); + } + } else + throw new AutomatorException("Invalid configuration"); + + // ---------------- connection + try { + isOk = stillOk(serverDefinition.workerExecutionThreads, "ExecutionThread", analysis, false, true, isOk); - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); + analysis.append(" ExecutionThread["); + analysis.append(serverDefinition.workerExecutionThreads); + analysis.append("] MaxJobsActive["); + analysis.append(serverDefinition.workerMaxJobsActive); + analysis.append("] "); + if (serverDefinition.workerMaxJobsActive == -1) { + serverDefinition.workerMaxJobsActive = serverDefinition.workerExecutionThreads; + analysis.append("No workerMaxJobsActive defined, align to ExecutionThread["); + analysis.append(serverDefinition.workerExecutionThreads); + analysis.append("]"); + } + if (serverDefinition.workerExecutionThreads > serverDefinition.workerMaxJobsActive) { + logger.error( + "Camunda8 [{}] Incorrect definition: the workerExecutionThreads {} must be <= workerMaxJobsActive {} , else ZeebeClient will not fetch enough jobs to feed threads", + serverDefinition.name, serverDefinition.workerExecutionThreads, serverDefinition.workerMaxJobsActive); + } - // ---------------- connection - try { + if (!isOk) + throw new AutomatorException("Invalid configuration " + analysis); - operateClient = camundaOperateClientBuilder.build(); + clientBuilder.numJobWorkerExecutionThreads(serverDefinition.workerExecutionThreads); + clientBuilder.defaultJobWorkerMaxJobsActive(serverDefinition.workerMaxJobsActive); - analysis.append("successfully, "); + analysis.append("Zeebe connection..."); + zeebeClient = clientBuilder.build(); - } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } - } - - /** - * Connect to TaskList - * - * @param analysis complete the analysis - * @throws AutomatorException in case of error - */ - private void connectTaskList(StringBuilder analysis) throws AutomatorException { - - if (!serverDefinition.isTaskList()) { - analysis.append("No TaskList connection required, "); - return; - } - analysis.append("Tasklist ..."); + // simple test + Topology join = zeebeClient.newTopologyRequest().send().join(); - boolean isOk = true; - isOk = stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, true, true, isOk); + // Actually, if an error arrived, an exception is thrown - CamundaTaskListClientBuilder taskListBuilder = CamundaTaskListClient.builder(); - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { - try { - isOk = stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); + analysis.append(join != null ? "successfully, " : "error, "); - String taskListUrl = "https://" + serverDefinition.zeebeSaasRegion + ".tasklist.camunda.io/" - + serverDefinition.zeebeSaasClusterId; + } catch (Exception e) { + zeebeClient = null; + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); + } + } - taskListBuilder.taskListUrl(taskListUrl) - .saaSAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret); - } catch (Exception e) { - logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } + /** + * Connect Operate + * + * @param analysis to cpmplete the analysis + * @throws AutomatorException in case of error + */ + private void connectOperate(StringBuilder analysis) throws AutomatorException { + if (!serverDefinition.isOperate()) { + analysis.append("No operate connection required, "); + return; + } + analysis.append("Operate connection..."); + + boolean isOk = true; + isOk = stillOk(serverDefinition.operateUrl, "operateUrl", analysis, true, true, isOk); + + CamundaOperateClientBuilder camundaOperateClientBuilder = new CamundaOperateClientBuilder(); + // ---------------------------- Camunda Saas + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { + + try { + isOk = stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientId, "zeebeClientId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeClientSecret, "zeebeClientSecret", analysis, true, false, isOk); + + URL operateUrl = URI.create("https://" + serverDefinition.zeebeSaasRegion + ".operate.camunda.io/" + + serverDefinition.zeebeSaasClusterId).toURL(); + + SaaSAuthenticationBuilder saaSAuthenticationBuilder = SaaSAuthentication.builder(); + JwtConfig jwtConfig = new JwtConfig(); + jwtConfig.addProduct(Product.OPERATE, + new JwtCredential(serverDefinition.zeebeClientId, serverDefinition.zeebeClientSecret, + serverDefinition.operateAudience != null ? serverDefinition.operateAudience : "operate.camunda.io", + serverDefinition.authenticationUrl != null ? + serverDefinition.authenticationUrl : + SAAS_AUTHENTICATE_URL)); + + Authentication saasAuthentication = SaaSAuthentication.builder() + .withJwtConfig(jwtConfig) + .withJsonMapper(new SdkObjectMapper()) + .build(); + + camundaOperateClientBuilder.authentication(saasAuthentication) + .operateUrl(serverDefinition.operateUrl) + .setup() + .build(); + + } catch (Exception e) { + zeebeClient = null; + logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + //---------------------------- Camunda 8 Self Manage + } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + + isOk = stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); + + try { + if (serverDefinition.isAuthenticationUrl()) { + isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.operateClientId, "operateClientId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.operateClientSecret, "operateClientSecret", analysis, true, false, isOk); + + IdentityConfiguration identityConfiguration = new IdentityConfiguration.Builder().withBaseUrl( + serverDefinition.identityUrl) + .withIssuer(serverDefinition.authenticationUrl) + .withIssuerBackendUrl(serverDefinition.authenticationUrl) + .withClientId(serverDefinition.operateClientId) + .withClientSecret(serverDefinition.operateClientSecret) + .withAudience(serverDefinition.operateAudience) + .build(); + Identity identity = new Identity(identityConfiguration); + + IdentityConfig identityConfig = new IdentityConfig(); + identityConfig.addProduct(Product.OPERATE, new IdentityContainer(identity, identityConfiguration)); + + JwtConfig jwtConfig = new JwtConfig(); + jwtConfig.addProduct(Product.OPERATE, new JwtCredential(serverDefinition.operateClientId, // clientId + serverDefinition.operateClientSecret, // clientSecret + "zeebe-api", // audience + serverDefinition.authenticationUrl)); + + io.camunda.common.auth.SelfManagedAuthenticationBuilder identityAuthenticationBuilder = io.camunda.common.auth.SelfManagedAuthentication.builder(); + identityAuthenticationBuilder.withJwtConfig(jwtConfig); + identityAuthenticationBuilder.withIdentityConfig(identityConfig); + + Authentication identityAuthentication = identityAuthenticationBuilder.build(); + camundaOperateClientBuilder.authentication(identityAuthentication) + .operateUrl(serverDefinition.operateUrl) + .setup() + .build(); + + } else { + // Simple authentication + isOk = stillOk(serverDefinition.operateUserName, "operateUserName", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.operateUserPassword, "operateUserPassword", analysis, true, false, isOk); + + SimpleCredential simpleCredential = new SimpleCredential(serverDefinition.operateUrl, + serverDefinition.operateUserName, serverDefinition.operateUserPassword); + + SimpleConfig jwtConfig = new io.camunda.common.auth.SimpleConfig(); + jwtConfig.addProduct(Product.OPERATE, simpleCredential); + + io.camunda.common.auth.SimpleAuthenticationBuilder simpleAuthenticationBuilder = SimpleAuthentication.builder(); + simpleAuthenticationBuilder.withSimpleConfig(jwtConfig); + + Authentication simpleAuthentication = simpleAuthenticationBuilder.build(); + camundaOperateClientBuilder.authentication(simpleAuthentication) + .operateUrl(serverDefinition.operateUrl) + .setup() + .build(); + } + } catch (Exception e) { + logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + } else + throw new AutomatorException("Invalid configuration"); + + if (!isOk) + throw new AutomatorException("Invalid configuration " + analysis); + + // ---------------- connection + try { - //---------------------------- Camunda 8 Self Manage - } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + operateClient = camundaOperateClientBuilder.build(); - if (serverDefinition.isAuthenticationUrl()) { - isOk = stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); - isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListKeycloakUrl, "taskListKeycloakUrl", analysis, true, true, isOk); - - taskListBuilder.taskListUrl(serverDefinition.taskListUrl) - .selfManagedAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret, - serverDefinition.taskListKeycloakUrl); - } else { - isOk = stillOk(serverDefinition.taskListUserName, "User", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListUserPassword, "Password", analysis, true, false, isOk); - - SimpleConfig simpleConf = new SimpleConfig(); - simpleConf.addProduct(Product.TASKLIST, - new SimpleCredential(serverDefinition.taskListUrl, serverDefinition.taskListUserName, - serverDefinition.taskListUserPassword)); - Authentication auth = SimpleAuthentication.builder().withSimpleConfig(simpleConf).build(); - - taskListBuilder.taskListUrl(serverDefinition.taskListUrl) - .authentication(auth) - .cookieExpiration(Duration.ofSeconds(5)); - } - } else - throw new AutomatorException("Invalid configuration"); + analysis.append("successfully, "); - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); + } catch (Exception e) { + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); + } + } - // ---------------- connection - try { + /** + * Connect to TaskList + * + * @param analysis complete the analysis + * @throws AutomatorException in case of error + */ + private void connectTaskList(StringBuilder analysis) throws AutomatorException { - taskClient = taskListBuilder.build(); - analysis.append("successfully, "); + if (!serverDefinition.isTaskList()) { + analysis.append("No TaskList connection required, "); + return; + } + analysis.append("Tasklist ..."); + + boolean isOk = true; + isOk = stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, true, true, isOk); + + CamundaTaskListClientBuilder taskListBuilder = CamundaTaskListClient.builder(); + // ---------------------------- Camunda Saas + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { + try { + isOk = stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); + + String taskListUrl = "https://" + serverDefinition.zeebeSaasRegion + ".tasklist.camunda.io/" + + serverDefinition.zeebeSaasClusterId; + + taskListBuilder.taskListUrl(taskListUrl) + .saaSAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret); + } catch (Exception e) { + logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + //---------------------------- Camunda 8 Self Manage + } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + + if (serverDefinition.isAuthenticationUrl()) { + isOk = stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); + isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.taskListKeycloakUrl, "taskListKeycloakUrl", analysis, true, true, isOk); + + taskListBuilder.taskListUrl(serverDefinition.taskListUrl) + .selfManagedAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret, + serverDefinition.taskListKeycloakUrl); + } else { + isOk = stillOk(serverDefinition.taskListUserName, "User", analysis, true, true, isOk); + isOk = stillOk(serverDefinition.taskListUserPassword, "Password", analysis, true, false, isOk); + + SimpleConfig simpleConf = new SimpleConfig(); + simpleConf.addProduct(Product.TASKLIST, + new SimpleCredential(serverDefinition.taskListUrl, serverDefinition.taskListUserName, + serverDefinition.taskListUserPassword)); + Authentication auth = SimpleAuthentication.builder().withSimpleConfig(simpleConf).build(); + + taskListBuilder.taskListUrl(serverDefinition.taskListUrl) + .authentication(auth) + .cookieExpiration(Duration.ofSeconds(5)); + } + } else + throw new AutomatorException("Invalid configuration"); + + if (!isOk) + throw new AutomatorException("Invalid configuration " + analysis); + + // ---------------- connection + try { - } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } + taskClient = taskListBuilder.build(); + analysis.append("successfully, "); + + } catch (Exception e) { + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); + } /* 1.6.1 boolean isOk = true; @@ -1206,48 +1191,48 @@ private void connectTaskList(StringBuilder analysis) throws AutomatorException { } */ - } - - /** - * add in analysis and check the consistence - * - * @param value value to check - * @param message name of parameter - * @param analysis analysis builder - * @param check true if the value must not be null or empty - * @param displayValueInAnalysis true if the value can be added in the analysis - * @param wasOkBefore previous value, is returned if this check is Ok - * @return previous value is ok false else - */ - private boolean stillOk(Object value, - String message, - StringBuilder analysis, - boolean check, - boolean displayValueInAnalysis, - boolean wasOkBefore) { - analysis.append(message); - analysis.append("["); - analysis.append(getDisplayValue(value, displayValueInAnalysis)); - analysis.append("], "); - - if (check) { - if (value == null || (value instanceof String valueString && valueString.isEmpty())) { - analysis.append("No "); + } + + /** + * add in analysis and check the consistence + * + * @param value value to check + * @param message name of parameter + * @param analysis analysis builder + * @param check true if the value must not be null or empty + * @param displayValueInAnalysis true if the value can be added in the analysis + * @param wasOkBefore previous value, is returned if this check is Ok + * @return previous value is ok false else + */ + private boolean stillOk(Object value, + String message, + StringBuilder analysis, + boolean check, + boolean displayValueInAnalysis, + boolean wasOkBefore) { analysis.append(message); - logger.error("Check failed {} value:[{}]", message, getDisplayValue(value, displayValueInAnalysis)); - return false; - } + analysis.append("["); + analysis.append(getDisplayValue(value, displayValueInAnalysis)); + analysis.append("], "); + + if (check) { + if (value == null || (value instanceof String valueString && valueString.isEmpty())) { + analysis.append("No "); + analysis.append(message); + logger.error("Check failed {} value:[{}]", message, getDisplayValue(value, displayValueInAnalysis)); + return false; + } + } + return wasOkBefore; + } + + private String getDisplayValue(Object value, boolean displayValueInAnalysis) { + if (value == null) + return "null"; + if (displayValueInAnalysis) + return value.toString(); + if (value.toString().length() <= 3) + return "***"; + return value.toString().substring(0, 3) + "***"; } - return wasOkBefore; - } - - private String getDisplayValue(Object value, boolean displayValueInAnalysis) { - if (value == null) - return "null"; - if (displayValueInAnalysis) - return value.toString(); - if (value.toString().length() <= 3) - return "***"; - return value.toString().substring(0, 3) + "***"; - } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java index 5df37b6..29f15ea 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/StatisticsCollector.java @@ -8,42 +8,42 @@ @Component public class StatisticsCollector { - private final Date startTime = new Date(); + private final Date startTime = new Date(); - private final long lastPrintStartedProcessInstances = 0; - private final long lastPrintCompletedProcessInstances = 0; - private final long lastPrintCompletedJobs = 0; - private final long lastPrintStartedProcessInstancesBackpressure = 0; + private final long lastPrintStartedProcessInstances = 0; + private final long lastPrintCompletedProcessInstances = 0; + private final long lastPrintCompletedJobs = 0; + private final long lastPrintStartedProcessInstancesBackpressure = 0; - private long piPerSecondGoal; + private long piPerSecondGoal; - @PostConstruct - public void init() { - } + @PostConstruct + public void init() { + } - public void hintOnNewPiPerSecondGoald(long piPerSecondGoal) { - this.piPerSecondGoal = piPerSecondGoal; - } + public void hintOnNewPiPerSecondGoald(long piPerSecondGoal) { + this.piPerSecondGoal = piPerSecondGoal; + } - public void incStartedProcessInstances() { - } + public void incStartedProcessInstances() { + } - public void incStartedProcessInstancesBackpressure() { - } + public void incStartedProcessInstancesBackpressure() { + } - public void incCompletedProcessInstances() { - } + public void incCompletedProcessInstances() { + } - public void incCompletedProcessInstances(long startMillis, long endMillis) { - } + public void incCompletedProcessInstances(long startMillis, long endMillis) { + } - public void incCompletedJobs() { - } + public void incCompletedJobs() { + } - public void incStartedProcessInstancesException(String exceptionMessage) { - } + public void incStartedProcessInstancesException(String exceptionMessage) { + } - public void incCompletedJobsException(String exceptionMessage) { - } + public void incCompletedJobsException(String exceptionMessage) { + } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java index b120f06..ae5c95f 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java @@ -15,75 +15,75 @@ */ public class RefactoredCommandWrapper extends CommandWrapper { - private final FinalCommandStep<Void> command; - private final long deadline; - private final String entityLogInfo; - private final DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy; - private final int maxRetries = 20; - private long currentRetryDelay = 50L; - private int invocationCounter = 0; + private final FinalCommandStep<Void> command; + private final long deadline; + private final String entityLogInfo; + private final DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy; + private final int maxRetries = 20; + private long currentRetryDelay = 50L; + private int invocationCounter = 0; - public RefactoredCommandWrapper(FinalCommandStep<Void> command, - long deadline, - String entityLogInfo, - DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy) { - super(command, null, commandExceptionHandlingStrategy); - this.command = command; - this.deadline = deadline; - this.entityLogInfo = entityLogInfo; - this.commandExceptionHandlingStrategy = commandExceptionHandlingStrategy; - } + public RefactoredCommandWrapper(FinalCommandStep<Void> command, + long deadline, + String entityLogInfo, + DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy) { + super(command, null, commandExceptionHandlingStrategy); + this.command = command; + this.deadline = deadline; + this.entityLogInfo = entityLogInfo; + this.commandExceptionHandlingStrategy = commandExceptionHandlingStrategy; + } - @Override - public void executeAsync() { - ++this.invocationCounter; - ZeebeFuture<Void> zeebeFuture = this.command.send(); - if (commandExceptionHandlingStrategy != null) - zeebeFuture.exceptionally(t -> { - this.commandExceptionHandlingStrategy.handleCommandError(this, t); - return null; - }); - } + @Override + public void executeAsync() { + ++this.invocationCounter; + ZeebeFuture<Void> zeebeFuture = this.command.send(); + if (commandExceptionHandlingStrategy != null) + zeebeFuture.exceptionally(t -> { + this.commandExceptionHandlingStrategy.handleCommandError(this, t); + return null; + }); + } - public Object executeSync() { - ++this.invocationCounter; - ZeebeFuture<Void> zeebeFutur = this.command.send(); - if (commandExceptionHandlingStrategy != null) - zeebeFutur.exceptionally(t -> { - this.commandExceptionHandlingStrategy.handleCommandError(this, t); - return null; - }); - return zeebeFutur.join(); - } + public Object executeSync() { + ++this.invocationCounter; + ZeebeFuture<Void> zeebeFutur = this.command.send(); + if (commandExceptionHandlingStrategy != null) + zeebeFutur.exceptionally(t -> { + this.commandExceptionHandlingStrategy.handleCommandError(this, t); + return null; + }); + return zeebeFutur.join(); + } - @Override - public void increaseBackoffUsing(BackoffSupplier backoffSupplier) { - this.currentRetryDelay = backoffSupplier.supplyRetryDelay(this.currentRetryDelay); - } + @Override + public void increaseBackoffUsing(BackoffSupplier backoffSupplier) { + this.currentRetryDelay = backoffSupplier.supplyRetryDelay(this.currentRetryDelay); + } - @Override - public void scheduleExecutionUsing(ScheduledExecutorService scheduledExecutorService) { - scheduledExecutorService.schedule(this::executeAsync, this.currentRetryDelay, TimeUnit.MILLISECONDS); - } + @Override + public void scheduleExecutionUsing(ScheduledExecutorService scheduledExecutorService) { + scheduledExecutorService.schedule(this::executeAsync, this.currentRetryDelay, TimeUnit.MILLISECONDS); + } - @Override - public String toString() { - return "{command=" + this.command.getClass() + ", entity=" + this.entityLogInfo + ", currentRetryDelay=" - + this.currentRetryDelay + '}'; - } + @Override + public String toString() { + return "{command=" + this.command.getClass() + ", entity=" + this.entityLogInfo + ", currentRetryDelay=" + + this.currentRetryDelay + '}'; + } - @Override - public boolean hasMoreRetries() { - if (this.jobDeadlineExceeded()) { - return false; - } else { - return this.invocationCounter < this.maxRetries; + @Override + public boolean hasMoreRetries() { + if (this.jobDeadlineExceeded()) { + return false; + } else { + return this.invocationCounter < this.maxRetries; + } } - } - @Override - public boolean jobDeadlineExceeded() { - return Instant.now().getEpochSecond() > this.deadline; - } + @Override + public boolean jobDeadlineExceeded() { + return Instant.now().getEpochSecond() > this.deadline; + } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java index 3fbbbb0..8be203f 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java +++ b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java @@ -17,149 +17,149 @@ public class BpmnEngineDummy implements BpmnEngine { - private final Logger logger = LoggerFactory.getLogger(BpmnEngineDummy.class); - - public BpmnEngineDummy(BpmnEngineList.BpmnServerDefinition serverDefinition) { - } - - @Override - public void init() { - logger.info("BpmnEngineDummy.Init:"); - } - - public void connection() throws AutomatorException { - // nothing to do here - } - - public void disconnection() throws AutomatorException { - // nothing to do here - } - - /** - * Engine is ready. If not, a connection() method must be call - * - * @return ready if the engine is ready - true everytime - */ - public boolean isReady() { - return true; - } - - @Override - public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) - throws AutomatorException { - logger.info("BpmnEngineDummy.CreateProcessInstance: Process[" + processId + "] StartEvent[" + starterEventId + "]"); - return "111"; - } - - @Override - public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { - - } - - @Override - public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) - throws AutomatorException { - logger.info("BpmnEngineDummy.searchForActivity: Process[" + processInstanceId + "] taskName[" + userTaskId + "]"); - return List.of("5555"); - } - - @Override - public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { - logger.info("BpmnEngineDummy.searchForActivity: taskName[" + userTaskId + "]"); - return List.of("5555"); - } - - @Override - public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) - throws AutomatorException { - logger.info("BpmnEngineDummy.executeUserTask: activityId[" + userTaskId + "]"); - } - - @Override - public RegisteredTask registerServiceTask(String workerId, - String topic, - boolean streamEnable, - Duration lockTime, - Object jobHandler, - FixedBackoffSupplier backoffSupplier) { - return null; - } - - @Override - public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) - throws AutomatorException { - return Collections.emptyList(); - } - - @Override - public void executeServiceTask(String serviceTaskId, String workerId, Map<String, Object> variables) - throws AutomatorException { - } - - @Override - public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) - throws AutomatorException { - return Collections.emptyList(); - } - - @Override - public List<ProcessDescription> searchProcessInstanceByVariable(String processId, - Map<String, Object> filterVariables, - int maxResult) throws AutomatorException { - return Collections.emptyList(); - } - - @Override - public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { - return Collections.emptyMap(); - } - - - - /* ******************************************************************** */ - /* */ - /* CountInformation */ - /* */ - /* ******************************************************************** */ - - @Override - public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) - throws AutomatorException { - throw new AutomatorException("Not yet implemented"); - } - - @Override - public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) - throws AutomatorException { - throw new AutomatorException("Not yet implemented"); - } - - public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { - throw new AutomatorException("Not yet implemented"); - } - - @Override - public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { - return null; - } - - @Override - public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { - return BpmnEngineList.CamundaEngine.DUMMY; - } - - @Override - public String getSignature() { - return BpmnEngineList.CamundaEngine.DUMMY.toString(); - } - - @Override - - public int getWorkerExecutionThreads() { - return 0; - } - - public void turnHighFlowMode(boolean hightFlowMode) { - } + private final Logger logger = LoggerFactory.getLogger(BpmnEngineDummy.class); + + public BpmnEngineDummy(BpmnEngineList.BpmnServerDefinition serverDefinition) { + } + + @Override + public void init() { + logger.info("BpmnEngineDummy.Init:"); + } + + public void connection() throws AutomatorException { + // nothing to do here + } + + public void disconnection() throws AutomatorException { + // nothing to do here + } + + /** + * Engine is ready. If not, a connection() method must be call + * + * @return ready if the engine is ready - true everytime + */ + public boolean isReady() { + return true; + } + + @Override + public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) + throws AutomatorException { + logger.info("BpmnEngineDummy.CreateProcessInstance: Process[" + processId + "] StartEvent[" + starterEventId + "]"); + return "111"; + } + + @Override + public void endProcessInstance(String processInstanceId, boolean cleanAll) throws AutomatorException { + + } + + @Override + public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) + throws AutomatorException { + logger.info("BpmnEngineDummy.searchForActivity: Process[" + processInstanceId + "] taskName[" + userTaskId + "]"); + return List.of("5555"); + } + + @Override + public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { + logger.info("BpmnEngineDummy.searchForActivity: taskName[" + userTaskId + "]"); + return List.of("5555"); + } + + @Override + public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) + throws AutomatorException { + logger.info("BpmnEngineDummy.executeUserTask: activityId[" + userTaskId + "]"); + } + + @Override + public RegisteredTask registerServiceTask(String workerId, + String topic, + boolean streamEnable, + Duration lockTime, + Object jobHandler, + FixedBackoffSupplier backoffSupplier) { + return null; + } + + @Override + public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + throws AutomatorException { + return Collections.emptyList(); + } + + @Override + public void executeServiceTask(String serviceTaskId, String workerId, Map<String, Object> variables) + throws AutomatorException { + } + + @Override + public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String taskId, int maxResult) + throws AutomatorException { + return Collections.emptyList(); + } + + @Override + public List<ProcessDescription> searchProcessInstanceByVariable(String processId, + Map<String, Object> filterVariables, + int maxResult) throws AutomatorException { + return Collections.emptyList(); + } + + @Override + public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { + return Collections.emptyMap(); + } + + + + /* ******************************************************************** */ + /* */ + /* CountInformation */ + /* */ + /* ******************************************************************** */ + + @Override + public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } + + @Override + public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) + throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } + + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + throw new AutomatorException("Not yet implemented"); + } + + @Override + public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) throws AutomatorException { + return null; + } + + @Override + public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { + return BpmnEngineList.CamundaEngine.DUMMY; + } + + @Override + public String getSignature() { + return BpmnEngineList.CamundaEngine.DUMMY.toString(); + } + + @Override + + public int getWorkerExecutionThreads() { + return 0; + } + + public void turnHighFlowMode(boolean hightFlowMode) { + } } diff --git a/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java b/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java index 2b8c49f..5222766 100644 --- a/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java +++ b/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java @@ -13,586 +13,582 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.StringTokenizer; +import java.util.*; @Component public class BpmnEngineList { - public static final int DEFAULT_VALUE_EXECUTION_THREADS = 100; - public static final int DEFAULT_VALUE_MAX_JOBS_ACTIVE = -1; - public static final String CONF_WORKER_MAX_JOBS_ACTIVE = "workerMaxJobsActive"; - public static final String CONF_WORKER_EXECUTION_THREADS = "workerExecutionThreads"; - public static final String CONF_TASK_LIST_URL = "taskListUrl"; - public static final String CONF_TASK_LIST_USER_NAME = "taskListUserName"; - public static final String CONF_TASK_LIST_PASSWORD = "taskListUserPassword"; - public static final String CONF_TASK_LIST_CLIENT_ID = "taskListClientId"; - public static final String CONF_TASK_LIST_CLIENT_SECRET = "taskListClientSecret"; - // Example taskListKeycloakUrl: "http://localhost:18080/auth/realms/camunda-platform" - public static final String CONF_TASK_LIST_KEYCLOAK_URL = "taskListKeycloakUrl"; - - public static final String CONF_IDENTITY_URL = "identityUrl"; - public static final String CONF_OPERATE_URL = "operateUrl"; - public static final String CONF_OPERATE_USER_PASSWORD = "operateUserPassword"; - public static final String CONF_OPERATE_USER_NAME = "operateUserName"; - public static final String CONF_AUTHENTICATIONURL = "authenticationUrl"; - public static final String CONF_OPERATE_CLIENT_ID = "operateClientId"; - public static final String CONF_OPERATE_CLIENT_SECRET = "operateClientSecret"; - public static final String CONF_OPERATE_AUDIENCE = "operateAudientce"; - - public static final String CONF_ZEEBE_GATEWAY_ADDRESS = "zeebeGatewayAddress"; - public static final String CONF_URL = "url"; - public static final String CONF_TYPE = "type"; - public static final String CONF_TYPE_V_CAMUNDA_8 = "camunda8"; - public static final String CONF_TYPE_V_CAMUNDA_8_SAAS = "camunda8Saas"; - public static final String CONF_TYPE_V_CAMUNDA_7 = "camunda7"; - - public static final String CONF_ZEEBE_SAAS_REGION = "region"; - public static final String CONF_ZEEBE_SECRET = "zeebeClientSecret"; - public static final String CONF_ZEEBE_SAAS_CLUSTER_ID = "clusterId"; - public static final String CONF_ZEEBE_CLIENT_ID = "zeebeClientId"; - public static final String CONF_ZEEBE_AUDIENCE = "zeebeAudience"; - public static final String CONF_ZEEBE_PLAINTEXT = "zeebePlainText"; - public static final String ZEEBE_DEFAULT_AUDIENCE = "zeebe.camunda.io"; - - static Logger logger = LoggerFactory.getLogger(BpmnEngineList.class); - - @Autowired - ConfigurationServersEngine configurationServersEngine; - - private List<BpmnServerDefinition> allServers = new ArrayList<>(); - - @PostConstruct - public void init() { - allServers = new ArrayList<>(); - - try { - // get From Server Connection - allServers.addAll(getFromServerConfiguration()); - - // decode the serverConnections - allServers.addAll(getFromServersConnectionList()); - - // decode serversList - allServers.addAll(getFromServersList()); - - // log all servers detected - logger.info("ConfigurationBpmEngine: servers detected : {} ", allServers.size()); - for (BpmnServerDefinition server : allServers) { - String serverDetails = "Configuration Server Type[" + server.serverType + "] "; - if (server.serverType == null) { - logger.error("ServerType not declared for server [{}]", server.name); - return; + public static final int DEFAULT_VALUE_EXECUTION_THREADS = 100; + public static final int DEFAULT_VALUE_MAX_JOBS_ACTIVE = -1; + public static final String CONF_WORKER_MAX_JOBS_ACTIVE = "workerMaxJobsActive"; + public static final String CONF_WORKER_EXECUTION_THREADS = "workerExecutionThreads"; + public static final String CONF_TASK_LIST_URL = "taskListUrl"; + public static final String CONF_TASK_LIST_USER_NAME = "taskListUserName"; + public static final String CONF_TASK_LIST_PASSWORD = "taskListUserPassword"; + public static final String CONF_TASK_LIST_CLIENT_ID = "taskListClientId"; + public static final String CONF_TASK_LIST_CLIENT_SECRET = "taskListClientSecret"; + // Example taskListKeycloakUrl: "http://localhost:18080/auth/realms/camunda-platform" + public static final String CONF_TASK_LIST_KEYCLOAK_URL = "taskListKeycloakUrl"; + + public static final String CONF_IDENTITY_URL = "identityUrl"; + public static final String CONF_OPERATE_URL = "operateUrl"; + public static final String CONF_OPERATE_USER_PASSWORD = "operateUserPassword"; + public static final String CONF_OPERATE_USER_NAME = "operateUserName"; + public static final String CONF_AUTHENTICATIONURL = "authenticationUrl"; + public static final String CONF_OPERATE_CLIENT_ID = "operateClientId"; + public static final String CONF_OPERATE_CLIENT_SECRET = "operateClientSecret"; + public static final String CONF_OPERATE_AUDIENCE = "operateAudientce"; + + public static final String CONF_ZEEBE_GATEWAY_ADDRESS = "zeebeGatewayAddress"; + public static final String CONF_URL = "url"; + public static final String CONF_TYPE = "type"; + public static final String CONF_TYPE_V_CAMUNDA_8 = "camunda8"; + public static final String CONF_TYPE_V_CAMUNDA_8_SAAS = "camunda8Saas"; + public static final String CONF_TYPE_V_CAMUNDA_7 = "camunda7"; + + public static final String CONF_ZEEBE_SAAS_REGION = "region"; + public static final String CONF_ZEEBE_SECRET = "zeebeClientSecret"; + public static final String CONF_ZEEBE_SAAS_CLUSTER_ID = "clusterId"; + public static final String CONF_ZEEBE_CLIENT_ID = "zeebeClientId"; + public static final String CONF_ZEEBE_AUDIENCE = "zeebeAudience"; + public static final String CONF_ZEEBE_PLAINTEXT = "zeebePlainText"; + public static final String ZEEBE_DEFAULT_AUDIENCE = "zeebe.camunda.io"; + + static Logger logger = LoggerFactory.getLogger(BpmnEngineList.class); + + @Autowired + ConfigurationServersEngine configurationServersEngine; + + private List<BpmnServerDefinition> allServers = new ArrayList<>(); + + @PostConstruct + public void init() { + allServers = new ArrayList<>(); + + try { + // get From Server Connection + allServers.addAll(getFromServerConfiguration()); + + // decode the serverConnections + allServers.addAll(getFromServersConnectionList()); + + // decode serversList + allServers.addAll(getFromServersList()); + + // log all servers detected + logger.info("ConfigurationBpmEngine: servers detected : {} ", allServers.size()); + for (BpmnServerDefinition server : allServers) { + String serverDetails = "Configuration Server Type[" + server.serverType + "] "; + if (server.serverType == null) { + logger.error("ServerType not declared for server [{}]", server.name); + return; + } + + serverDetails += switch (server.serverType) { + case CAMUNDA_8 -> "ZeebeadressGateway [" + server.zeebeGatewayAddress + "]"; + case CAMUNDA_8_SAAS -> + "ZeebeClientId [" + server.zeebeClientId + "] ClusterId[" + server.zeebeSaasClusterId + "] RegionId[" + + server.zeebeSaasRegion + "]"; + case CAMUNDA_7 -> "Camunda7URL [" + server.camunda7ServerUrl + "]"; + case DUMMY -> "Dummy"; + }; + logger.info(serverDetails); + } + } catch (Exception e) { + logger.error("Error during initialization : {}", e.getMessage()); } + } - serverDetails += switch (server.serverType) { - case CAMUNDA_8 -> "ZeebeadressGateway [" + server.zeebeGatewayAddress + "]"; - case CAMUNDA_8_SAAS -> - "ZeebeClientId [" + server.zeebeClientId + "] ClusterId[" + server.zeebeSaasClusterId + "] RegionId[" - + server.zeebeSaasRegion + "]"; - case CAMUNDA_7 -> "Camunda7URL [" + server.camunda7ServerUrl + "]"; - case DUMMY -> "Dummy"; - }; - logger.info(serverDetails); - } - } catch (Exception e) { - logger.error("Error during initialization : {}", e.getMessage()); + /** + * Add an explicit server, by the API + * + * @param bpmnEngineConfiguration server to add in the list + */ + public void addExplicitServer(BpmnServerDefinition bpmnEngineConfiguration) { + allServers.add(bpmnEngineConfiguration); } - } - - /** - * Add an explicit server, by the API - * - * @param bpmnEngineConfiguration server to add in the list - */ - public void addExplicitServer(BpmnServerDefinition bpmnEngineConfiguration) { - allServers.add(bpmnEngineConfiguration); - } - - public List<BpmnServerDefinition> getListServers() { - return allServers; - } - - /** - * get a server by its name - * - * @param serverName serverName - * @return the server, or null - * @throws AutomatorException on any error - */ - public BpmnEngineList.BpmnServerDefinition getByServerName(String serverName) throws AutomatorException { - Optional<BpmnServerDefinition> first = allServers.stream().filter(t -> t.name.equals(serverName)).findFirst(); - return first.isPresent() ? first.get() : null; - } - - /** - * get a server by its type - * - * @param serverType type of server CAMUNDA 8 ? 7 ? - * @return a server - * @throws AutomatorException on any error - */ - public BpmnEngineList.BpmnServerDefinition getByServerType(CamundaEngine serverType) { - Optional<BpmnServerDefinition> first = allServers.stream() - .filter(t -> sameType(t.serverType, serverType)) - .findFirst(); - return first.isPresent() ? first.get() : null; - } - - public boolean getLogDebug() { - return configurationServersEngine.logDebug; - } - - /* ******************************************************************** */ - /* */ - /* Different information in the YAML */ - /* */ - /* ******************************************************************** */ - - /** - * Explode the configuration serverConnections - * - * @return list of server definition - * @throws AutomatorException any errors - */ - private List<BpmnServerDefinition> getFromServersConnectionList() throws AutomatorException { - // not possible to use a Stream: decode throw an exception - List<BpmnServerDefinition> list = new ArrayList<>(); - int count = 0; - for (String s : configurationServersEngine.serversConnection) { - count++; - if (s.isEmpty()) - continue; - BpmnServerDefinition bpmnServerDefinition = decodeServerConnection(s, "Range in ConnectionString: #" + count); - if (bpmnServerDefinition.serverType == null) { - logger.error("Server Type can't be detected in string [{}]", s); - continue; - } - - list.add(bpmnServerDefinition); + + public List<BpmnServerDefinition> getListServers() { + return allServers; } - return list; - } - - /** - * getFromServerList - * in configuration, give a list of server. - * - * @return the list of available server - * @throws AutomatorException in case of error - */ - private List<BpmnServerDefinition> getFromServersList() throws AutomatorException { - List<BpmnServerDefinition> serverList = new ArrayList<>(); - - int count = 0; - for (Map<String, Object> serverMap : configurationServersEngine.getServersList()) { - count++; - BpmnServerDefinition bpmnServerDefinition = new BpmnServerDefinition(); - bpmnServerDefinition.name = getString("name", serverMap, null, "ServerList #" + count, true); - String contextLog = "ServerList #" + count + " Name [" + bpmnServerDefinition.name + "]"; - bpmnServerDefinition.workerMaxJobsActive = getInteger(CONF_WORKER_MAX_JOBS_ACTIVE, serverMap, - DEFAULT_VALUE_MAX_JOBS_ACTIVE, contextLog); - - if (CONF_TYPE_V_CAMUNDA_7.equalsIgnoreCase(getString(CONF_TYPE, serverMap, null, contextLog, true))) { - bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_7; - bpmnServerDefinition.camunda7ServerUrl = getString(CONF_URL, serverMap, null, contextLog, true); - if (bpmnServerDefinition.camunda7ServerUrl == null) - throw new AutomatorException( - "Incorrect Definition - [url] expected for [" + CONF_TYPE_V_CAMUNDA_7 + "] type " + contextLog); - } - - if (CONF_TYPE_V_CAMUNDA_8.equalsIgnoreCase(getString(CONF_TYPE, serverMap, null, contextLog, true))) { - bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_8; - bpmnServerDefinition.zeebeGatewayAddress = getString(CONF_ZEEBE_GATEWAY_ADDRESS, serverMap, null, contextLog, - true); - bpmnServerDefinition.zeebeClientId = getString(CONF_ZEEBE_CLIENT_ID, serverMap, null, contextLog, false); - bpmnServerDefinition.zeebeClientSecret = getString(CONF_ZEEBE_SECRET, serverMap, null, contextLog, false); - bpmnServerDefinition.zeebeAudience = getString(CONF_ZEEBE_AUDIENCE, serverMap, ZEEBE_DEFAULT_AUDIENCE, - contextLog, false); - bpmnServerDefinition.zeebePlainText = getBoolean(CONF_ZEEBE_PLAINTEXT, serverMap, true, contextLog, false); - bpmnServerDefinition.authenticationUrl = getString(CONF_AUTHENTICATIONURL, serverMap, null, contextLog, false); - - bpmnServerDefinition.identityUrl = getString(CONF_IDENTITY_URL, serverMap, null, contextLog, false); - bpmnServerDefinition.operateUrl = getString(CONF_OPERATE_URL, serverMap, null, contextLog, false); - bpmnServerDefinition.operateUserName = getString(CONF_OPERATE_USER_NAME, serverMap, "Demo", contextLog, false); - bpmnServerDefinition.operateUserPassword = getString(CONF_OPERATE_USER_PASSWORD, serverMap, "Demo", contextLog, - false); - bpmnServerDefinition.operateClientId = getString(CONF_OPERATE_CLIENT_ID, serverMap, null, contextLog, false); - bpmnServerDefinition.operateClientSecret = getString(CONF_OPERATE_CLIENT_SECRET, serverMap, null, contextLog, - false); - bpmnServerDefinition.operateAudience = getString(CONF_OPERATE_AUDIENCE, serverMap, null, contextLog, false); - - bpmnServerDefinition.taskListUrl = getString(CONF_TASK_LIST_URL, serverMap, null, contextLog, false); - bpmnServerDefinition.taskListUserName = getString(CONF_TASK_LIST_USER_NAME, serverMap, null, contextLog, false); - bpmnServerDefinition.taskListUserPassword = getString(CONF_TASK_LIST_PASSWORD, serverMap, null, contextLog, - false); - bpmnServerDefinition.taskListClientId = getString(CONF_TASK_LIST_CLIENT_ID, serverMap, null, contextLog, false); - bpmnServerDefinition.taskListClientSecret = getString(CONF_TASK_LIST_CLIENT_SECRET, serverMap, null, contextLog, - false); - bpmnServerDefinition.taskListKeycloakUrl = getString(CONF_TASK_LIST_KEYCLOAK_URL, serverMap, null, contextLog, - false); - - bpmnServerDefinition.workerExecutionThreads = getInteger(CONF_WORKER_EXECUTION_THREADS, serverMap, - DEFAULT_VALUE_EXECUTION_THREADS, contextLog); - if (bpmnServerDefinition.zeebeGatewayAddress == null) - throw new AutomatorException( - "Incorrect Definition - [zeebeGatewayAddress] expected for [" + CONF_TYPE_V_CAMUNDA_8 + "] type"); - } - - if (CONF_TYPE_V_CAMUNDA_8_SAAS.equalsIgnoreCase(getString(CONF_TYPE, serverMap, null, contextLog, true))) { - bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_8_SAAS; - bpmnServerDefinition.zeebeSaasRegion = getString(CONF_ZEEBE_SAAS_REGION, serverMap, null, contextLog, true); - bpmnServerDefinition.zeebeSaasClusterId = getString(CONF_ZEEBE_SAAS_CLUSTER_ID, serverMap, null, contextLog, - true); - bpmnServerDefinition.zeebeClientId = getString(CONF_ZEEBE_CLIENT_ID, serverMap, null, contextLog, true); - bpmnServerDefinition.zeebeClientSecret = getString(CONF_ZEEBE_SECRET, serverMap, null, contextLog, true); - bpmnServerDefinition.zeebeAudience = getString(CONF_ZEEBE_AUDIENCE, serverMap, ZEEBE_DEFAULT_AUDIENCE, - contextLog, true); - bpmnServerDefinition.authenticationUrl = getString(CONF_AUTHENTICATIONURL, serverMap, - "https://login.cloud.camunda.io/oauth/token", contextLog, false); - - bpmnServerDefinition.workerExecutionThreads = getInteger(CONF_WORKER_EXECUTION_THREADS, serverMap, - DEFAULT_VALUE_EXECUTION_THREADS, contextLog); - bpmnServerDefinition.operateUserName = getString(CONF_OPERATE_USER_NAME, serverMap, null, contextLog, false); - bpmnServerDefinition.operateUserPassword = getString(CONF_OPERATE_USER_PASSWORD, serverMap, null, contextLog, - false); - bpmnServerDefinition.operateUrl = getString(CONF_OPERATE_URL, serverMap, null, contextLog, false); - bpmnServerDefinition.taskListUrl = getString(CONF_TASK_LIST_URL, serverMap, null, contextLog, false); - bpmnServerDefinition.taskListClientId = getString(CONF_TASK_LIST_CLIENT_ID, serverMap, null, contextLog, false); - bpmnServerDefinition.taskListClientSecret = getString(CONF_TASK_LIST_CLIENT_SECRET, serverMap, null, contextLog, - false); - - if (bpmnServerDefinition.zeebeSaasRegion == null || bpmnServerDefinition.zeebeClientSecret == null - || bpmnServerDefinition.zeebeSaasClusterId == null || bpmnServerDefinition.zeebeClientId == null) - throw new AutomatorException( - "Incorrect Definition - [zeebeCloudRegister],[zeebeCloudRegion], [zeebeClientSecret},[zeebeCloudClusterId],[zeebeCloudClientId] expected for [Camunda8SaaS] type"); - } - serverList.add(bpmnServerDefinition); + + /** + * get a server by its name + * + * @param serverName serverName + * @return the server, or null + * @throws AutomatorException on any error + */ + public BpmnEngineList.BpmnServerDefinition getByServerName(String serverName) throws AutomatorException { + Optional<BpmnServerDefinition> first = allServers.stream().filter(t -> t.name.equals(serverName)).findFirst(); + return first.isPresent() ? first.get() : null; } - return serverList; - } - - /** - * DecodeServerConnection - * - * @param connectionString connection string - * @return a ServerDefinition - * @throws AutomatorException on any error - */ - private BpmnServerDefinition decodeServerConnection(String connectionString, String contextLog) - throws AutomatorException { - StringTokenizer st = new StringTokenizer(connectionString, ","); - BpmnServerDefinition bpmnServerDefinition = new BpmnServerDefinition(); - bpmnServerDefinition.name = (st.hasMoreTokens() ? st.nextToken() : null); - try { - bpmnServerDefinition.serverType = st.hasMoreTokens() ? CamundaEngine.valueOf(st.nextToken()) : null; - if (CamundaEngine.CAMUNDA_7.equals(bpmnServerDefinition.serverType)) { - bpmnServerDefinition.camunda7ServerUrl = (st.hasMoreTokens() ? st.nextToken() : null); - - } else if (CamundaEngine.CAMUNDA_8.equals(bpmnServerDefinition.serverType)) { - bpmnServerDefinition.zeebeGatewayAddress = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUrl = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUserName = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateUserPassword = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.taskListUrl = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.workerExecutionThreads = (st.hasMoreTokens() ? - parseInt(CONF_WORKER_EXECUTION_THREADS, st.nextToken(), DEFAULT_VALUE_EXECUTION_THREADS, contextLog) : - null); - bpmnServerDefinition.workerMaxJobsActive = (st.hasMoreTokens() ? - parseInt(CONF_WORKER_MAX_JOBS_ACTIVE, st.nextToken(), DEFAULT_VALUE_MAX_JOBS_ACTIVE, contextLog) : - null); - - } else if (CamundaEngine.CAMUNDA_8_SAAS.equals(bpmnServerDefinition.serverType)) { - bpmnServerDefinition.zeebeSaasRegion = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeSaasClusterId = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeClientId = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeClientSecret = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.zeebeAudience = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateClientId = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.operateClientSecret = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.taskListClientId = (st.hasMoreTokens() ? st.nextToken() : null); - bpmnServerDefinition.taskListClientSecret = (st.hasMoreTokens() ? st.nextToken() : null); - - bpmnServerDefinition.workerExecutionThreads = (st.hasMoreTokens() ? - parseInt(CONF_WORKER_EXECUTION_THREADS, st.nextToken(), DEFAULT_VALUE_EXECUTION_THREADS, contextLog) : - null); - bpmnServerDefinition.workerMaxJobsActive = (st.hasMoreTokens() ? - parseInt(CONF_WORKER_MAX_JOBS_ACTIVE, st.nextToken(), DEFAULT_VALUE_MAX_JOBS_ACTIVE, contextLog) : - null); - } - return bpmnServerDefinition; - } catch (Exception e) { - throw new AutomatorException("Can't decode string [" + connectionString + "] " + e.getMessage()); + + /** + * get a server by its type + * + * @param serverType type of server CAMUNDA 8 ? 7 ? + * @return a server + * @throws AutomatorException on any error + */ + public BpmnEngineList.BpmnServerDefinition getByServerType(CamundaEngine serverType) { + Optional<BpmnServerDefinition> first = allServers.stream() + .filter(t -> sameType(t.serverType, serverType)) + .findFirst(); + return first.isPresent() ? first.get() : null; } - } - - /** - * Get the list from the serverConfiguration. If the variable exist, then use the value. Easy to configure for K8 - * - * @return list of BpmnServer - */ - private List<BpmnServerDefinition> getFromServerConfiguration() { - List<BpmnServerDefinition> list = new ArrayList<>(); - - // get the direct list - if (hasValue(configurationServersEngine.camunda7Url)) { - BpmnServerDefinition camunda7 = new BpmnServerDefinition(); - camunda7.serverType = CamundaEngine.CAMUNDA_7; - camunda7.name = configurationServersEngine.camunda7Name; - camunda7.camunda7ServerUrl = configurationServersEngine.camunda7Url; - camunda7.camunda7UserName = configurationServersEngine.camunda7UserName; - camunda7.camunda7Password = configurationServersEngine.camunda7Password; - - camunda7.workerMaxJobsActive = parseInt("Camunda7." + CONF_WORKER_MAX_JOBS_ACTIVE, - configurationServersEngine.C7WorkerMaxJobsActive, DEFAULT_VALUE_MAX_JOBS_ACTIVE, ""); - camunda7.workerExecutionThreads = parseInt("Camunda7." + CONF_WORKER_EXECUTION_THREADS, - configurationServersEngine.C7WorkerMaxJobsActive, DEFAULT_VALUE_EXECUTION_THREADS, ""); - - camunda7.workerMaxJobsActive = parseInt("Camunda7." + CONF_WORKER_MAX_JOBS_ACTIVE, - configurationServersEngine.C7WorkerMaxJobsActive, DEFAULT_VALUE_MAX_JOBS_ACTIVE, ""); - list.add(camunda7); - logger.info("Configuration: Camunda7 Name[{}] url[{}] MaxJobsActive[{}]", camunda7.name, - camunda7.camunda7ServerUrl, camunda7.workerMaxJobsActive); + + public boolean getLogDebug() { + return configurationServersEngine.logDebug; } - if (hasValue(configurationServersEngine.zeebeGatewayAddress)) { - BpmnServerDefinition camunda8 = new BpmnServerDefinition(); - camunda8.serverType = CamundaEngine.CAMUNDA_8; - camunda8.name = configurationServersEngine.zeebeName; - camunda8.zeebeGatewayAddress = configurationServersEngine.zeebeGatewayAddress; - camunda8.workerExecutionThreads = parseInt("Camunda8." + CONF_WORKER_EXECUTION_THREADS, - configurationServersEngine.zeebeWorkerExecutionThreads, DEFAULT_VALUE_EXECUTION_THREADS, ""); - camunda8.workerMaxJobsActive = parseInt("Camunda8." + CONF_WORKER_MAX_JOBS_ACTIVE, - configurationServersEngine.zeebeWorkerMaxJobsActive, DEFAULT_VALUE_MAX_JOBS_ACTIVE, ""); - camunda8.operateUrl = configurationServersEngine.zeebeOperateUrl; - camunda8.operateUserName = configurationServersEngine.zeebeOperateUserName; - camunda8.operateUserPassword = configurationServersEngine.zeebeOperateUserPassword; - camunda8.taskListUrl = configurationServersEngine.zeebeTaskListUrl; - camunda8.taskListUserName = configurationServersEngine.zeebeTaskListUserName; - camunda8.taskListUserPassword = configurationServersEngine.zeebeTaskListUserPassword; - list.add(camunda8); - logger.info( - "Configuration: Camunda8 Name[{}] zeebeGateway[{}] MaxJobsActive[{}] WorkerThreads[{}] " + "OperateURL[{}]", - camunda8.name, camunda8.camunda7ServerUrl, camunda8.workerMaxJobsActive, camunda8.workerExecutionThreads, - camunda8.operateUrl); + /* ******************************************************************** */ + /* */ + /* Different information in the YAML */ + /* */ + /* ******************************************************************** */ + + /** + * Explode the configuration serverConnections + * + * @return list of server definition + * @throws AutomatorException any errors + */ + private List<BpmnServerDefinition> getFromServersConnectionList() throws AutomatorException { + // not possible to use a Stream: decode throw an exception + List<BpmnServerDefinition> list = new ArrayList<>(); + int count = 0; + for (String s : configurationServersEngine.serversConnection) { + count++; + if (s.isEmpty()) + continue; + BpmnServerDefinition bpmnServerDefinition = decodeServerConnection(s, "Range in ConnectionString: #" + count); + if (bpmnServerDefinition.serverType == null) { + logger.error("Server Type can't be detected in string [{}]", s); + continue; + } + + list.add(bpmnServerDefinition); + } + return list; } - if (hasValue(configurationServersEngine.zeebeSaasClusterId)) { - BpmnServerDefinition camunda8 = new BpmnServerDefinition(); - camunda8.serverType = CamundaEngine.CAMUNDA_8_SAAS; - camunda8.name = configurationServersEngine.zeebeName; - camunda8.zeebeSaasRegion = configurationServersEngine.zeebeSaasRegion; - camunda8.zeebeSaasClusterId = configurationServersEngine.zeebeSaasClusterId; - camunda8.zeebeClientId = configurationServersEngine.zeebeSaasClientId; - camunda8.zeebeClientSecret = configurationServersEngine.zeebeSaasClientSecret; - camunda8.authenticationUrl = configurationServersEngine.zeebeSaasOAuthUrl; - camunda8.zeebeAudience = configurationServersEngine.zeebeSaasAudience; - camunda8.operateUrl = configurationServersEngine.zeebeOperateUrl; - camunda8.operateUserName = configurationServersEngine.zeebeOperateUserName; - camunda8.operateUserPassword = configurationServersEngine.zeebeOperateUserPassword; - camunda8.taskListUrl = configurationServersEngine.zeebeTaskListUrl; - list.add(camunda8); + /** + * getFromServerList + * in configuration, give a list of server. + * + * @return the list of available server + * @throws AutomatorException in case of error + */ + private List<BpmnServerDefinition> getFromServersList() throws AutomatorException { + List<BpmnServerDefinition> serverList = new ArrayList<>(); + + int count = 0; + for (Map<String, Object> serverMap : configurationServersEngine.getServersList()) { + count++; + BpmnServerDefinition bpmnServerDefinition = new BpmnServerDefinition(); + bpmnServerDefinition.name = getString("name", serverMap, null, "ServerList #" + count, true); + String contextLog = "ServerList #" + count + " Name [" + bpmnServerDefinition.name + "]"; + bpmnServerDefinition.workerMaxJobsActive = getInteger(CONF_WORKER_MAX_JOBS_ACTIVE, serverMap, + DEFAULT_VALUE_MAX_JOBS_ACTIVE, contextLog); + + if (CONF_TYPE_V_CAMUNDA_7.equalsIgnoreCase(getString(CONF_TYPE, serverMap, null, contextLog, true))) { + bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_7; + bpmnServerDefinition.camunda7ServerUrl = getString(CONF_URL, serverMap, null, contextLog, true); + if (bpmnServerDefinition.camunda7ServerUrl == null) + throw new AutomatorException( + "Incorrect Definition - [url] expected for [" + CONF_TYPE_V_CAMUNDA_7 + "] type " + contextLog); + } + + if (CONF_TYPE_V_CAMUNDA_8.equalsIgnoreCase(getString(CONF_TYPE, serverMap, null, contextLog, true))) { + bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_8; + bpmnServerDefinition.zeebeGatewayAddress = getString(CONF_ZEEBE_GATEWAY_ADDRESS, serverMap, null, contextLog, + true); + bpmnServerDefinition.zeebeClientId = getString(CONF_ZEEBE_CLIENT_ID, serverMap, null, contextLog, false); + bpmnServerDefinition.zeebeClientSecret = getString(CONF_ZEEBE_SECRET, serverMap, null, contextLog, false); + bpmnServerDefinition.zeebeAudience = getString(CONF_ZEEBE_AUDIENCE, serverMap, ZEEBE_DEFAULT_AUDIENCE, + contextLog, false); + bpmnServerDefinition.zeebePlainText = getBoolean(CONF_ZEEBE_PLAINTEXT, serverMap, true, contextLog, false); + bpmnServerDefinition.authenticationUrl = getString(CONF_AUTHENTICATIONURL, serverMap, null, contextLog, false); + + bpmnServerDefinition.identityUrl = getString(CONF_IDENTITY_URL, serverMap, null, contextLog, false); + bpmnServerDefinition.operateUrl = getString(CONF_OPERATE_URL, serverMap, null, contextLog, false); + bpmnServerDefinition.operateUserName = getString(CONF_OPERATE_USER_NAME, serverMap, "Demo", contextLog, false); + bpmnServerDefinition.operateUserPassword = getString(CONF_OPERATE_USER_PASSWORD, serverMap, "Demo", contextLog, + false); + bpmnServerDefinition.operateClientId = getString(CONF_OPERATE_CLIENT_ID, serverMap, null, contextLog, false); + bpmnServerDefinition.operateClientSecret = getString(CONF_OPERATE_CLIENT_SECRET, serverMap, null, contextLog, + false); + bpmnServerDefinition.operateAudience = getString(CONF_OPERATE_AUDIENCE, serverMap, null, contextLog, false); + + bpmnServerDefinition.taskListUrl = getString(CONF_TASK_LIST_URL, serverMap, null, contextLog, false); + bpmnServerDefinition.taskListUserName = getString(CONF_TASK_LIST_USER_NAME, serverMap, null, contextLog, false); + bpmnServerDefinition.taskListUserPassword = getString(CONF_TASK_LIST_PASSWORD, serverMap, null, contextLog, + false); + bpmnServerDefinition.taskListClientId = getString(CONF_TASK_LIST_CLIENT_ID, serverMap, null, contextLog, false); + bpmnServerDefinition.taskListClientSecret = getString(CONF_TASK_LIST_CLIENT_SECRET, serverMap, null, contextLog, + false); + bpmnServerDefinition.taskListKeycloakUrl = getString(CONF_TASK_LIST_KEYCLOAK_URL, serverMap, null, contextLog, + false); + + bpmnServerDefinition.workerExecutionThreads = getInteger(CONF_WORKER_EXECUTION_THREADS, serverMap, + DEFAULT_VALUE_EXECUTION_THREADS, contextLog); + if (bpmnServerDefinition.zeebeGatewayAddress == null) + throw new AutomatorException( + "Incorrect Definition - [zeebeGatewayAddress] expected for [" + CONF_TYPE_V_CAMUNDA_8 + "] type"); + } + + if (CONF_TYPE_V_CAMUNDA_8_SAAS.equalsIgnoreCase(getString(CONF_TYPE, serverMap, null, contextLog, true))) { + bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_8_SAAS; + bpmnServerDefinition.zeebeSaasRegion = getString(CONF_ZEEBE_SAAS_REGION, serverMap, null, contextLog, true); + bpmnServerDefinition.zeebeSaasClusterId = getString(CONF_ZEEBE_SAAS_CLUSTER_ID, serverMap, null, contextLog, + true); + bpmnServerDefinition.zeebeClientId = getString(CONF_ZEEBE_CLIENT_ID, serverMap, null, contextLog, true); + bpmnServerDefinition.zeebeClientSecret = getString(CONF_ZEEBE_SECRET, serverMap, null, contextLog, true); + bpmnServerDefinition.zeebeAudience = getString(CONF_ZEEBE_AUDIENCE, serverMap, ZEEBE_DEFAULT_AUDIENCE, + contextLog, true); + bpmnServerDefinition.authenticationUrl = getString(CONF_AUTHENTICATIONURL, serverMap, + "https://login.cloud.camunda.io/oauth/token", contextLog, false); + + bpmnServerDefinition.workerExecutionThreads = getInteger(CONF_WORKER_EXECUTION_THREADS, serverMap, + DEFAULT_VALUE_EXECUTION_THREADS, contextLog); + bpmnServerDefinition.operateUserName = getString(CONF_OPERATE_USER_NAME, serverMap, null, contextLog, false); + bpmnServerDefinition.operateUserPassword = getString(CONF_OPERATE_USER_PASSWORD, serverMap, null, contextLog, + false); + bpmnServerDefinition.operateUrl = getString(CONF_OPERATE_URL, serverMap, null, contextLog, false); + bpmnServerDefinition.taskListUrl = getString(CONF_TASK_LIST_URL, serverMap, null, contextLog, false); + bpmnServerDefinition.taskListClientId = getString(CONF_TASK_LIST_CLIENT_ID, serverMap, null, contextLog, false); + bpmnServerDefinition.taskListClientSecret = getString(CONF_TASK_LIST_CLIENT_SECRET, serverMap, null, contextLog, + false); + + if (bpmnServerDefinition.zeebeSaasRegion == null || bpmnServerDefinition.zeebeClientSecret == null + || bpmnServerDefinition.zeebeSaasClusterId == null || bpmnServerDefinition.zeebeClientId == null) + throw new AutomatorException( + "Incorrect Definition - [zeebeCloudRegister],[zeebeCloudRegion], [zeebeClientSecret},[zeebeCloudClusterId],[zeebeCloudClientId] expected for [Camunda8SaaS] type"); + } + serverList.add(bpmnServerDefinition); + } + return serverList; } - return list; - } - - - - - /* ******************************************************************** */ - /* */ - /* Toolbox */ - /* */ - /* ******************************************************************** */ - - private String getString(String name, - Map<String, Object> recordData, - String defaultValue, - String contextLog, - boolean isMandatory) { - try { - if (!recordData.containsKey(name)) { - if (isMandatory) { - if (defaultValue == null) - logger.error("{}Variable [{}] not defined in {}", contextLog, name, contextLog); - else - logger.info("{} Variable [{}] not defined in {}", contextLog, name, contextLog); + + /** + * DecodeServerConnection + * + * @param connectionString connection string + * @return a ServerDefinition + * @throws AutomatorException on any error + */ + private BpmnServerDefinition decodeServerConnection(String connectionString, String contextLog) + throws AutomatorException { + StringTokenizer st = new StringTokenizer(connectionString, ","); + BpmnServerDefinition bpmnServerDefinition = new BpmnServerDefinition(); + bpmnServerDefinition.name = (st.hasMoreTokens() ? st.nextToken() : null); + try { + bpmnServerDefinition.serverType = st.hasMoreTokens() ? CamundaEngine.valueOf(st.nextToken()) : null; + if (CamundaEngine.CAMUNDA_7.equals(bpmnServerDefinition.serverType)) { + bpmnServerDefinition.camunda7ServerUrl = (st.hasMoreTokens() ? st.nextToken() : null); + + } else if (CamundaEngine.CAMUNDA_8.equals(bpmnServerDefinition.serverType)) { + bpmnServerDefinition.zeebeGatewayAddress = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUrl = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUserName = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateUserPassword = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.taskListUrl = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.workerExecutionThreads = (st.hasMoreTokens() ? + parseInt(CONF_WORKER_EXECUTION_THREADS, st.nextToken(), DEFAULT_VALUE_EXECUTION_THREADS, contextLog) : + null); + bpmnServerDefinition.workerMaxJobsActive = (st.hasMoreTokens() ? + parseInt(CONF_WORKER_MAX_JOBS_ACTIVE, st.nextToken(), DEFAULT_VALUE_MAX_JOBS_ACTIVE, contextLog) : + null); + + } else if (CamundaEngine.CAMUNDA_8_SAAS.equals(bpmnServerDefinition.serverType)) { + bpmnServerDefinition.zeebeSaasRegion = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeSaasClusterId = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeClientId = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeClientSecret = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.zeebeAudience = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateClientId = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.operateClientSecret = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.taskListClientId = (st.hasMoreTokens() ? st.nextToken() : null); + bpmnServerDefinition.taskListClientSecret = (st.hasMoreTokens() ? st.nextToken() : null); + + bpmnServerDefinition.workerExecutionThreads = (st.hasMoreTokens() ? + parseInt(CONF_WORKER_EXECUTION_THREADS, st.nextToken(), DEFAULT_VALUE_EXECUTION_THREADS, contextLog) : + null); + bpmnServerDefinition.workerMaxJobsActive = (st.hasMoreTokens() ? + parseInt(CONF_WORKER_MAX_JOBS_ACTIVE, st.nextToken(), DEFAULT_VALUE_MAX_JOBS_ACTIVE, contextLog) : + null); + } + return bpmnServerDefinition; + } catch (Exception e) { + throw new AutomatorException("Can't decode string [" + connectionString + "] " + e.getMessage()); } - return defaultValue; - } - return (String) recordData.get(name); - } catch (Exception e) { - logger.error("{} Variable [{}] {} bad definition {}", contextLog, name, contextLog, e.getMessage()); - return defaultValue; } - } - private Boolean getBoolean(String name, + /** + * Get the list from the serverConfiguration. If the variable exist, then use the value. Easy to configure for K8 + * + * @return list of BpmnServer + */ + private List<BpmnServerDefinition> getFromServerConfiguration() { + List<BpmnServerDefinition> list = new ArrayList<>(); + + // get the direct list + if (hasValue(configurationServersEngine.camunda7Url)) { + BpmnServerDefinition camunda7 = new BpmnServerDefinition(); + camunda7.serverType = CamundaEngine.CAMUNDA_7; + camunda7.name = configurationServersEngine.camunda7Name; + camunda7.camunda7ServerUrl = configurationServersEngine.camunda7Url; + camunda7.camunda7UserName = configurationServersEngine.camunda7UserName; + camunda7.camunda7Password = configurationServersEngine.camunda7Password; + + camunda7.workerMaxJobsActive = parseInt("Camunda7." + CONF_WORKER_MAX_JOBS_ACTIVE, + configurationServersEngine.C7WorkerMaxJobsActive, DEFAULT_VALUE_MAX_JOBS_ACTIVE, ""); + camunda7.workerExecutionThreads = parseInt("Camunda7." + CONF_WORKER_EXECUTION_THREADS, + configurationServersEngine.C7WorkerMaxJobsActive, DEFAULT_VALUE_EXECUTION_THREADS, ""); + + camunda7.workerMaxJobsActive = parseInt("Camunda7." + CONF_WORKER_MAX_JOBS_ACTIVE, + configurationServersEngine.C7WorkerMaxJobsActive, DEFAULT_VALUE_MAX_JOBS_ACTIVE, ""); + list.add(camunda7); + logger.info("Configuration: Camunda7 Name[{}] url[{}] MaxJobsActive[{}]", camunda7.name, + camunda7.camunda7ServerUrl, camunda7.workerMaxJobsActive); + } + if (hasValue(configurationServersEngine.zeebeGatewayAddress)) { + BpmnServerDefinition camunda8 = new BpmnServerDefinition(); + camunda8.serverType = CamundaEngine.CAMUNDA_8; + camunda8.name = configurationServersEngine.zeebeName; + camunda8.zeebeGatewayAddress = configurationServersEngine.zeebeGatewayAddress; + camunda8.workerExecutionThreads = parseInt("Camunda8." + CONF_WORKER_EXECUTION_THREADS, + configurationServersEngine.zeebeWorkerExecutionThreads, DEFAULT_VALUE_EXECUTION_THREADS, ""); + camunda8.workerMaxJobsActive = parseInt("Camunda8." + CONF_WORKER_MAX_JOBS_ACTIVE, + configurationServersEngine.zeebeWorkerMaxJobsActive, DEFAULT_VALUE_MAX_JOBS_ACTIVE, ""); + camunda8.operateUrl = configurationServersEngine.zeebeOperateUrl; + camunda8.operateUserName = configurationServersEngine.zeebeOperateUserName; + camunda8.operateUserPassword = configurationServersEngine.zeebeOperateUserPassword; + camunda8.taskListUrl = configurationServersEngine.zeebeTaskListUrl; + camunda8.taskListUserName = configurationServersEngine.zeebeTaskListUserName; + camunda8.taskListUserPassword = configurationServersEngine.zeebeTaskListUserPassword; + list.add(camunda8); + logger.info( + "Configuration: Camunda8 Name[{}] zeebeGateway[{}] MaxJobsActive[{}] WorkerThreads[{}] " + "OperateURL[{}]", + camunda8.name, camunda8.camunda7ServerUrl, camunda8.workerMaxJobsActive, camunda8.workerExecutionThreads, + camunda8.operateUrl); + + } + if (hasValue(configurationServersEngine.zeebeSaasClusterId)) { + BpmnServerDefinition camunda8 = new BpmnServerDefinition(); + camunda8.serverType = CamundaEngine.CAMUNDA_8_SAAS; + camunda8.name = configurationServersEngine.zeebeName; + camunda8.zeebeSaasRegion = configurationServersEngine.zeebeSaasRegion; + camunda8.zeebeSaasClusterId = configurationServersEngine.zeebeSaasClusterId; + camunda8.zeebeClientId = configurationServersEngine.zeebeSaasClientId; + camunda8.zeebeClientSecret = configurationServersEngine.zeebeSaasClientSecret; + camunda8.authenticationUrl = configurationServersEngine.zeebeSaasOAuthUrl; + camunda8.zeebeAudience = configurationServersEngine.zeebeSaasAudience; + camunda8.operateUrl = configurationServersEngine.zeebeOperateUrl; + camunda8.operateUserName = configurationServersEngine.zeebeOperateUserName; + camunda8.operateUserPassword = configurationServersEngine.zeebeOperateUserPassword; + camunda8.taskListUrl = configurationServersEngine.zeebeTaskListUrl; + list.add(camunda8); + + } + return list; + } + + + + + /* ******************************************************************** */ + /* */ + /* Toolbox */ + /* */ + /* ******************************************************************** */ + + private String getString(String name, Map<String, Object> recordData, - Boolean defaultValue, + String defaultValue, String contextLog, boolean isMandatory) { - try { - if (!recordData.containsKey(name)) { - if (isMandatory) { - if (defaultValue == null) - logger.error("{}Variable [{}] not defined in {}", contextLog, name, contextLog); - else - logger.info("{} Variable [{}] not defined in {}", contextLog, name, contextLog); + try { + if (!recordData.containsKey(name)) { + if (isMandatory) { + if (defaultValue == null) + logger.error("{}Variable [{}] not defined in {}", contextLog, name, contextLog); + else + logger.info("{} Variable [{}] not defined in {}", contextLog, name, contextLog); + } + return defaultValue; + } + return (String) recordData.get(name); + } catch (Exception e) { + logger.error("{} Variable [{}] {} bad definition {}", contextLog, name, contextLog, e.getMessage()); + return defaultValue; } - return defaultValue; - } - if (recordData.get(name) instanceof Boolean valueBoolean) - return valueBoolean; - return Boolean.valueOf(recordData.get(name).toString()); - } catch (Exception e) { - logger.error("{} Variable [{}] {} bad definition {}", contextLog, name, contextLog, e.getMessage()); - return defaultValue; - } - } - - private Integer getInteger(String name, Map<String, Object> recordData, Integer defaultValue, String contextLog) { - try { - if (!recordData.containsKey(name)) { - if (defaultValue == null) - logger.error("Variable [{}] not defined in {}", name, contextLog); - else - logger.info("Variable [{}] not defined in {}", name, contextLog); - return defaultValue; - } - return (Integer) recordData.get(name); - } catch (Exception e) { - logger.error("Variable [{}] {} bad definition {}", name, contextLog, e.getMessage()); - return defaultValue; - } - } - - private int parseInt(String label, String value, int defaultValue, String contextLog) { - try { - if (value.equals("''")) - return defaultValue; - return Integer.parseInt(value); - } catch (Exception e) { - logger.error("Can't parse value [{}] at [{}] {}", value, label, contextLog); - return defaultValue; } - } - - private boolean hasValue(String value) { - if (value == null) - return false; - if (value.equals("''")) - return false; - return !value.trim().isEmpty(); - } - - /** - * Compare type : CAMUNDA_8 and CAMUNDA_8_SAAS are considered as equals - * - * @param type1 type one to compare - * @param type2 type two to compare - * @return true if types are identical - */ - private boolean sameType(CamundaEngine type1, CamundaEngine type2) { - if (type1.equals(CamundaEngine.CAMUNDA_8_SAAS)) - type1 = CamundaEngine.CAMUNDA_8; - if (type2.equals(CamundaEngine.CAMUNDA_8_SAAS)) - type2 = CamundaEngine.CAMUNDA_8; - return type1.equals(type2); - } - - public enum CamundaEngine {CAMUNDA_7, CAMUNDA_8, CAMUNDA_8_SAAS, DUMMY} - - public static class BpmnServerDefinition { - public String name; - - public CamundaEngine serverType; - /** - * My Zeebe Address - */ - public String zeebeGatewayAddress; - public Boolean zeebePlainText; + private Boolean getBoolean(String name, + Map<String, Object> recordData, + Boolean defaultValue, + String contextLog, + boolean isMandatory) { + try { + if (!recordData.containsKey(name)) { + if (isMandatory) { + if (defaultValue == null) + logger.error("{}Variable [{}] not defined in {}", contextLog, name, contextLog); + else + logger.info("{} Variable [{}] not defined in {}", contextLog, name, contextLog); + } + return defaultValue; + } + if (recordData.get(name) instanceof Boolean valueBoolean) + return valueBoolean; + return Boolean.valueOf(recordData.get(name).toString()); + } catch (Exception e) { + logger.error("{} Variable [{}] {} bad definition {}", contextLog, name, contextLog, e.getMessage()); + return defaultValue; + } + } - /** - * SaaS Zeebe - */ - public String zeebeSaasRegion; - public String zeebeSaasClusterId; - public String zeebeClientId; - public String zeebeClientSecret; - public String zeebeAudience; - public String zeebeTenantId = null; - - public String identityUrl; - /** - * Connection to Operate - */ - public String operateUserName; - public String operateUserPassword; - public String operateUrl; - - // something like "http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token" - public String authenticationUrl; - public String operateClientId; - public String operateClientSecret; - public String operateAudience; - - public String taskListUrl; - public String taskListUserName; - public String taskListUserPassword; - public String taskListClientId; - public String taskListClientSecret; - public String taskListKeycloakUrl; + private Integer getInteger(String name, Map<String, Object> recordData, Integer defaultValue, String contextLog) { + try { + if (!recordData.containsKey(name)) { + if (defaultValue == null) + logger.error("Variable [{}] not defined in {}", name, contextLog); + else + logger.info("Variable [{}] not defined in {}", name, contextLog); + return defaultValue; + } + return (Integer) recordData.get(name); + } catch (Exception e) { + logger.error("Variable [{}] {} bad definition {}", name, contextLog, e.getMessage()); + return defaultValue; + } + } - /** - * Camunda 7 - */ - public String camunda7ServerUrl; - public String camunda7UserName; - public String camunda7Password; + private int parseInt(String label, String value, int defaultValue, String contextLog) { + try { + if (value.equals("''")) + return defaultValue; + return Integer.parseInt(value); + } catch (Exception e) { + logger.error("Can't parse value [{}] at [{}] {}", value, label, contextLog); + return defaultValue; + } + } - /** - * Common Camunda 7 and Camunda8 - */ - public Integer workerExecutionThreads = Integer.valueOf(DEFAULT_VALUE_EXECUTION_THREADS); - public Integer workerMaxJobsActive = Integer.valueOf(DEFAULT_VALUE_MAX_JOBS_ACTIVE); + private boolean hasValue(String value) { + if (value == null) + return false; + if (value.equals("''")) + return false; + return !value.trim().isEmpty(); + } /** - * return true if the definition have an Operate connection valid + * Compare type : CAMUNDA_8 and CAMUNDA_8_SAAS are considered as equals * - * @return true is Operate is required + * @param type1 type one to compare + * @param type2 type two to compare + * @return true if types are identical */ - public boolean isOperate() { - return !(operateUrl == null || operateUrl.isEmpty()); + private boolean sameType(CamundaEngine type1, CamundaEngine type2) { + if (type1.equals(CamundaEngine.CAMUNDA_8_SAAS)) + type1 = CamundaEngine.CAMUNDA_8; + if (type2.equals(CamundaEngine.CAMUNDA_8_SAAS)) + type2 = CamundaEngine.CAMUNDA_8; + return type1.equals(type2); } - public boolean isTaskList() { - return !(taskListUrl == null || taskListUrl.isEmpty()); - } + public enum CamundaEngine {CAMUNDA_7, CAMUNDA_8, CAMUNDA_8_SAAS, DUMMY} + + public static class BpmnServerDefinition { + public String name; + + public CamundaEngine serverType; + + /** + * My Zeebe Address + */ + public String zeebeGatewayAddress; + public Boolean zeebePlainText; + + /** + * SaaS Zeebe + */ + public String zeebeSaasRegion; + public String zeebeSaasClusterId; + public String zeebeClientId; + public String zeebeClientSecret; + public String zeebeAudience; + public String zeebeTenantId = null; + + public String identityUrl; + /** + * Connection to Operate + */ + public String operateUserName; + public String operateUserPassword; + public String operateUrl; + + // something like "http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token" + public String authenticationUrl; + public String operateClientId; + public String operateClientSecret; + public String operateAudience; + + public String taskListUrl; + public String taskListUserName; + public String taskListUserPassword; + public String taskListClientId; + public String taskListClientSecret; + public String taskListKeycloakUrl; + + /** + * Camunda 7 + */ + public String camunda7ServerUrl; + public String camunda7UserName; + public String camunda7Password; + + /** + * Common Camunda 7 and Camunda8 + */ + public Integer workerExecutionThreads = Integer.valueOf(DEFAULT_VALUE_EXECUTION_THREADS); + public Integer workerMaxJobsActive = Integer.valueOf(DEFAULT_VALUE_MAX_JOBS_ACTIVE); + + /** + * return true if the definition have an Operate connection valid + * + * @return true is Operate is required + */ + public boolean isOperate() { + return !(operateUrl == null || operateUrl.isEmpty()); + } - public boolean isAuthenticationUrl() { - return !(authenticationUrl == null || authenticationUrl.isEmpty()); - } + public boolean isTaskList() { + return !(taskListUrl == null || taskListUrl.isEmpty()); + } + + public boolean isAuthenticationUrl() { + return !(authenticationUrl == null || authenticationUrl.isEmpty()); + } - public String getSynthesis() { - String synthesis = serverType.name(); - if (serverType.equals(CamundaEngine.CAMUNDA_7)) { - synthesis += " url[" + camunda7ServerUrl + "] userName[" + camunda7UserName + "]"; - } - if (serverType.equals(CamundaEngine.CAMUNDA_8)) { - synthesis += " address[" + zeebeGatewayAddress + "] workerThread[" + workerExecutionThreads + "] MaxJobActive[" - + workerMaxJobsActive + "]"; - } - if (serverType.equals(CamundaEngine.CAMUNDA_8_SAAS)) { - synthesis += " clientId[" + zeebeClientId + "] workerThread[" + workerExecutionThreads + "] MaxJobActive[" - + workerMaxJobsActive + "]"; - } - return synthesis; + public String getSynthesis() { + String synthesis = serverType.name(); + if (serverType.equals(CamundaEngine.CAMUNDA_7)) { + synthesis += " url[" + camunda7ServerUrl + "] userName[" + camunda7UserName + "]"; + } + if (serverType.equals(CamundaEngine.CAMUNDA_8)) { + synthesis += " address[" + zeebeGatewayAddress + "] workerThread[" + workerExecutionThreads + "] MaxJobActive[" + + workerMaxJobsActive + "]"; + } + if (serverType.equals(CamundaEngine.CAMUNDA_8_SAAS)) { + synthesis += " clientId[" + zeebeClientId + "] workerThread[" + workerExecutionThreads + "] MaxJobActive[" + + workerMaxJobsActive + "]"; + } + return synthesis; + } } - } } diff --git a/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java b/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java index c36017e..d4877ae 100644 --- a/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java +++ b/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java @@ -11,84 +11,84 @@ @ConfigurationProperties("automator") public class ConfigurationServersEngine { - // @ V alue("${automator.logDebug:false}") - public boolean logDebug = false; - - @Value("#{'${automator.serversConnection}'.split(';')}") - public List<String> serversConnection; - - public List<Map<String, Object>> serversList; - - @Value("${automator.servers.camunda7.url:''}") - public String camunda7Url; - @Value("${automator.servers.camunda7.username:}") - public String camunda7UserName; - @Value("${automator.servers.camunda7.password:}") - public String camunda7Password; - @Value("${automator.servers.camunda7.name:''}") - public String camunda7Name; - @Value("${automator.servers.camunda7.workerMaxJobsActive:''}") - public String C7WorkerMaxJobsActive; - - @Value("${automator.servers.camunda8.name:''}") - public String zeebeName; - @Value("${automator.servers.camunda8.zeebeGatewayAddress:''}") - public String zeebeGatewayAddress; - @Value("${automator.servers.camunda8.operateUrl:''}") - public String zeebeOperateUrl; - @Value("${automator.servers.camunda8.operateUserName:''}") - public String zeebeOperateUserName; - @Value("${automator.servers.camunda8.operateUserPassword:''}") - public String zeebeOperateUserPassword; - - @Value("${automator.servers.camunda8.taskListUrl:''}") - public String zeebeTaskListUrl; - @Value("${automator.servers.camunda8.taskListUserName:''}") - public String zeebeTaskListUserName; - @Value("${automator.servers.camunda8.taskListUserPassword:''}") - public String zeebeTaskListUserPassword; - - @Value("${automator.servers.camunda8.workerExecutionThreads:''}") - public String zeebeWorkerExecutionThreads; - @Value("${automator.servers.camunda8.workerMaxJobsActive:''}") - public String zeebeWorkerMaxJobsActive; - - @Value("${automator.servers.camunda8Saas.region:''}") - public String zeebeSaasRegion; - @Value("${automator.servers.camunda8Saas.clusterId:''}") - public String zeebeSaasClusterId; - @Value("${automator.servers.camunda8Saas.clientId:''}") - public String zeebeSaasClientId; - - @Value("${automator.servers.camunda8Saas.secret:''}") - public String zeebeSaasClientSecret; - - @Value("${automator.servers.camunda8Saas.oAuthUrl:''}") - public String zeebeSaasOAuthUrl; - @Value("${automator.servers.camunda8Saas.audience:''}") - public String zeebeSaasAudience; - - @Value("${automator.servers.camunda8Saas.operateUrl:''}") - public String zeebeSaasOperateUrl; - @Value("${automator.servers.camunda8Saas.operateUserName:''}") - public String zeebeSaasOperateUserName; - @Value("${automator.servers.camunda8Saas.operateUserPassword:''}") - public String zeebeSaasOperateUserPassword; - @Value("${automator.servers.camunda8Saas.taskListUrl:''}") - public String zeebeSaasTaskListUrl; - @Value("${automator.servers.camunda8Saas.workerExecutionThreads:''}") - public String zeebeSaasWorkerExecutionThreads; - @Value("${automator.servers.camunda8Saas.workerMaxJobsActive:''}") - public String zeebeSaasWorkerMaxJobsActive; - - public List<Map<String, Object>> getServersList() { - return serversList; - } - - // this method is mandatory for Spring to get the value and to force it as a List<Map<>> - public void setServersList(List<Map<String, Object>> serversList) { - this.serversList = serversList; - } + // @ V alue("${automator.logDebug:false}") + public boolean logDebug = false; + + @Value("#{'${automator.serversConnection}'.split(';')}") + public List<String> serversConnection; + + public List<Map<String, Object>> serversList; + + @Value("${automator.servers.camunda7.url:''}") + public String camunda7Url; + @Value("${automator.servers.camunda7.username:}") + public String camunda7UserName; + @Value("${automator.servers.camunda7.password:}") + public String camunda7Password; + @Value("${automator.servers.camunda7.name:''}") + public String camunda7Name; + @Value("${automator.servers.camunda7.workerMaxJobsActive:''}") + public String C7WorkerMaxJobsActive; + + @Value("${automator.servers.camunda8.name:''}") + public String zeebeName; + @Value("${automator.servers.camunda8.zeebeGatewayAddress:''}") + public String zeebeGatewayAddress; + @Value("${automator.servers.camunda8.operateUrl:''}") + public String zeebeOperateUrl; + @Value("${automator.servers.camunda8.operateUserName:''}") + public String zeebeOperateUserName; + @Value("${automator.servers.camunda8.operateUserPassword:''}") + public String zeebeOperateUserPassword; + + @Value("${automator.servers.camunda8.taskListUrl:''}") + public String zeebeTaskListUrl; + @Value("${automator.servers.camunda8.taskListUserName:''}") + public String zeebeTaskListUserName; + @Value("${automator.servers.camunda8.taskListUserPassword:''}") + public String zeebeTaskListUserPassword; + + @Value("${automator.servers.camunda8.workerExecutionThreads:''}") + public String zeebeWorkerExecutionThreads; + @Value("${automator.servers.camunda8.workerMaxJobsActive:''}") + public String zeebeWorkerMaxJobsActive; + + @Value("${automator.servers.camunda8Saas.region:''}") + public String zeebeSaasRegion; + @Value("${automator.servers.camunda8Saas.clusterId:''}") + public String zeebeSaasClusterId; + @Value("${automator.servers.camunda8Saas.clientId:''}") + public String zeebeSaasClientId; + + @Value("${automator.servers.camunda8Saas.secret:''}") + public String zeebeSaasClientSecret; + + @Value("${automator.servers.camunda8Saas.oAuthUrl:''}") + public String zeebeSaasOAuthUrl; + @Value("${automator.servers.camunda8Saas.audience:''}") + public String zeebeSaasAudience; + + @Value("${automator.servers.camunda8Saas.operateUrl:''}") + public String zeebeSaasOperateUrl; + @Value("${automator.servers.camunda8Saas.operateUserName:''}") + public String zeebeSaasOperateUserName; + @Value("${automator.servers.camunda8Saas.operateUserPassword:''}") + public String zeebeSaasOperateUserPassword; + @Value("${automator.servers.camunda8Saas.taskListUrl:''}") + public String zeebeSaasTaskListUrl; + @Value("${automator.servers.camunda8Saas.workerExecutionThreads:''}") + public String zeebeSaasWorkerExecutionThreads; + @Value("${automator.servers.camunda8Saas.workerMaxJobsActive:''}") + public String zeebeSaasWorkerMaxJobsActive; + + public List<Map<String, Object>> getServersList() { + return serversList; + } + + // this method is mandatory for Spring to get the value and to force it as a List<Map<>> + public void setServersList(List<Map<String, Object>> serversList) { + this.serversList = serversList; + } } diff --git a/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java b/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java index 230f46a..fedc556 100644 --- a/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java +++ b/src/main/java/org/camunda/automator/configuration/ConfigurationStartup.java @@ -17,134 +17,134 @@ @PropertySource("classpath:application.yaml") @Configuration public class ConfigurationStartup { - static Logger logger = LoggerFactory.getLogger(ConfigurationStartup.class); - @Value("${automator.startup.scenarioPath}") - public String scenarioPath; - @Value("${automator.startup.logLevel:MONITORING}") - public String logLevel; - @Value("${automator.startup.deeptracking:false}") - public boolean deepTracking; - @Value("${automator.startup.policyExecution:DEPLOYPROCESS|WARMINGUP|CREATION|SERVICETASK|USERTASK}") - public String policyExecution; - - @Value("${automator.startEvent.nbThreads:#{null}}") - public Integer startEventNbThreads; - - /** - * it may be necessary to wait the other component to warm up - */ - @Value("${automator.startup.waitWarmUpServer:PT0S}") - public String waitWarmupServer; - - @Value("${automator.startup.serverName}") - private String serverName; - - @Value("#{'${automator.startup.scenarioFileAtStartup:}'.split(';')}") - private List<String> scenarioFileAtStartup; - - @Value("${automator.startup.scenarioResourceAtStartup:}") - private Resource scenarioResourceAtStartup; - - @Value("#{'${automator.startup.filterService:}'.split(';')}") - private List<String> filterService; - - public String getServerName() { - return serverName; - } - - public void setLogLevel(String logLevel) { - this.logLevel = logLevel; - } - - public RunParameters.LOGLEVEL getLogLevelEnum() { - try { - return RunParameters.LOGLEVEL.valueOf(logLevel); - } catch (Exception e) { - logger.error("Unknow LogLevel (automator.startup.loglevel) : [{}} ", logLevel); - return RunParameters.LOGLEVEL.MONITORING; - } - } - - public boolean deepTracking() { - return deepTracking; - } - - public boolean isPolicyExecutionCreation() { - String policyExtended = "|" + policyExecution + "|"; - return policyExtended.contains("|CREATION|"); - } - - public boolean isPolicyExecutionServiceTask() { - String policyExtended = "|" + policyExecution + "|"; - return policyExtended.contains("|SERVICETASK|"); - } - - public boolean isPolicyExecutionUserTask() { - String policyExtended = "|" + policyExecution + "|"; - return policyExtended.contains("|USERTASK|"); - } - - public boolean isPolicyExecutionWarmingUp() { - String policyExtended = "|" + policyExecution + "|"; - return policyExtended.contains("|WARMINGUP|"); - } - - public boolean isPolicyDeployProcess() { - String policyExtended = "|" + policyExecution + "|"; - return policyExtended.contains("|DEPLOYPROCESS|"); - } - - public Integer getStartEventNbThreads() { - return startEventNbThreads; - } - - public List<String> getScenarioFileAtStartup() { - return recalibrateAfterSplit(scenarioFileAtStartup); - } - - /** - * Return the name for the variable scenarioAtStartup - * - * @return the name - */ - public String getScenarioFileAtStartupName() { - return "automator.startup.scenarioAtStartup"; - } - - /** - * Return the list of collection - only one at this moment - * - * @return list of scenario detected as a resource - */ - public List<Resource> getScenarioResourceAtStartup() { - return Collections.singletonList(scenarioResourceAtStartup); - } - - /** - * return the name of the resourceAtStartup variable name - * - * @return name of the variable - */ - public String getScenarioResourceAtStartupName() { - return "automator.startup.scenarioResourceAtStartup"; - } - - public List<String> getFilterService() { - return recalibrateAfterSplit(filterService); - } - - public Duration getWarmingUpServer() { - try { - return Duration.parse(waitWarmupServer); - } catch (Exception e) { - logger.error("Can't parse warmup [{}]", waitWarmupServer); - return Duration.ZERO; - } - } - - private List<String> recalibrateAfterSplit(List<String> originalList) { - if (originalList.size() == 1 && originalList.get(0).isEmpty()) - return Collections.emptyList(); - return originalList; - } + static Logger logger = LoggerFactory.getLogger(ConfigurationStartup.class); + @Value("${automator.startup.scenarioPath}") + public String scenarioPath; + @Value("${automator.startup.logLevel:MONITORING}") + public String logLevel; + @Value("${automator.startup.deeptracking:false}") + public boolean deepTracking; + @Value("${automator.startup.policyExecution:DEPLOYPROCESS|WARMINGUP|CREATION|SERVICETASK|USERTASK}") + public String policyExecution; + + @Value("${automator.startEvent.nbThreads:#{null}}") + public Integer startEventNbThreads; + + /** + * it may be necessary to wait the other component to warm up + */ + @Value("${automator.startup.waitWarmUpServer:PT0S}") + public String waitWarmupServer; + + @Value("${automator.startup.serverName}") + private String serverName; + + @Value("#{'${automator.startup.scenarioFileAtStartup:}'.split(';')}") + private List<String> scenarioFileAtStartup; + + @Value("${automator.startup.scenarioResourceAtStartup:}") + private Resource scenarioResourceAtStartup; + + @Value("#{'${automator.startup.filterService:}'.split(';')}") + private List<String> filterService; + + public String getServerName() { + return serverName; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public RunParameters.LOGLEVEL getLogLevelEnum() { + try { + return RunParameters.LOGLEVEL.valueOf(logLevel); + } catch (Exception e) { + logger.error("Unknow LogLevel (automator.startup.loglevel) : [{}} ", logLevel); + return RunParameters.LOGLEVEL.MONITORING; + } + } + + public boolean deepTracking() { + return deepTracking; + } + + public boolean isPolicyExecutionCreation() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|CREATION|"); + } + + public boolean isPolicyExecutionServiceTask() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|SERVICETASK|"); + } + + public boolean isPolicyExecutionUserTask() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|USERTASK|"); + } + + public boolean isPolicyExecutionWarmingUp() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|WARMINGUP|"); + } + + public boolean isPolicyDeployProcess() { + String policyExtended = "|" + policyExecution + "|"; + return policyExtended.contains("|DEPLOYPROCESS|"); + } + + public Integer getStartEventNbThreads() { + return startEventNbThreads; + } + + public List<String> getScenarioFileAtStartup() { + return recalibrateAfterSplit(scenarioFileAtStartup); + } + + /** + * Return the name for the variable scenarioAtStartup + * + * @return the name + */ + public String getScenarioFileAtStartupName() { + return "automator.startup.scenarioAtStartup"; + } + + /** + * Return the list of collection - only one at this moment + * + * @return list of scenario detected as a resource + */ + public List<Resource> getScenarioResourceAtStartup() { + return Collections.singletonList(scenarioResourceAtStartup); + } + + /** + * return the name of the resourceAtStartup variable name + * + * @return name of the variable + */ + public String getScenarioResourceAtStartupName() { + return "automator.startup.scenarioResourceAtStartup"; + } + + public List<String> getFilterService() { + return recalibrateAfterSplit(filterService); + } + + public Duration getWarmingUpServer() { + try { + return Duration.parse(waitWarmupServer); + } catch (Exception e) { + logger.error("Can't parse warmup [{}]", waitWarmupServer); + return Duration.ZERO; + } + } + + private List<String> recalibrateAfterSplit(List<String> originalList) { + if (originalList.size() == 1 && originalList.get(0).isEmpty()) + return Collections.emptyList(); + return originalList; + } } diff --git a/src/main/java/org/camunda/automator/content/ContentManager.java b/src/main/java/org/camunda/automator/content/ContentManager.java new file mode 100644 index 0000000..dafa0fd --- /dev/null +++ b/src/main/java/org/camunda/automator/content/ContentManager.java @@ -0,0 +1,104 @@ +package org.camunda.automator.content; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.PostConstruct; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@PropertySource("classpath:application.yaml") +@Configuration + +public class ContentManager { + private static final Logger logger = LoggerFactory.getLogger(ContentManager.class.getName()); + @Value("${automator.content.repositoryPath}") + public String repositoryPath; + @Value("${automator.content.uploadPath}") + public String uploadPath; + + public File getFromName(String scenarioName) { + return new File(repositoryPath + File.separator + scenarioName + ".json"); + } + + + public void saveFromMultiPartFile(MultipartFile file, String fileName) { + // save the file on a temporary disk + OutputStream outputStream = null; + File fileScenario = null; + try { + fileScenario = new File(repositoryPath + File.separator + fileName); + // Open an OutputStream to the temporary file + outputStream = new FileOutputStream(fileScenario); + // Transfer data from InputStream to OutputStream + byte[] buffer = new byte[1024 * 100]; // 100Ko + int bytesRead; + int count = 0; + InputStream inputStream = file.getInputStream(); + while ((bytesRead = inputStream.read(buffer)) != -1) { + count += bytesRead; + outputStream.write(buffer, 0, bytesRead); + } + outputStream.flush(); + outputStream.close(); + outputStream = null; + } catch (Exception e) { + logger.error("Can't load File [" + fileName + "] : " + e.getMessage()); + } finally { + if (outputStream != null) + try { + outputStream.close(); + } catch (Exception e) { + // do nothing + } + } + } + + @PostConstruct + public void init() { + Path sourceDirectory = Paths.get(uploadPath); + Path targetDirectory = Paths.get(repositoryPath); + logger.info("ContentManager initiaalisation Copied: [{}] to [{}]", sourceDirectory, targetDirectory); + + int nbFilesCopied = 0; + try { + // Create target directory if it doesn't exist + if (!Files.exists(targetDirectory)) { + Files.createDirectories(targetDirectory); + } + + // Copy all files from source to target + List<Path> listFiles = Files.walk(sourceDirectory) + .filter(Files::isRegularFile).toList(); + + for (Path sourcePath : listFiles)// Filter only regular files + { + try { + Path targetPath = targetDirectory.resolve(sourceDirectory.relativize(sourcePath)); + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + logger.info("Copied: [{}] tp [{}]", sourcePath, targetPath); + nbFilesCopied++; + } catch (IOException e) { + logger.error("Error copying file: [{}] -> [{}] : {}", sourcePath, targetDirectory, e.getMessage()); + } + } + + + } catch (IOException e) { + logger.error("Error occurred: {} ", e.getMessage()); + } + logger.info("End of ContentManager {} files copied", nbFilesCopied); + } + +} diff --git a/src/main/java/org/camunda/automator/content/ContentRestController.java b/src/main/java/org/camunda/automator/content/ContentRestController.java new file mode 100644 index 0000000..41db040 --- /dev/null +++ b/src/main/java/org/camunda/automator/content/ContentRestController.java @@ -0,0 +1,40 @@ +package org.camunda.automator.content; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +public class ContentRestController { + private static final Logger logger = LoggerFactory.getLogger(ContentRestController.class.getName()); + + + @Autowired + ContentManager contentManager; + + @PostMapping(value = "/api/content/add", consumes = { + MediaType.MULTIPART_FORM_DATA_VALUE}, produces = MediaType.APPLICATION_JSON_VALUE) + public Map<String, Object> upload(@RequestPart("File") List<MultipartFile> uploadedfiles) { + Map<String, Object> status = new HashMap<>(); + for (MultipartFile file : uploadedfiles) { + String resultFile = "Load [" + file.getName() + "]"; + + // is this worker is running? + String jarFileName = file.getOriginalFilename(); + contentManager.saveFromMultiPartFile(file, jarFileName); + + } + return new HashMap<>(); + } + + +} \ No newline at end of file diff --git a/src/main/java/org/camunda/automator/definition/Scenario.java b/src/main/java/org/camunda/automator/definition/Scenario.java index 59bf4c5..ff8bce3 100644 --- a/src/main/java/org/camunda/automator/definition/Scenario.java +++ b/src/main/java/org/camunda/automator/definition/Scenario.java @@ -12,13 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.util.ArrayList; import java.util.List; @@ -26,177 +20,177 @@ * the Scenario Head group a scenario definition */ public class Scenario { - static Logger logger = LoggerFactory.getLogger(Scenario.class); - - private final List<ScenarioDeployment> deployments = new ArrayList<>(); - private final List<ScenarioStep> flows = new ArrayList<>(); - /** - * Type UNIT - */ - private final List<ScenarioExecution> executions = new ArrayList<>(); - - public TYPESCENARIO typeScenario; - - /** - * Type FLOW - */ - private ScenarioWarmingUp warmingUp; - private ScenarioFlowControl flowControl; - private String name; - private String version; - private String processName; - private String processId; - /** - * Server to run the scenario (optional, will be overide by the configuration) - */ - private String serverName; - private String serverType; - /** - * This value is fulfill only if the scenario was read from a file - */ - private String scenarioFile = null; - - public static Scenario createFromJson(String jsonContent) { - GsonBuilder builder = new GsonBuilder(); - builder.setPrettyPrinting(); - - Gson gson = builder.create(); - Scenario scenario = gson.fromJson(jsonContent, Scenario.class); - if (scenario == null) { - logger.error("Scenario: Can't build scenario from content [{}]", jsonContent); - return null; - } - scenario.afterUnSerialize(); - return scenario; - } - - public static Scenario createFromFile(File scenarioFile) throws AutomatorException { - try { - - Scenario scenario = createFromInputStream(new FileInputStream(scenarioFile), scenarioFile.getAbsolutePath()); - scenario.scenarioFile = scenarioFile.getAbsolutePath(); - scenario.initialize(); - return scenario; - - } catch (FileNotFoundException e) { - throw new AutomatorException("Can't access file [" + scenarioFile.getAbsolutePath() + "] " + e.getMessage()); - } catch (AutomatorException e) { - throw e; - } - } - - /** - * Load the scenario from a File - * - * @param scenarioInput InputStream to read - * @return the scenario - * @throws AutomatorException if file cannot be read or it's not a Json file - */ - public static Scenario createFromInputStream(InputStream scenarioInput, String origin) throws AutomatorException { - logger.info("Load Scenario [{}] from InputStream", origin); - try (BufferedReader br = new BufferedReader(new InputStreamReader(scenarioInput))) { - StringBuilder jsonContent = new StringBuilder(); - String st; - while ((st = br.readLine()) != null) - jsonContent.append(st); - - Scenario scnHead = createFromJson(jsonContent.toString()); - if (scnHead == null) { - throw new AutomatorException("Scenario: can't load from JSON [" + jsonContent + "] "); - } - scnHead.initialize(); - return scnHead; - } catch (IOException e) { - logger.error("CreateScenarioFromInputString: origin[{}] error {} : {} ", origin, e.getMessage(), e.toString()); - throw new AutomatorException("Can't load content from [" + origin + "] " + e.getMessage()); - } - - } - - /** - * Initialize the scenario and complete it - */ - private void initialize() { - } - - /** - * Add a new execution - * - * @return the scenario itself - */ - public Scenario addExecution(ScenarioExecution scnExecution) { - executions.add(scnExecution); - return this; - } - - public List<ScenarioExecution> getExecutions() { - return executions; - } - - public List<ScenarioStep> getFlows() { - return flows; - } - - public ScenarioWarmingUp getWarmingUp() { - return warmingUp; - } - - public ScenarioFlowControl getFlowControl() { - return flowControl; - } - - public List<ScenarioDeployment> getDeployments() { - return deployments; - } - - public String getName() { - return name; - } - - public Scenario setName(String name) { - this.name = name; - return this; - } - - public String getVersion() { - return version; - } - - public String getProcessName() { - return processName; - } - - public String getProcessId() { - return processId; - } - - public Scenario setProcessId(String processId) { - this.processId = processId; - return this; - } - - public File getScenarioFile() { - try { - return new File(scenarioFile); - } catch (Exception e) { - logger.error("Can't access file [{}] ", scenarioFile); - return null; - } - } - - public String getServerName() { - if (serverName == null || serverName.isEmpty()) - return null; - return serverName; - } - - private void afterUnSerialize() { - // Attention, now we have to manually set the tree relation - for (ScenarioExecution scnExecution : getExecutions()) { - scnExecution.afterUnSerialize(this); - } - } - - public enum TYPESCENARIO {FLOW, UNIT} + static Logger logger = LoggerFactory.getLogger(Scenario.class); + + private final List<ScenarioDeployment> deployments = new ArrayList<>(); + private final List<ScenarioStep> flows = new ArrayList<>(); + /** + * Type UNIT + */ + private final List<ScenarioExecution> executions = new ArrayList<>(); + + public TYPESCENARIO typeScenario; + + /** + * Type FLOW + */ + private ScenarioWarmingUp warmingUp; + private ScenarioFlowControl flowControl; + private String name; + private String version; + private String processName; + private String processId; + /** + * Server to run the scenario (optional, will be overide by the configuration) + */ + private String serverName; + private String serverType; + /** + * This value is fulfill only if the scenario was read from a file + */ + private String scenarioFile = null; + + public static Scenario createFromJson(String jsonContent) { + GsonBuilder builder = new GsonBuilder(); + builder.setPrettyPrinting(); + + Gson gson = builder.create(); + Scenario scenario = gson.fromJson(jsonContent, Scenario.class); + if (scenario == null) { + logger.error("Scenario: Can't build scenario from content [{}]", jsonContent); + return null; + } + scenario.afterUnSerialize(); + return scenario; + } + + public static Scenario createFromFile(File scenarioFile) throws AutomatorException { + try { + + Scenario scenario = createFromInputStream(new FileInputStream(scenarioFile), scenarioFile.getAbsolutePath()); + scenario.scenarioFile = scenarioFile.getAbsolutePath(); + scenario.initialize(); + return scenario; + + } catch (FileNotFoundException e) { + throw new AutomatorException("Can't access file [" + scenarioFile.getAbsolutePath() + "] " + e.getMessage()); + } catch (AutomatorException e) { + throw e; + } + } + + /** + * Load the scenario from a File + * + * @param scenarioInput InputStream to read + * @return the scenario + * @throws AutomatorException if file cannot be read or it's not a Json file + */ + public static Scenario createFromInputStream(InputStream scenarioInput, String origin) throws AutomatorException { + logger.info("Load Scenario [{}] from InputStream", origin); + try (BufferedReader br = new BufferedReader(new InputStreamReader(scenarioInput))) { + StringBuilder jsonContent = new StringBuilder(); + String st; + while ((st = br.readLine()) != null) + jsonContent.append(st); + + Scenario scnHead = createFromJson(jsonContent.toString()); + if (scnHead == null) { + throw new AutomatorException("Scenario: can't load from JSON [" + jsonContent + "] "); + } + scnHead.initialize(); + return scnHead; + } catch (IOException e) { + logger.error("CreateScenarioFromInputString: origin[{}] error {} : {} ", origin, e.getMessage(), e.toString()); + throw new AutomatorException("Can't load content from [" + origin + "] " + e.getMessage()); + } + + } + + /** + * Initialize the scenario and complete it + */ + private void initialize() { + } + + /** + * Add a new execution + * + * @return the scenario itself + */ + public Scenario addExecution(ScenarioExecution scnExecution) { + executions.add(scnExecution); + return this; + } + + public List<ScenarioExecution> getExecutions() { + return executions; + } + + public List<ScenarioStep> getFlows() { + return flows; + } + + public ScenarioWarmingUp getWarmingUp() { + return warmingUp; + } + + public ScenarioFlowControl getFlowControl() { + return flowControl; + } + + public List<ScenarioDeployment> getDeployments() { + return deployments; + } + + public String getName() { + return name; + } + + public Scenario setName(String name) { + this.name = name; + return this; + } + + public String getVersion() { + return version; + } + + public String getProcessName() { + return processName; + } + + public String getProcessId() { + return processId; + } + + public Scenario setProcessId(String processId) { + this.processId = processId; + return this; + } + + public File getScenarioFile() { + try { + return new File(scenarioFile); + } catch (Exception e) { + logger.error("Can't access file [{}] ", scenarioFile); + return null; + } + } + + public String getServerName() { + if (serverName == null || serverName.isEmpty()) + return null; + return serverName; + } + + private void afterUnSerialize() { + // Attention, now we have to manually set the tree relation + for (ScenarioExecution scnExecution : getExecutions()) { + scnExecution.afterUnSerialize(this); + } + } + + public enum TYPESCENARIO {FLOW, UNIT} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java b/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java index 7db6cb4..c211c0b 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioDeployment.java @@ -3,23 +3,23 @@ import org.camunda.automator.configuration.BpmnEngineList; public class ScenarioDeployment { - /** - * type of server - */ - public BpmnEngineList.CamundaEngine serverType; - /** - * Type pf deployment - */ - public String typeDeployment; - /** - * Name of the file - */ - public String processFile; + /** + * type of server + */ + public BpmnEngineList.CamundaEngine serverType; + /** + * Type pf deployment + */ + public String typeDeployment; + /** + * Name of the file + */ + public String processFile; - public Policy policy; + public Policy policy; - public enum TypeDeployment {PROCESS} + public enum TypeDeployment {PROCESS} - public enum Policy {ONLYNOTEXIST, ALWAYS} + public enum Policy {ONLYNOTEXIST, ALWAYS} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java index 132bf2d..0d0af65 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java @@ -9,132 +9,136 @@ */ public class ScenarioExecution { - private final List<ScenarioStep> steps = new ArrayList<>(); - private Scenario scnHead; - private ScenarioVerification verifications; - - /** - * Name of this execution - */ - private String name; - /** - * Number of process instance to create - */ - private Integer numberProcessInstances; - - /** - * Number of thread in parallel to execute all process instances. Default is 1 - */ - private Integer numberOfThreads; - private Policy policy; - /** - * if set to false, this execution is skipped - */ - private Boolean execution; - - /** - * Note: when the object is un-serialized from JSON, scnHead is null - * - * @param scenario root information - */ - protected ScenarioExecution(Scenario scenario) { - this.scnHead = scenario; - } - - public static ScenarioExecution createExecution(Scenario scnHead) { - return new ScenarioExecution(scnHead); - } - - - - /* ******************************************************************** */ - /* */ - /* Creator and setter to help the API */ - /* */ - /* ******************************************************************** */ - - /** - * After UnSerialize, all link to parent are not restored - * - * @param scnHead head of scenario - */ - public void afterUnSerialize(Scenario scnHead) { - this.scnHead = scnHead; - for (ScenarioStep scnStep : steps) { - scnStep.afterUnSerialize(this); + private final List<ScenarioStep> steps = new ArrayList<>(); + private Scenario scnHead; + private ScenarioVerification verifications; + + /** + * Name of this execution + */ + private String name; + /** + * Number of process instance to create + */ + private Integer numberProcessInstances; + + /** + * Number of thread in parallel to execute all process instances. Default is 1 + */ + private Integer numberOfThreads; + private Policy policy; + /** + * if set to false, this execution is skipped + */ + private Boolean execution; + + /** + * Note: when the object is un-serialized from JSON, scnHead is null + * + * @param scenario root information + */ + protected ScenarioExecution(Scenario scenario) { + this.scnHead = scenario; } - } - - /** - * Add a step in the scenario - * - * @param step step part of the scenario - * @return this object - */ - public ScenarioExecution addStep(ScenarioStep step) { - steps.add(step); - return this; - } - - public List<ScenarioStep> getSteps() { - return steps == null ? Collections.emptyList() : steps; - } - - public ScenarioVerification getVerifications() { - return verifications; - } - - - - /* ******************************************************************** */ - /* */ - /* getter */ - /* */ - /* ******************************************************************** */ - - public int getNumberProcessInstances() { - return numberProcessInstances == null ? 1 : numberProcessInstances; - } - - /** - * Ask this execution to execute a number of process instance. - * - * @param numberProcessInstances number of process instance to execute - * @return this object - */ - public ScenarioExecution setNumberProcessInstances(int numberProcessInstances) { - this.numberProcessInstances = numberProcessInstances; - return this; - } - - public Scenario getScnHead() { - return scnHead; - } - - public String getName() { - return name; - } - - public ScenarioExecution setName(String name) { - this.name = name; - return this; - } - - public int getNumberOfThreads() { - return (numberOfThreads == null ? 1 : numberOfThreads <= 0 ? 1 : numberOfThreads); - } - - public Policy getPolicy() { - return (policy == null ? Policy.STOPATFIRSTERROR : policy); - } - - public boolean isExecution() { - return execution == null || Boolean.TRUE.equals(execution); - } - - /** - * Decide what to do when an error is find: stop or continue? - * default is STOPATFIRSTERROR - */ - public enum Policy {STOPATFIRSTERROR, CONTINUE} + + public static ScenarioExecution createExecution(Scenario scnHead) { + return new ScenarioExecution(scnHead); + } + + + + /* ******************************************************************** */ + /* */ + /* Creator and setter to help the API */ + /* */ + /* ******************************************************************** */ + + /** + * After UnSerialize, all link to parent are not restored + * + * @param scnHead head of scenario + */ + public void afterUnSerialize(Scenario scnHead) { + this.scnHead = scnHead; + for (ScenarioStep scnStep : steps) { + scnStep.afterUnSerialize(this); + } + } + + /** + * Add a step in the scenario + * + * @param step step part of the scenario + * @return this object + */ + public ScenarioExecution addStep(ScenarioStep step) { + steps.add(step); + return this; + } + + public List<ScenarioStep> getSteps() { + return steps == null ? Collections.emptyList() : steps; + } + + public ScenarioVerification getVerifications() { + return verifications; + } + + + + /* ******************************************************************** */ + /* */ + /* getter */ + /* */ + /* ******************************************************************** */ + + public int getNumberProcessInstances() { + return numberProcessInstances == null ? 1 : numberProcessInstances; + } + + /** + * Ask this execution to execute a number of process instance. + * + * @param numberProcessInstances number of process instance to execute + * @return this object + */ + public ScenarioExecution setNumberProcessInstances(int numberProcessInstances) { + this.numberProcessInstances = numberProcessInstances; + return this; + } + + public Scenario getScnHead() { + return scnHead; + } + + public String getName() { + return name; + } + + public ScenarioExecution setName(String name) { + this.name = name; + return this; + } + + public int getNumberOfThreads() { + return (numberOfThreads == null ? 1 : numberOfThreads <= 0 ? 1 : numberOfThreads); + } + + public void setNumberOfThreads(Integer numberOfThreads) { + this.numberOfThreads = numberOfThreads; + } + + public Policy getPolicy() { + return (policy == null ? Policy.STOPATFIRSTERROR : policy); + } + + public boolean isExecution() { + return execution == null || Boolean.TRUE.equals(execution); + } + + /** + * Decide what to do when an error is find: stop or continue? + * default is STOPATFIRSTERROR + */ + public enum Policy {STOPATFIRSTERROR, CONTINUE} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java b/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java index b413e9d..ad5eebc 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioFlowControl.java @@ -11,49 +11,49 @@ import java.util.List; public class ScenarioFlowControl { - private String duration; + private String duration; - private Integer increaseStep; - private List<Objective> objectives; + private Integer increaseStep; + private List<Objective> objectives; - public Duration getDuration() { - try { - return Duration.parse(duration); - } catch (Exception e) { - return Duration.ofMinutes(10); + public Duration getDuration() { + try { + return Duration.parse(duration); + } catch (Exception e) { + return Duration.ofMinutes(10); + } } - } - - public Integer getIncreaseStep() { - return increaseStep; - } - - public List<Objective> getObjectives() { - return objectives == null ? Collections.emptyList() : objectives; - } - - public static class Objective { - public int index; - public String name; - public String label; - public TYPEOBJECTIVE type; - public String processId; - public String taskId; - public String period; - public Integer value; - public Integer standardDeviation; - - public int getStandardDeviation() { - return standardDeviation == null ? 0 : standardDeviation; + + public Integer getIncreaseStep() { + return increaseStep; } - public String getInformation() { - String information = (name == null ? "" : name + "-") + (label == null ? "" : label); - if (information.length() > 0) - return information; - return (type == null ? "NoType" : type.toString()) + " period[" + period + "] value:[" + value + "]"; + public List<Objective> getObjectives() { + return objectives == null ? Collections.emptyList() : objectives; } - public enum TYPEOBJECTIVE {CREATED, ENDED, USERTASK, FLOWRATEUSERTASKMN} - } + public static class Objective { + public int index; + public String name; + public String label; + public TYPEOBJECTIVE type; + public String processId; + public String taskId; + public String period; + public Integer value; + public Integer standardDeviation; + + public int getStandardDeviation() { + return standardDeviation == null ? 0 : standardDeviation; + } + + public String getInformation() { + String information = (name == null ? "" : name + "-") + (label == null ? "" : label); + if (information.length() > 0) + return information; + return (type == null ? "NoType" : type.toString()) + " period[" + period + "] value:[" + value + "]"; + } + + public enum TYPEOBJECTIVE {CREATED, ENDED, USERTASK, FLOWRATEUSERTASKMN} + } } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioStep.java b/src/main/java/org/camunda/automator/definition/ScenarioStep.java index d451a6e..aec8558 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioStep.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioStep.java @@ -15,277 +15,276 @@ public class ScenarioStep { - /** - * each component contains an operations, to fulfill variables - * operations; stringtodate() - */ - private final Map<String, String> variablesOperation = Collections.emptyMap(); - private final Long fixedBackOffDelay = 0L; - private final MODEEXECUTION modeExecution = MODEEXECUTION.CLASSICAL; - private final Boolean streamEnabled = true; - /** - * Receive a step range in the scenario, which help to identify the step - */ - private final int stepNumber = -1; - /** - * In case of a Flow Step, the number of workers to execute this tasks - */ - private Integer nbThreads = Integer.valueOf(1); - - private final Integer nbTokens = null; - /** - * if the step is used in a WarmingUp operation, it can decide this is the time to finish it - * Expression is - * UserTaskThreashold(<taskId>,<numberOfTaskExpected>) - */ - private String endWarmingUp; - private ScenarioExecution scnExecution; - private Step type; - private String taskId; - /** - * Name is optional in the step, help to find it in case of error - */ - private String name; - /** - * to execute a service task in C8, topic is mandatory - */ - private String topic; - private Map<String, Object> variables = Collections.emptyMap(); - private String userId; - /** - * ISO 8601: PT10S - */ - private String delay; - /** - * ISO 8601: PT10S - */ - private String waitingTime; - /** - * Optional, may not be set - */ - private Integer numberOfExecutions; - /** - * In case of a Flow step, the frequency to execute this step, for example PT10S every 10 seconds - */ - private String frequency; - /** - * In case of FlowStep, the processId to execute the step - */ - private String processId; - - public ScenarioStep(ScenarioExecution scnExecution) { - this.scnExecution = scnExecution; - } - - public static ScenarioStep createStepCreate(ScenarioExecution scnExecution, String starterId) { - ScenarioStep scenarioStep = new ScenarioStep(scnExecution); - scenarioStep.type = Step.STARTEVENT; - scenarioStep.taskId = starterId; - return scenarioStep; - } - - /* ******************************************************************** */ - /* */ - /* Creator and setter to help the API */ - /* */ - /* ******************************************************************** */ - - public static ScenarioStep createStepUserTask(ScenarioExecution scnExecution, String activityId) { - ScenarioStep scenarioStep = new ScenarioStep(scnExecution); - scenarioStep.type = Step.USERTASK; - scenarioStep.taskId = activityId; - return scenarioStep; - } - - public String getInformation() { - return "step_" + stepNumber + " " // cartouche - + (name == null ? "" : ("[" + name + "]:")) // name - + getType().toString() // type - + ",taskId:[" + getTaskId() + "]" + (getTopic() == null ? "" : " topic:[" + getTopic() + "]"); - } - - public Step getType() { - return type; - } - - public ScenarioStep setType(Step type) { - this.type = type; - return this; - } - - public String getTaskId() { - return taskId; - } - - public ScenarioStep setTaskId(String taskId) { - this.taskId = taskId; - return this; - } - - public String getUserId() { - return userId; - } - - public ScenarioStep setUserId(String userId) { - this.userId = userId; - return this; - } - - public String getTopic() { - return topic; - } - - public boolean isStreamEnabled() { - return streamEnabled == null || streamEnabled.booleanValue(); - } - - /* ******************************************************************** */ - /* */ - /* getter */ - /* */ - /* ******************************************************************** */ - - public Map<String, String> getVariablesOperations() { - return variablesOperation == null ? Collections.emptyMap() : variablesOperation; - } - - public Map<String, Object> getVariables() { - return variables == null ? Collections.emptyMap() : variables; - } - - public ScenarioStep setVariables(Map<String, Object> variables) { - this.variables = variables; - return this; - } - - public String getDelay() { - return delay; - } - - public ScenarioStep setDelay(String delay) { - this.delay = delay; - return this; - } - - public String getWaitingTime() { - return waitingTime; - } - - public void setWaitingTime(String waitingTime) { - this.waitingTime = waitingTime; - } - - /** - * Return the waiting time in Duration - * - * @return Duration, defaultDuration if error - */ - public Duration getWaitingTimeDuration(Duration defaultDuration) { - try { - return Duration.parse(waitingTime); - } catch (Exception e) { - return defaultDuration; - } - } - - public ScenarioExecution getScnExecution() { - return scnExecution; - } - - public void setScnExecution(ScenarioExecution scnExecution) { - this.scnExecution = scnExecution; - } - - public int getNumberOfExecutions() { - return numberOfExecutions == null ? 1 : numberOfExecutions; - } - - public void setNumberOfExecutions(int numberOfExecutions) { - this.numberOfExecutions = numberOfExecutions; - } - - public String getFrequency() { - return frequency; - } - - public int getNbThreads() { - return nbThreads == null || nbThreads == 0 ? 1 : nbThreads; - } - - public void setNbThreads(int nbThreads) { - this.nbThreads = nbThreads; - } - - public int getNbTokens() { - return nbTokens == null ? 1 : nbTokens; - } - - public String getProcessId() { - return processId; - } - - public long getFixedBackOffDelay() { - return fixedBackOffDelay == null ? 0 : fixedBackOffDelay.longValue(); - } - - protected void afterUnSerialize(ScenarioExecution scnExecution) { - this.scnExecution = scnExecution; - } - - public void checkConsistence() throws AutomatorException { - if (getTaskId() == null || getTaskId().trim().isEmpty()) - throw new AutomatorException("Step taskId is mandatory"); - switch (type) { - case SERVICETASK -> { - if (getTopic() == null || getTopic().trim().isEmpty()) - throw new AutomatorException("Step.SERVICETASK: " + getTaskId() + " topic is mandatory"); - } - default -> { - } - } - } - - public String getEndWarmingUp() { - return endWarmingUp; - } - - public MODEEXECUTION getModeExecution() { - return modeExecution == null ? MODEEXECUTION.CLASSICAL : modeExecution; - } - - /** - * Return an uniq ID of the step (use to - * - * @return the id of the step - */ - public String getId() { - return getType() + " " + switch (getType()) { - case STARTEVENT -> getProcessId() + "(" + getTaskId() + ")"; - case SERVICETASK -> getTopic(); - - default -> ""; - }; - } - - /** - * MODE EXECUTION - * CLASSICAL, WAIT: the worker wait the waitingTime time - * THREAD, ASYNCHRONOUS: the worker release the method, wait asynchronously the waiting time and send back the answer - * THREADTOKEN, ASYNCHRONOUSLIMITED: same as THREAD, but use the maxClient information to not accept more than this number - * In ASYNCHRONOUS, the method can potentially have millions of works in parallel (it accept <NumberOfClients> works, - * but because it finishes the method, then Zeebe Client will accept more works. So, with a waiting time of 1 mn, it may have a lot - * of works in progress in the client. - * This mode limit the number of current execution on the worker. it redeems immediately the method, but when we reach this - * limitation, it froze the worker, waiting for a slot. - */ - public enum MODEEXECUTION {CLASSICAL, THREAD, THREADTOKEN, WAIT, ASYNCHRONOUS, ASYNCHRONOUSLIMITED} - - /* ******************************************************************** */ - /* */ - /* Check consistence */ - /* */ - /* ******************************************************************** */ - - public enum Step {STARTEVENT, USERTASK, SERVICETASK, MESSAGE, ENDEVENT, EXCLUSIVEGATEWAY, PARALLELGATEWAY} + /** + * each component contains an operations, to fulfill variables + * operations; stringtodate() + */ + private final Map<String, String> variablesOperation = Collections.emptyMap(); + private final Long fixedBackOffDelay = 0L; + private final MODEEXECUTION modeExecution = MODEEXECUTION.CLASSICAL; + private final Boolean streamEnabled = true; + /** + * Receive a step range in the scenario, which help to identify the step + */ + private final int stepNumber = -1; + private final Integer nbTokens = null; + /** + * In case of a Flow Step, the number of workers to execute this tasks + */ + private Integer nbThreads = Integer.valueOf(1); + /** + * if the step is used in a WarmingUp operation, it can decide this is the time to finish it + * Expression is + * UserTaskThreashold(<taskId>,<numberOfTaskExpected>) + */ + private String endWarmingUp; + private ScenarioExecution scnExecution; + private Step type; + private String taskId; + /** + * Name is optional in the step, help to find it in case of error + */ + private String name; + /** + * to execute a service task in C8, topic is mandatory + */ + private String topic; + private Map<String, Object> variables = Collections.emptyMap(); + private String userId; + /** + * ISO 8601: PT10S + */ + private String delay; + /** + * ISO 8601: PT10S + */ + private String waitingTime; + /** + * Optional, may not be set + */ + private Integer numberOfExecutions; + /** + * In case of a Flow step, the frequency to execute this step, for example PT10S every 10 seconds + */ + private String frequency; + /** + * In case of FlowStep, the processId to execute the step + */ + private String processId; + + public ScenarioStep(ScenarioExecution scnExecution) { + this.scnExecution = scnExecution; + } + + public static ScenarioStep createStepCreate(ScenarioExecution scnExecution, String starterId) { + ScenarioStep scenarioStep = new ScenarioStep(scnExecution); + scenarioStep.type = Step.STARTEVENT; + scenarioStep.taskId = starterId; + return scenarioStep; + } + + /* ******************************************************************** */ + /* */ + /* Creator and setter to help the API */ + /* */ + /* ******************************************************************** */ + + public static ScenarioStep createStepUserTask(ScenarioExecution scnExecution, String activityId) { + ScenarioStep scenarioStep = new ScenarioStep(scnExecution); + scenarioStep.type = Step.USERTASK; + scenarioStep.taskId = activityId; + return scenarioStep; + } + + public String getInformation() { + return "step_" + stepNumber + " " // cartouche + + (name == null ? "" : ("[" + name + "]:")) // name + + getType().toString() // type + + ",taskId:[" + getTaskId() + "]" + (getTopic() == null ? "" : " topic:[" + getTopic() + "]"); + } + + public Step getType() { + return type; + } + + public ScenarioStep setType(Step type) { + this.type = type; + return this; + } + + public String getTaskId() { + return taskId; + } + + public ScenarioStep setTaskId(String taskId) { + this.taskId = taskId; + return this; + } + + public String getUserId() { + return userId; + } + + public ScenarioStep setUserId(String userId) { + this.userId = userId; + return this; + } + + public String getTopic() { + return topic; + } + + public boolean isStreamEnabled() { + return streamEnabled == null || streamEnabled.booleanValue(); + } + + /* ******************************************************************** */ + /* */ + /* getter */ + /* */ + /* ******************************************************************** */ + + public Map<String, String> getVariablesOperations() { + return variablesOperation == null ? Collections.emptyMap() : variablesOperation; + } + + public Map<String, Object> getVariables() { + return variables == null ? Collections.emptyMap() : variables; + } + + public ScenarioStep setVariables(Map<String, Object> variables) { + this.variables = variables; + return this; + } + + public String getDelay() { + return delay; + } + + public ScenarioStep setDelay(String delay) { + this.delay = delay; + return this; + } + + public String getWaitingTime() { + return waitingTime; + } + + public void setWaitingTime(String waitingTime) { + this.waitingTime = waitingTime; + } + + /** + * Return the waiting time in Duration + * + * @return Duration, defaultDuration if error + */ + public Duration getWaitingTimeDuration(Duration defaultDuration) { + try { + return Duration.parse(waitingTime); + } catch (Exception e) { + return defaultDuration; + } + } + + public ScenarioExecution getScnExecution() { + return scnExecution; + } + + public void setScnExecution(ScenarioExecution scnExecution) { + this.scnExecution = scnExecution; + } + + public int getNumberOfExecutions() { + return numberOfExecutions == null ? 1 : numberOfExecutions; + } + + public void setNumberOfExecutions(int numberOfExecutions) { + this.numberOfExecutions = numberOfExecutions; + } + + public String getFrequency() { + return frequency; + } + + public int getNbThreads() { + return nbThreads == null || nbThreads == 0 ? 1 : nbThreads; + } + + public void setNbThreads(int nbThreads) { + this.nbThreads = nbThreads; + } + + public int getNbTokens() { + return nbTokens == null ? 1 : nbTokens; + } + + public String getProcessId() { + return processId; + } + + public long getFixedBackOffDelay() { + return fixedBackOffDelay == null ? 0 : fixedBackOffDelay.longValue(); + } + + protected void afterUnSerialize(ScenarioExecution scnExecution) { + this.scnExecution = scnExecution; + } + + public void checkConsistence() throws AutomatorException { + if (getTaskId() == null || getTaskId().trim().isEmpty()) + throw new AutomatorException("Step taskId is mandatory"); + switch (type) { + case SERVICETASK -> { + if (getTopic() == null || getTopic().trim().isEmpty()) + throw new AutomatorException("Step.SERVICETASK: " + getTaskId() + " topic is mandatory"); + } + default -> { + } + } + } + + public String getEndWarmingUp() { + return endWarmingUp; + } + + public MODEEXECUTION getModeExecution() { + return modeExecution == null ? MODEEXECUTION.CLASSICAL : modeExecution; + } + + /** + * Return an uniq ID of the step (use to + * + * @return the id of the step + */ + public String getId() { + return getType() + " " + switch (getType()) { + case STARTEVENT -> getProcessId() + "(" + getTaskId() + ")"; + case SERVICETASK -> getTopic(); + + default -> ""; + }; + } + + /** + * MODE EXECUTION + * CLASSICAL, WAIT: the worker wait the waitingTime time + * THREAD, ASYNCHRONOUS: the worker release the method, wait asynchronously the waiting time and send back the answer + * THREADTOKEN, ASYNCHRONOUSLIMITED: same as THREAD, but use the maxClient information to not accept more than this number + * In ASYNCHRONOUS, the method can potentially have millions of works in parallel (it accept <NumberOfClients> works, + * but because it finishes the method, then Zeebe Client will accept more works. So, with a waiting time of 1 mn, it may have a lot + * of works in progress in the client. + * This mode limit the number of current execution on the worker. it redeems immediately the method, but when we reach this + * limitation, it froze the worker, waiting for a slot. + */ + public enum MODEEXECUTION {CLASSICAL, THREAD, THREADTOKEN, WAIT, ASYNCHRONOUS, ASYNCHRONOUSLIMITED} + + /* ******************************************************************** */ + /* */ + /* Check consistence */ + /* */ + /* ******************************************************************** */ + + public enum Step {STARTEVENT, USERTASK, SERVICETASK, MESSAGE, ENDEVENT, EXCLUSIVEGATEWAY, PARALLELGATEWAY, TASK, SCRIPTTASK} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioTool.java b/src/main/java/org/camunda/automator/definition/ScenarioTool.java index cab9678..30871a8 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioTool.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioTool.java @@ -8,26 +8,26 @@ public class ScenarioTool { - public static File loadFile(String fileName, RunScenario runScenario) throws AutomatorException { - File file = new File(fileName); - if (file.exists()) - return file; - - // maybe the file is present under the scenario path? - if (runScenario.getScenario().getScenarioFile() != null) { - File pathScenario = runScenario.getScenario().getScenarioFile().getParentFile(); - file = new File(pathScenario + "/" + fileName); - if (file.exists()) - return file; - } - String currentPath = null; + public static File loadFile(String fileName, RunScenario runScenario) throws AutomatorException { + File file = new File(fileName); + if (file.exists()) + return file; + + // maybe the file is present under the scenario path? + if (runScenario.getScenario().getScenarioFile() != null) { + File pathScenario = runScenario.getScenario().getScenarioFile().getParentFile(); + file = new File(pathScenario + "/" + fileName); + if (file.exists()) + return file; + } + String currentPath = null; + + try { + currentPath = new File(".").getCanonicalPath(); + } catch (IOException e) { + } + + throw new AutomatorException("File [" + fileName + "] does not exist - current path =[" + currentPath + "]"); - try { - currentPath = new File(".").getCanonicalPath(); - } catch (IOException e) { } - - throw new AutomatorException("File [" + fileName + "] does not exist - current path =[" + currentPath + "]"); - - } } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerification.java b/src/main/java/org/camunda/automator/definition/ScenarioVerification.java index d650143..9b8a074 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerification.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerification.java @@ -6,49 +6,60 @@ import java.util.Map; public class ScenarioVerification { - private final ScenarioExecution scenarioExecution; - /** - * List of activities to check - * Maybe null due the Gson deserializer if there is no definition - */ - private List<ScenarioVerificationTask> activities = new ArrayList<>(); - /** - * List of Variables to check - * Maybe null due the Gson deserializer if there is no definition - */ - private List<ScenarioVerificationVariable> variables = new ArrayList<>(); - /** - * Variable to search the process instance, if only the verification is running - * Maybe null due the Gson deserializer if there is no definition - */ - private Map<String, Object> searchProcessInstanceByVariable; - - protected ScenarioVerification(ScenarioExecution scenarioExecution) { - this.scenarioExecution = scenarioExecution; - } - - public List<ScenarioVerificationTask> getActivities() { - return activities == null ? Collections.emptyList() : activities; - } - - public void setActivities(List<ScenarioVerificationTask> activities) { - this.activities = activities; - } - - public Map<String, Object> getSearchProcessInstanceByVariable() { - return searchProcessInstanceByVariable == null ? Collections.emptyMap() : searchProcessInstanceByVariable; - } - - public List<ScenarioVerificationVariable> getVariables() { - return variables == null ? Collections.emptyList() : variables; - } - - public void setVariables(List<ScenarioVerificationVariable> variables) { - this.variables = variables; - } - - public ScenarioExecution getScenarioExecution() { - return scenarioExecution; - } + private final ScenarioExecution scenarioExecution; + /** + * List of activities to check + * Maybe null due the Gson deserializer if there is no definition + */ + private List<ScenarioVerificationTask> activities = new ArrayList<>(); + /** + * List of Variables to check + * Maybe null due the Gson deserializer if there is no definition + */ + private List<ScenarioVerificationVariable> variables = new ArrayList<>(); + + /** + * List of duration to check + * Maybe null due the Gson deserializer if there is no definition + */ + private final List<ScenarioVerificationPerformance> performances = new ArrayList<>(); + + /** + * Variable to search the process instance, if only the verification is running + * Maybe null due the Gson deserializer if there is no definition + */ + private Map<String, Object> searchProcessInstanceByVariable; + + protected ScenarioVerification(ScenarioExecution scenarioExecution) { + this.scenarioExecution = scenarioExecution; + } + + public List<ScenarioVerificationTask> getActivities() { + return activities == null ? Collections.emptyList() : activities; + } + + public void setActivities(List<ScenarioVerificationTask> activities) { + this.activities = activities; + } + + public Map<String, Object> getSearchProcessInstanceByVariable() { + return searchProcessInstanceByVariable == null ? Collections.emptyMap() : searchProcessInstanceByVariable; + } + + public List<ScenarioVerificationVariable> getVariables() { + return variables == null ? Collections.emptyList() : variables; + } + + public void setVariables(List<ScenarioVerificationVariable> variables) { + this.variables = variables; + } + + public List<ScenarioVerificationPerformance> getPerformances() { + return performances == null ? Collections.emptyList() : performances; + } + + public ScenarioExecution getScenarioExecution() { + return scenarioExecution; + } } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerificationBasic.java b/src/main/java/org/camunda/automator/definition/ScenarioVerificationBasic.java index d5b3206..e84bf62 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerificationBasic.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerificationBasic.java @@ -2,5 +2,5 @@ public interface ScenarioVerificationBasic { - String getSynthesis(); + String getSynthesis(); } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerificationPerformance.java b/src/main/java/org/camunda/automator/definition/ScenarioVerificationPerformance.java new file mode 100644 index 0000000..328fbc3 --- /dev/null +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerificationPerformance.java @@ -0,0 +1,62 @@ +package org.camunda.automator.definition; + +import java.time.Duration; + +public class ScenarioVerificationPerformance implements ScenarioVerificationBasic { + public String fromFlowNode; + public String fromMarker; + public String toFlowNode; + public String toMarker; + public String duration; + + public void setFromFlowNode(String fromFlowNode) { + this.fromFlowNode = fromFlowNode; + } + + public void setToFlowNode(String toFlowNode) { + this.toFlowNode = toFlowNode; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public Marker getMarker(String marker) { + try { + if (marker == null) + return Marker.BEGIN; + return Marker.valueOf(marker); + } catch (Exception e) { + return Marker.BEGIN; + } + } + + public Marker getFromMarker() { + return getMarker(fromMarker); + } + + public void setFromMarker(String fromMarker) { + this.fromMarker = fromMarker; + } + + public Marker getToMarker() { + return getMarker(toMarker); + } + + public void setToMarker(String toMarker) { + this.toMarker = toMarker; + } + + public long getDurationInMs() { + return Duration.parse(duration).toMillis(); + } + + public String getSynthesis() { + return "PerformanceCheck [" + fromFlowNode + "] => [" + toFlowNode + "] in [" + duration + "]"; + } + + public enum Marker {BEGIN, END} + +} + + diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java b/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java index 39a9224..0e423d0 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerificationTask.java @@ -1,48 +1,48 @@ package org.camunda.automator.definition; public class ScenarioVerificationTask implements ScenarioVerificationBasic { - private final ScenarioVerification scenarioVerification; - public ScenarioStep.Step type; - public String taskId; - public Integer numberOfTasks; - public StepState state; + private final ScenarioVerification scenarioVerification; + public ScenarioStep.Step type; + public String taskId; + public Integer numberOfTasks; + public StepState state; - public ScenarioVerificationTask(ScenarioVerification scenarioVerification) { - this.scenarioVerification = scenarioVerification; - } + public ScenarioVerificationTask(ScenarioVerification scenarioVerification) { + this.scenarioVerification = scenarioVerification; + } - public ScenarioStep.Step getType() { - return type; - } + public ScenarioStep.Step getType() { + return type; + } - public void setType(ScenarioStep.Step type) { - this.type = type; - } + public void setType(ScenarioStep.Step type) { + this.type = type; + } - public String getTaskId() { - return taskId; - } + public String getTaskId() { + return taskId; + } - public void setTaskId(String taskId) { - this.taskId = taskId; - } + public void setTaskId(String taskId) { + this.taskId = taskId; + } - public int getNumberOfTasks() { - return numberOfTasks == null ? 1 : numberOfTasks; - } + public int getNumberOfTasks() { + return numberOfTasks == null ? 1 : numberOfTasks; + } - public void setNumberOfTasks(int numberOfTasks) { - this.numberOfTasks = numberOfTasks; - } + public void setNumberOfTasks(int numberOfTasks) { + this.numberOfTasks = numberOfTasks; + } - public ScenarioVerification getScenarioVerification() { - return scenarioVerification; - } + public ScenarioVerification getScenarioVerification() { + return scenarioVerification; + } - public String getSynthesis() { - return "ActivityCheck [" + taskId + "] " + state.toString(); - } + public String getSynthesis() { + return "ActivityCheck [" + taskId + "] state[" + (state == null ? "" : state.toString()) + "]"; + } - public enum StepState {COMPLETED, ACTIVE} + public enum StepState {COMPLETED, ACTIVE} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java b/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java index 9affe2a..d1e1a40 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerificationVariable.java @@ -1,11 +1,19 @@ package org.camunda.automator.definition; public class ScenarioVerificationVariable implements ScenarioVerificationBasic { - public String name; - public Object value; + public String name; + public Object value; - public String getSynthesis() { - return "VariableCheck [" + name + "]=[" + value + "]"; - } + public void setName(String name) { + this.name = name; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getSynthesis() { + return "VariableCheck [" + name + "]=[" + value + "]"; + } } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java b/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java index 554012e..d75fd89 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioWarmingUp.java @@ -13,22 +13,22 @@ public class ScenarioWarmingUp { - /** - * The warmingUp will take this duration maximum, except if during this time, all operations warmingUp declare the end - * (see ScenarioStep) - */ - public String duration; - public List<ScenarioStep> operations; - - public boolean useServiceTasks = false; - public boolean useUserTasks = false; - - public Duration getDuration() { - return duration == null ? Duration.ZERO : Duration.parse(duration); - } - - public List<ScenarioStep> getOperations() { - return operations == null ? Collections.emptyList() : operations; - } + /** + * The warmingUp will take this duration maximum, except if during this time, all operations warmingUp declare the end + * (see ScenarioStep) + */ + public String duration; + public List<ScenarioStep> operations; + + public boolean useServiceTasks = false; + public boolean useUserTasks = false; + + public Duration getDuration() { + return duration == null ? Duration.ZERO : Duration.parse(duration); + } + + public List<ScenarioStep> getOperations() { + return operations == null ? Collections.emptyList() : operations; + } } diff --git a/src/main/java/org/camunda/automator/engine/AutomatorException.java b/src/main/java/org/camunda/automator/engine/AutomatorException.java index f501fef..b042efc 100644 --- a/src/main/java/org/camunda/automator/engine/AutomatorException.java +++ b/src/main/java/org/camunda/automator/engine/AutomatorException.java @@ -3,28 +3,28 @@ import org.camunda.community.rest.client.invoker.ApiException; public class AutomatorException extends Exception { - public int code; - public String message; + public int code; + public String message; - public AutomatorException(int code, String message) { - this.code = code; - this.message = message; - } + public AutomatorException(int code, String message) { + this.code = code; + this.message = message; + } - public AutomatorException(String message) { - this.message = message; - } + public AutomatorException(String message) { + this.message = message; + } - public AutomatorException(String message, ApiException exception) { - this.code = exception.getCode(); - this.message = message + " : " + exception.getMessage() + " " + exception.getResponseBody(); - } + public AutomatorException(String message, ApiException exception) { + this.code = exception.getCode(); + this.message = message + " : " + exception.getMessage() + " " + exception.getResponseBody(); + } - public String getMessage() { - return message; - } + public String getMessage() { + return message; + } - public int getCode() { - return code; - } + public int getCode() { + return code; + } } diff --git a/src/main/java/org/camunda/automator/engine/RunParameters.java b/src/main/java/org/camunda/automator/engine/RunParameters.java index 4ce7b1b..d060ea2 100644 --- a/src/main/java/org/camunda/automator/engine/RunParameters.java +++ b/src/main/java/org/camunda/automator/engine/RunParameters.java @@ -4,247 +4,247 @@ import java.util.List; public class RunParameters { - private LOGLEVEL logLevel = LOGLEVEL.MONITORING; - - private String serverName; - - private int numberOfThreadsPerScenario = 10; - - /** - * Return the number of thread to use in the start event. Coming from the configuration. - * it may be n - */ - private Integer startEventNbThreads; - - /** - * Execute the scenario (execution part): create process instance, execute user & service task - */ - private boolean execution = false; - - /** - * On execution, it's possible to pilot each item, one by one - */ - private boolean creation = true; - private boolean servicetask = true; - private boolean usertask = true; - /** - * Verify the scenario (verification part) : check that tasks exist - */ - private boolean verification = false; - - /** - * After the execution, clean the processInstance - */ - private boolean clearAllAfter = false; - - /** - * Allow any deployment - */ - private boolean deploymentProcess = true; - - private boolean fullDetailsSynthesis = false; - private List<String> filterServiceTask = Collections.emptyList(); - - private boolean deepTracking = true; - /** - * Load the scenario path here. Some functions may be relative to this path - */ - private String scenarioPath; - - private boolean warmingUp = true; - - public RunParameters() { - } - - public LOGLEVEL getLogLevel() { - return logLevel; - } - - public RunParameters setLogLevel(LOGLEVEL logLevel) { - this.logLevel = logLevel; - return this; - } - - public String getServerName() { - return this.serverName; - } - - public RunParameters setServerName(String serverName) { - this.serverName = serverName; - return this; - } - - public boolean isExecution() { - return execution; - } - - public RunParameters setExecution(boolean execution) { - this.execution = execution; - return this; - } - - public boolean isCreation() { - return creation; - } - - public RunParameters setCreation(boolean creation) { - this.creation = creation; - return this; - } - - public boolean isServiceTask() { - return servicetask; - } - - public RunParameters setServiceTask(boolean servicetask) { - this.servicetask = servicetask; - return this; - } - - public boolean isUserTask() { - return usertask; - } - - public RunParameters setUserTask(boolean usertask) { - this.usertask = usertask; - return this; - } - - public boolean isVerification() { - return verification; - } - - public RunParameters setVerification(boolean verification) { - this.verification = verification; - return this; - } - - public boolean isClearAllAfter() { - return clearAllAfter; - } - - public RunParameters setClearAllAfter(boolean clearAllAfter) { - this.clearAllAfter = clearAllAfter; - return this; - } - - public boolean isDeploymentProcess() { - return deploymentProcess; - } - - public RunParameters setDeploymentProcess(boolean deploymentProcess) { - this.deploymentProcess = deploymentProcess; - return this; - } - - public boolean isFullDetailsSynthesis() { - return fullDetailsSynthesis; - } - - public RunParameters setFullDetailsSynthesis(boolean fullDetailsSynthesis) { - this.fullDetailsSynthesis = fullDetailsSynthesis; - return this; - } - - public List<String> getFilterServiceTask() { - return filterServiceTask; - } - - public RunParameters setFilterServiceTask(List<String> filterServiceTask) { - this.filterServiceTask = filterServiceTask; - return this; - } - - public boolean isDeepTracking() { - return deepTracking; - } - - public RunParameters setDeepTracking(boolean deepTracking) { - this.deepTracking = deepTracking; - return this; - } - - public String getScenarioPath() { - return scenarioPath; - } - - public RunParameters setScenarioPath(String scenarioPath) { - this.scenarioPath = scenarioPath; - return this; - } - - public boolean isWarmingUp() { - return warmingUp; - } - - public RunParameters setWarmingUp(boolean warmingUp) { - this.warmingUp = warmingUp; - return this; - } - - public int getNumberOfThreadsPerScenario() { - return (numberOfThreadsPerScenario <= 0 ? 1 : numberOfThreadsPerScenario); - } - - public RunParameters setNumberOfThreadsPerScenario(int numberOfThreadsPerScenario) { - this.numberOfThreadsPerScenario = numberOfThreadsPerScenario; - return this; - } - - /** - * return the nbThreads to use in a start event, comming from the configuration - * If the configuration does not specify anything, then return null. - * - * @return the number of thread to use for the start event - */ - public Integer getStartEventNbThreads() { - return startEventNbThreads; - } - - public void setStartEventNbThreads(Integer startEventNbThreads) { - this.startEventNbThreads = startEventNbThreads; - } - - public boolean showLevelDebug() { - return getLogLevelAsNumber() >= 5; - } - - public boolean showLevelInfo() { - return getLogLevelAsNumber() >= 4; - } - - public boolean showLevelMonitoring() { - return getLogLevelAsNumber() >= 3; - } - - public boolean showLevelDashboard() { - return getLogLevelAsNumber() >= 2; - } - - public void setFilterExecutionServiceTask(List<String> filterServiceTask) { - this.filterServiceTask = filterServiceTask; - } - - public boolean blockExecutionServiceTask(String topic) { - // no filter: execute everything - if (filterServiceTask.isEmpty()) - return false; - // filter in place: only if the topic is registered - return !filterServiceTask.contains(topic); - } - - private int getLogLevelAsNumber() { - return switch (logLevel) { - case NOTHING -> 0; - case MAIN -> 1; - case DASHBOARD -> 2; - case MONITORING -> 3; - case INFO -> 4; - case DEBUG -> 5; - default -> 0; - }; - } - - public enum LOGLEVEL {DEBUG, INFO, MONITORING, DASHBOARD, MAIN, NOTHING} + private LOGLEVEL logLevel = LOGLEVEL.MONITORING; + + private String serverName; + + private int numberOfThreadsPerScenario = 10; + + /** + * Return the number of thread to use in the start event. Coming from the configuration. + * it may be n + */ + private Integer startEventNbThreads; + + /** + * Execute the scenario (execution part): create process instance, execute user & service task + */ + private boolean execution = false; + + /** + * On execution, it's possible to pilot each item, one by one + */ + private boolean creation = true; + private boolean servicetask = true; + private boolean usertask = true; + /** + * Verify the scenario (verification part) : check that tasks exist + */ + private boolean verification = false; + + /** + * After the execution, clean the processInstance + */ + private boolean clearAllAfter = false; + + /** + * Allow any deployment + */ + private boolean deploymentProcess = true; + + private boolean fullDetailsSynthesis = false; + private List<String> filterServiceTask = Collections.emptyList(); + + private boolean deepTracking = true; + /** + * Load the scenario path here. Some functions may be relative to this path + */ + private String scenarioPath; + + private boolean warmingUp = true; + + public RunParameters() { + } + + public LOGLEVEL getLogLevel() { + return logLevel; + } + + public RunParameters setLogLevel(LOGLEVEL logLevel) { + this.logLevel = logLevel; + return this; + } + + public String getServerName() { + return this.serverName; + } + + public RunParameters setServerName(String serverName) { + this.serverName = serverName; + return this; + } + + public boolean isExecution() { + return execution; + } + + public RunParameters setExecution(boolean execution) { + this.execution = execution; + return this; + } + + public boolean isCreation() { + return creation; + } + + public RunParameters setCreation(boolean creation) { + this.creation = creation; + return this; + } + + public boolean isServiceTask() { + return servicetask; + } + + public RunParameters setServiceTask(boolean servicetask) { + this.servicetask = servicetask; + return this; + } + + public boolean isUserTask() { + return usertask; + } + + public RunParameters setUserTask(boolean usertask) { + this.usertask = usertask; + return this; + } + + public boolean isVerification() { + return verification; + } + + public RunParameters setVerification(boolean verification) { + this.verification = verification; + return this; + } + + public boolean isClearAllAfter() { + return clearAllAfter; + } + + public RunParameters setClearAllAfter(boolean clearAllAfter) { + this.clearAllAfter = clearAllAfter; + return this; + } + + public boolean isDeploymentProcess() { + return deploymentProcess; + } + + public RunParameters setDeploymentProcess(boolean deploymentProcess) { + this.deploymentProcess = deploymentProcess; + return this; + } + + public boolean isFullDetailsSynthesis() { + return fullDetailsSynthesis; + } + + public RunParameters setFullDetailsSynthesis(boolean fullDetailsSynthesis) { + this.fullDetailsSynthesis = fullDetailsSynthesis; + return this; + } + + public List<String> getFilterServiceTask() { + return filterServiceTask; + } + + public RunParameters setFilterServiceTask(List<String> filterServiceTask) { + this.filterServiceTask = filterServiceTask; + return this; + } + + public boolean isDeepTracking() { + return deepTracking; + } + + public RunParameters setDeepTracking(boolean deepTracking) { + this.deepTracking = deepTracking; + return this; + } + + public String getScenarioPath() { + return scenarioPath; + } + + public RunParameters setScenarioPath(String scenarioPath) { + this.scenarioPath = scenarioPath; + return this; + } + + public boolean isWarmingUp() { + return warmingUp; + } + + public RunParameters setWarmingUp(boolean warmingUp) { + this.warmingUp = warmingUp; + return this; + } + + public int getNumberOfThreadsPerScenario() { + return (numberOfThreadsPerScenario <= 0 ? 1 : numberOfThreadsPerScenario); + } + + public RunParameters setNumberOfThreadsPerScenario(int numberOfThreadsPerScenario) { + this.numberOfThreadsPerScenario = numberOfThreadsPerScenario; + return this; + } + + /** + * return the nbThreads to use in a start event, comming from the configuration + * If the configuration does not specify anything, then return null. + * + * @return the number of thread to use for the start event + */ + public Integer getStartEventNbThreads() { + return startEventNbThreads; + } + + public void setStartEventNbThreads(Integer startEventNbThreads) { + this.startEventNbThreads = startEventNbThreads; + } + + public boolean showLevelDebug() { + return getLogLevelAsNumber() >= 5; + } + + public boolean showLevelInfo() { + return getLogLevelAsNumber() >= 4; + } + + public boolean showLevelMonitoring() { + return getLogLevelAsNumber() >= 3; + } + + public boolean showLevelDashboard() { + return getLogLevelAsNumber() >= 2; + } + + public void setFilterExecutionServiceTask(List<String> filterServiceTask) { + this.filterServiceTask = filterServiceTask; + } + + public boolean blockExecutionServiceTask(String topic) { + // no filter: execute everything + if (filterServiceTask.isEmpty()) + return false; + // filter in place: only if the topic is registered + return !filterServiceTask.contains(topic); + } + + private int getLogLevelAsNumber() { + return switch (logLevel) { + case NOTHING -> 0; + case MAIN -> 1; + case DASHBOARD -> 2; + case MONITORING -> 3; + case INFO -> 4; + case DEBUG -> 5; + default -> 0; + }; + } + + public enum LOGLEVEL {DEBUG, INFO, MONITORING, DASHBOARD, MAIN, NOTHING} } diff --git a/src/main/java/org/camunda/automator/engine/RunResult.java b/src/main/java/org/camunda/automator/engine/RunResult.java index f78dfe8..1e44c8d 100644 --- a/src/main/java/org/camunda/automator/engine/RunResult.java +++ b/src/main/java/org/camunda/automator/engine/RunResult.java @@ -6,369 +6,395 @@ /* ******************************************************************** */ package org.camunda.automator.engine; +import org.camunda.automator.definition.ScenarioExecution; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.definition.ScenarioVerificationBasic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class RunResult { - /** - * Scenario attached to this execution - */ - private final RunScenario runScenario; - /** - * List of error. If empty, the scenario was executed with success - */ - private final List<ErrorDescription> listErrors = new ArrayList<>(); - private final List<StepExecution> listDetailsSteps = new ArrayList<>(); - private final List<VerificationStatus> listVerifications = new ArrayList<>(); - /** - * process instance started for this execution. The executionResult stand for only one process instance - */ - private final List<String> listProcessInstancesId = new ArrayList<>(); - private final List<String> listProcessIdDeployed = new ArrayList<>(); - /** - * Keep a photo of process instance created/failed per processid - */ - private final Map<String, RecordCreationPI> recordCreationPIMap = new HashMap<>(); - Logger logger = LoggerFactory.getLogger(RunResult.class); - private int numberOfSteps = 0; - private int numberOfErrorSteps = 0; - - /** - * Time to execute it - */ - private long timeExecution; - - private Date startDate; - private Date endDate; - - public RunResult(RunScenario runScenario) { - this.runScenario = runScenario; - - } - - public Date getStartDate() { - return this.startDate; - } - - public void setStartDate(Date date) { - this.startDate = date; - } - - public Date getEndDate() { - return this.endDate; - } - - public void setEndDate(Date date) { - this.endDate = date; - } - - /** - * Add the process instance - this is mandatory to - * - * @param processInstanceId processInstanceId to add - */ - public void addProcessInstanceId(String processId, String processInstanceId) { - this.listProcessInstancesId.add(processInstanceId); - - RecordCreationPI create = recordCreationPIMap.getOrDefault(processId, new RecordCreationPI(processId)); - create.nbCreated++; - recordCreationPIMap.put(processId, create); - } - - - /* ******************************************************************** */ - /* */ - /* method used during the execution to collect information */ - /* */ - /* ******************************************************************** */ - - /** - * large flow: just register the number of PI - */ - public void registerAddProcessInstance(String processId, boolean withSuccess) { - RecordCreationPI create = recordCreationPIMap.getOrDefault(processId, new RecordCreationPI(processId)); - if (withSuccess) - create.nbCreated++; - else - create.nbFailed++; - recordCreationPIMap.put(processId, create); - } - - public void addTimeExecution(long timeToAdd) { - this.timeExecution += timeToAdd; - } - - public void addStepExecution(ScenarioStep step, long timeExecution) { - addTimeExecution(timeExecution); - numberOfSteps++; - if (runScenario.getRunParameters().showLevelInfo()) { - StepExecution scenarioExecution = new StepExecution(this); - scenarioExecution.step = step; - listDetailsSteps.add(scenarioExecution); - } - } - - /** - * large flow: just register the number of execution - */ - public void registerAddStepExecution() { - numberOfSteps++; - } - - public void registerAddErrorStepExecution() { - numberOfErrorSteps++; - - } - - public List<ErrorDescription> getListErrors() { - return listErrors; - } - - /* ******************************************************************** */ - /* */ - /* Errors */ - /* */ - /* ******************************************************************** */ - - public void addError(ScenarioStep step, String explanation) { - this.listErrors.add(new ErrorDescription(step, explanation)); - logger.error((step == null ? "" : step.getType().toString()) + " " + explanation); - - } - - public void addError(ScenarioStep step, AutomatorException e) { - this.listErrors.add(new ErrorDescription(step, e.getMessage())); - } - - public void addVerification(ScenarioVerificationBasic verification, boolean isSuccess, String message) { - VerificationStatus verificationStatus = new VerificationStatus(); - verificationStatus.verification = verification; - verificationStatus.isSuccess = isSuccess; - verificationStatus.message = message; - this.listVerifications.add(verificationStatus); - } - - public boolean hasErrors() { - return !listErrors.isEmpty(); - } - - /* ******************************************************************** */ - /* */ - /* Verifications */ - /* */ - /* ******************************************************************** */ - - public List<VerificationStatus> getListVerifications() { - return listVerifications; - } - - /** - * Merge the result in this result - * - * @param result the result object - */ - public void add(RunResult result) { - addTimeExecution(result.getTimeExecution()); - listErrors.addAll(result.listErrors); - listVerifications.addAll(result.listVerifications); - for (Map.Entry<String, RecordCreationPI> entry : result.recordCreationPIMap.entrySet()) { - RecordCreationPI currentReference = recordCreationPIMap.getOrDefault(entry.getKey(), - new RecordCreationPI(entry.getKey())); - currentReference.nbFailed += entry.getValue().nbFailed; - currentReference.nbCreated += entry.getValue().nbCreated; - - recordCreationPIMap.put(entry.getKey(), currentReference); - } - numberOfSteps += result.numberOfSteps; - numberOfErrorSteps += result.numberOfErrorSteps; - // we collect the list only if the level is low - if (runScenario.getRunParameters() != null && runScenario.getRunParameters().showLevelInfo()) { - listDetailsSteps.addAll(result.listDetailsSteps); - listProcessInstancesId.addAll(result.listProcessInstancesId); - } - } - - /* ******************************************************************** */ - /* */ - /* merge */ - /* */ - /* ******************************************************************** */ - - public boolean isSuccess() { - long nbVerificationErrors = listVerifications.stream().filter(t -> !t.isSuccess).count(); - return listErrors.isEmpty() && nbVerificationErrors == 0; - } - - /* ******************************************************************** */ - /* */ - /* method to get information */ - /* */ - /* ******************************************************************** */ - - public String getFirstProcessInstanceId() { - return listProcessInstancesId.isEmpty() ? null : listProcessInstancesId.get(0); - } - - public List<String> getProcessInstanceId() { - return this.listProcessInstancesId; - } - - public long getTimeExecution() { - return timeExecution; - } - - public void setTimeExecution(long timeExecution) { - this.timeExecution = timeExecution; - } - - public List<String> getProcessIdDeployed() { - return listProcessIdDeployed; - } - - public void addDeploymentProcessId(String processId) { - this.listProcessIdDeployed.add(processId); - } - - public Map<String, RecordCreationPI> getRecordCreationPI() { - return recordCreationPIMap; - } - - public long getRecordCreationPIAllProcesses() { - long sum = 0; - for (RecordCreationPI value : recordCreationPIMap.values()) - sum += value.nbCreated; - return sum; - } - - public int getNumberOfSteps() { - return numberOfSteps; - } - - public int getNumberOfErrorSteps() { - return numberOfErrorSteps; - } - - /** - * @return a synthesis - */ - public String getSynthesis(boolean fullDetail) { - StringBuilder synthesis = new StringBuilder(); - synthesis.append((isSuccess() && !hasErrors()) ? "SUCCESS " : "FAIL "); - synthesis.append(runScenario.getScenario().getName()); - synthesis.append("("); - synthesis.append(runScenario.getScenario().getProcessId()); - synthesis.append("): "); - - StringBuilder append = synthesis.append(timeExecution); - synthesis.append(" timeExecution(ms), "); - RecordCreationPI recordCreationPI = recordCreationPIMap.get(runScenario.getScenario().getProcessId()); - synthesis.append(recordCreationPI == null ? 0 : recordCreationPI.nbCreated); - synthesis.append(" PICreated, "); - synthesis.append(recordCreationPI == null ? 0 : recordCreationPI.nbFailed); - synthesis.append(" PIFailed, "); - synthesis.append(numberOfSteps); - synthesis.append(" stepsExecuted, "); - synthesis.append(numberOfErrorSteps); - synthesis.append(" errorStepsExecuted, "); - - StringBuilder errorMessage = new StringBuilder(); - // add errors - errorMessage.append(listErrors.stream() // stream - .map(t -> { - return (t.step != null ? t.step.toString() : "") + t.explanation + "\n"; - }).collect(Collectors.joining(","))); + /** + * Scenario attached to this execution + */ + private final RunScenario runScenario; + + private final ScenarioExecution scnExecution; + + /** + * List of error. If empty, the scenario was executed with success + */ + private final List<ErrorDescription> listErrors = new ArrayList<>(); + private final List<StepExecution> listDetailsSteps = new ArrayList<>(); + private final List<VerificationStatus> listVerifications = new ArrayList<>(); + /** + * process instance started for this execution. The executionResult stand for only one process instance + */ + private final List<String> listProcessInstancesId = new ArrayList<>(); + private final List<String> listProcessIdDeployed = new ArrayList<>(); + /** + * Keep a photo of process instance created/failed per processid + */ + private final Map<String, RecordCreationPI> recordCreationPIMap = new HashMap<>(); + Logger logger = LoggerFactory.getLogger(RunResult.class); + private int numberOfSteps = 0; + private int numberOfErrorSteps = 0; + + /** + * Time to execute it + */ + private long timeExecution; + + private Date startDate; + private Date endDate; + private final List<RunResult> listRunResults = new ArrayList<>(); + + public RunResult(RunScenario runScenario) { + this.runScenario = runScenario; + this.scnExecution = null; + } + + public RunResult(RunScenario runScenario, ScenarioExecution scnExecution) { + this.runScenario = runScenario; + this.scnExecution = scnExecution; + + } + + public Date getStartDate() { + return this.startDate; + } + + public void setStartDate(Date date) { + this.startDate = date; + } + + public Date getEndDate() { + return this.endDate; + } + + public void setEndDate(Date date) { + this.endDate = date; + } + + + /* ******************************************************************** */ + /* */ + /* method used during the execution to collect information */ + /* */ + /* ******************************************************************** */ + + /** + * Add the process instance - this is mandatory to + * + * @param processInstanceId processInstanceId to add + */ + public void addProcessInstanceId(String processId, String processInstanceId) { + this.listProcessInstancesId.add(processInstanceId); + + RecordCreationPI create = recordCreationPIMap.getOrDefault(processId, new RecordCreationPI(processId)); + create.nbCreated++; + recordCreationPIMap.put(processId, create); + } - if (fullDetail) { - synthesis.append(errorMessage); + /** + * large flow: just register the number of PI + */ + public void registerAddProcessInstance(String processId, boolean withSuccess) { + RecordCreationPI create = recordCreationPIMap.getOrDefault(processId, new RecordCreationPI(processId)); + if (withSuccess) + create.nbCreated++; + else + create.nbFailed++; + recordCreationPIMap.put(processId, create); } - StringBuilder verificationMessage = new StringBuilder(); - verificationMessage.append(listVerifications.stream().map(t -> { - return t.verification.getSynthesis() + "? " + (t.isSuccess ? "OK" : "FAIL") + " " + t.message + "\n"; - }).collect(Collectors.joining(","))); - if (fullDetail) { - synthesis.append(verificationMessage); + + public void addTimeExecution(long timeToAdd) { + this.timeExecution += timeToAdd; } - // add full details - if (fullDetail) { - synthesis.append(" ListOfPICreated: "); - synthesis.append(listProcessInstancesId.stream() // stream - .collect(Collectors.joining(","))); + public void addStepExecution(ScenarioStep step, long timeExecution) { + addTimeExecution(timeExecution); + numberOfSteps++; + if (runScenario.getRunParameters().showLevelInfo()) { + StepExecution scenarioExecution = new StepExecution(this); + scenarioExecution.step = step; + listDetailsSteps.add(scenarioExecution); + } } - return synthesis.toString(); - } + /** + * large flow: just register the number of execution + */ + public void registerAddStepExecution() { + numberOfSteps++; + } - public static class StepExecution { - public final List<ErrorDescription> listErrors = new ArrayList<>(); - private final RunResult scenarioExecutionResult; - public ScenarioStep step; - public long timeExecution; + public void registerAddErrorStepExecution() { + numberOfErrorSteps++; - public StepExecution(RunResult scenarioExecutionResult) { - this.scenarioExecutionResult = scenarioExecutionResult; } - public void addError(ErrorDescription error) { - listErrors.add(error); + /* ******************************************************************** */ + /* */ + /* Errors */ + /* */ + /* ******************************************************************** */ + + public List<ErrorDescription> getListErrors() { + return listErrors; } - } - /* ******************************************************************** */ - /* */ - /* local class */ - /* */ - /* ******************************************************************** */ + public void addError(ScenarioStep step, String explanation) { + this.listErrors.add(new ErrorDescription(step, explanation)); + logger.error((step == null ? "" : step.getType().toString()) + " " + explanation); - public static class ErrorDescription { - public ScenarioStep step; - public ScenarioVerificationBasic verificationBasic; - public String explanation; + } - public ErrorDescription(ScenarioStep step, String explanation) { - this.step = step; - this.explanation = explanation; + public void addError(ScenarioStep step, AutomatorException e) { + this.listErrors.add(new ErrorDescription(step, e.getMessage())); } - public ErrorDescription(ScenarioVerificationBasic verificationBasic, String explanation) { - this.verificationBasic = verificationBasic; - this.explanation = explanation; + public void addVerification(ScenarioVerificationBasic verification, boolean isSuccess, String message) { + VerificationStatus verificationStatus = new VerificationStatus(); + verificationStatus.verification = verification; + verificationStatus.isSuccess = isSuccess; + verificationStatus.message = message; + this.listVerifications.add(verificationStatus); + } + + /* ******************************************************************** */ + /* */ + /* Verifications */ + /* */ + /* ******************************************************************** */ + + public boolean hasErrors() { + return !listErrors.isEmpty(); + } + + public List<VerificationStatus> getListVerifications() { + return listVerifications; + } + + /** + * Merge the result in this result + * + * @param result the result object + */ + public void merge(RunResult result) { + addTimeExecution(result.getTimeExecution()); + listErrors.addAll(result.listErrors); + listVerifications.addAll(result.listVerifications); + for (Map.Entry<String, RecordCreationPI> entry : result.recordCreationPIMap.entrySet()) { + RecordCreationPI currentReference = recordCreationPIMap.getOrDefault(entry.getKey(), + new RecordCreationPI(entry.getKey())); + currentReference.nbFailed += entry.getValue().nbFailed; + currentReference.nbCreated += entry.getValue().nbCreated; + + recordCreationPIMap.put(entry.getKey(), currentReference); + } + numberOfSteps += result.numberOfSteps; + numberOfErrorSteps += result.numberOfErrorSteps; + listRunResults.addAll(result.listRunResults); + // we collect the list only if the level is low + if (runScenario.getRunParameters() != null && runScenario.getRunParameters().showLevelInfo()) { + listDetailsSteps.addAll(result.listDetailsSteps); + listProcessInstancesId.addAll(result.listProcessInstancesId); + } + } + + public void add(RunResult runResult) { + // We keep track of the result in a list + listRunResults.add(runResult); + merge(runResult); + } + + /* ******************************************************************** */ + /* */ + /* merge */ + /* */ + /* ******************************************************************** */ + + public boolean isSuccess() { + long nbVerificationErrors = listVerifications.stream().filter(t -> !t.isSuccess).count(); + return listErrors.isEmpty() && nbVerificationErrors == 0; } - } - public static class RecordCreationPI { - public String processId; - public long nbCreated = 0; - public long nbFailed = 0; + /* ******************************************************************** */ + /* */ + /* method to get information */ + /* */ + /* ******************************************************************** */ - public RecordCreationPI(String processId) { - this.processId = processId; + public String getFirstProcessInstanceId() { + return listProcessInstancesId.isEmpty() ? null : listProcessInstancesId.get(0); } - public void add(RecordCreationPI record) { - if (record == null) - return; - nbCreated += record.nbCreated; - nbFailed += record.nbFailed; + public List<String> getProcessInstanceId() { + return this.listProcessInstancesId; } - public String toString() { - return "Created[" + nbCreated + "] Failed[" + nbFailed + "]"; + public long getTimeExecution() { + return timeExecution; } - } - public class VerificationStatus { - public ScenarioVerificationBasic verification; - public boolean isSuccess; - public String message; - } + public void setTimeExecution(long timeExecution) { + this.timeExecution = timeExecution; + } + + public List<String> getProcessIdDeployed() { + return listProcessIdDeployed; + } + + public void addDeploymentProcessId(String processId) { + this.listProcessIdDeployed.add(processId); + } + + public Map<String, RecordCreationPI> getRecordCreationPI() { + return recordCreationPIMap; + } + + public long getRecordCreationPIAllProcesses() { + long sum = 0; + for (RecordCreationPI value : recordCreationPIMap.values()) + sum += value.nbCreated; + return sum; + } + + public int getNumberOfSteps() { + return numberOfSteps; + } + + public int getNumberOfErrorSteps() { + return numberOfErrorSteps; + } + + public List<RunResult> getListRunResults() { + return listRunResults; + } + + public RunScenario getRunScenario() { + return runScenario; + } + + public ScenarioExecution getScnExecution() { + return scnExecution; + } + + /** + * @return a synthesis + */ + public String getSynthesis(boolean fullDetail) { + StringBuilder synthesis = new StringBuilder(); + synthesis.append((isSuccess() && !hasErrors()) ? "SUCCESS " : "FAIL "); + synthesis.append(runScenario.getScenario().getName()); + synthesis.append("("); + synthesis.append(runScenario.getScenario().getProcessId()); + synthesis.append("): "); + + StringBuilder append = synthesis.append(timeExecution); + synthesis.append(" timeExecution(ms), "); + RecordCreationPI recordCreationPI = recordCreationPIMap.get(runScenario.getScenario().getProcessId()); + synthesis.append(recordCreationPI == null ? 0 : recordCreationPI.nbCreated); + synthesis.append(" PICreated, "); + synthesis.append(recordCreationPI == null ? 0 : recordCreationPI.nbFailed); + synthesis.append(" PIFailed, "); + synthesis.append(numberOfSteps); + synthesis.append(" stepsExecuted, "); + synthesis.append(numberOfErrorSteps); + synthesis.append(" errorStepsExecuted, "); + + StringBuilder errorMessage = new StringBuilder(); + // add errors + errorMessage.append(listErrors.stream() // stream + .map(t -> { + return (t.step != null ? t.step.toString() : "") + t.explanation + "\n"; + }).collect(Collectors.joining(","))); + + if (fullDetail) { + synthesis.append(errorMessage); + } + StringBuilder verificationMessage = new StringBuilder(); + verificationMessage.append(listVerifications.stream().map(t -> { + return t.verification.getSynthesis() + "? " + (t.isSuccess ? "OK" : "FAIL") + " " + t.message + "\n"; + }).collect(Collectors.joining(","))); + if (fullDetail) { + synthesis.append(verificationMessage); + } + // add full details + if (fullDetail) { + synthesis.append(" ListOfPICreated: "); + + synthesis.append(listProcessInstancesId.stream() // stream + .collect(Collectors.joining(","))); + } + return synthesis.toString(); + + } + + public static class StepExecution { + public final List<ErrorDescription> listErrors = new ArrayList<>(); + private final RunResult scenarioExecutionResult; + public ScenarioStep step; + public long timeExecution; + + public StepExecution(RunResult scenarioExecutionResult) { + this.scenarioExecutionResult = scenarioExecutionResult; + } + + public void addError(ErrorDescription error) { + listErrors.add(error); + } + } + + /* ******************************************************************** */ + /* */ + /* local class */ + /* */ + /* ******************************************************************** */ + + public static class ErrorDescription { + public ScenarioStep step; + public ScenarioVerificationBasic verificationBasic; + public String explanation; + + public ErrorDescription(ScenarioStep step, String explanation) { + this.step = step; + this.explanation = explanation; + } + + public ErrorDescription(ScenarioVerificationBasic verificationBasic, String explanation) { + this.verificationBasic = verificationBasic; + this.explanation = explanation; + } + } + + public static class RecordCreationPI { + public String processId; + public long nbCreated = 0; + public long nbFailed = 0; + + public RecordCreationPI(String processId) { + this.processId = processId; + } + + public void add(RecordCreationPI record) { + if (record == null) + return; + nbCreated += record.nbCreated; + nbFailed += record.nbFailed; + } + + public String toString() { + return "Created[" + nbCreated + "] Failed[" + nbFailed + "]"; + } + } + + public class VerificationStatus { + public ScenarioVerificationBasic verification; + public boolean isSuccess; + public String message; + } } diff --git a/src/main/java/org/camunda/automator/engine/RunScenario.java b/src/main/java/org/camunda/automator/engine/RunScenario.java index d7a88c6..8ca09b5 100644 --- a/src/main/java/org/camunda/automator/engine/RunScenario.java +++ b/src/main/java/org/camunda/automator/engine/RunScenario.java @@ -17,11 +17,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.concurrent.*; /** * This object executes a scenario, in a context. Context is @@ -30,234 +26,249 @@ * - the RunParameters */ public class RunScenario { - private final Scenario scenario; - private final BpmnEngine bpmnEngine; - private final RunParameters runParameters; - private final ServiceAccess serviceAccess; - Logger logger = LoggerFactory.getLogger(RunScenario.class); - - /** - * @param scenario scenario to be executed - * @param bpmnEngine engine to connect - * @param runParameters different parameters to run the scenario - * @param serviceAccess service access to access all services, this object is created per execution - */ - public RunScenario(Scenario scenario, - BpmnEngine bpmnEngine, - RunParameters runParameters, - ServiceAccess serviceAccess) { - this.scenario = scenario; - this.bpmnEngine = bpmnEngine; - this.runParameters = runParameters; - this.serviceAccess = serviceAccess; - } - - /** - * Execute the scenario. - * A scenario is composed of - * - deployment - * - execution (which contains the verifications) - * <p> - * these steps are controlled by the runParameters - * - * @return tue result object - */ - public RunResult runScenario() { - RunResult result = new RunResult(this); - - // control - if (scenario.typeScenario == null) { - result.addError(null, "TypeScenario undefined"); - } - if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.UNIT)) { - if (scenario.getExecutions() == null || scenario.getExecutions().isEmpty()) - result.addError(null, "TypeScenario[" + Scenario.TYPESCENARIO.UNIT + "] must have a list of [executions]"); - } else if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.FLOW)) { - if (scenario.getFlowControl() == null) - result.addError(null, "TypeScenario[" + Scenario.TYPESCENARIO.FLOW + "] must have a list of [flowControl]"); - if (scenario.getFlows() == null || scenario.getFlows().isEmpty()) - result.addError(null, "TypeScenario[" + Scenario.TYPESCENARIO.FLOW + "] must have a list of [flows]"); + private final Scenario scenario; + private final BpmnEngine bpmnEngine; + private final RunParameters runParameters; + private final ServiceAccess serviceAccess; + Logger logger = LoggerFactory.getLogger(RunScenario.class); + + /** + * @param scenario scenario to be executed + * @param bpmnEngine engine to connect + * @param runParameters different parameters to run the scenario + * @param serviceAccess service access to access all services, this object is created per execution + */ + public RunScenario(Scenario scenario, + BpmnEngine bpmnEngine, + RunParameters runParameters, + ServiceAccess serviceAccess) { + this.scenario = scenario; + this.bpmnEngine = bpmnEngine; + this.runParameters = runParameters; + this.serviceAccess = serviceAccess; } - if (result.hasErrors()) - return result; - - logger.info("RunScenario: ------ Deployment ({})", runParameters.isDeploymentProcess()); - if (runParameters.isDeploymentProcess()) - result.add(runDeployment()); - logger.info("RunScenario: ------ End deployment "); - - // verification is inside execution - result.add(runExecutions()); - return result; - } - - /** - * run only the deployments on the process - test to verify the engine is performed - * - * @return result of deployment - */ - public RunResult runDeployment() { - RunResult result = new RunResult(this); - - // first, do we have to deploy something? - if (scenario.getDeployments() != null) { - for (ScenarioDeployment deployment : scenario.getDeployments()) { - - boolean sameTypeServer = false; - if (deployment.serverType.equals(BpmnEngineList.CamundaEngine.CAMUNDA_7)) { - sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineList.CamundaEngine.CAMUNDA_7); - } else if (deployment.serverType.equals(BpmnEngineList.CamundaEngine.CAMUNDA_8)) { - sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineList.CamundaEngine.CAMUNDA_8) - || bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS); - } - if (sameTypeServer) { - try { - long begin = System.currentTimeMillis(); - File processFile = ScenarioTool.loadFile(deployment.processFile, this); - logger.info("Deploy process[{}] on {}", processFile.getName(), bpmnEngine.getSignature()); - result.addDeploymentProcessId(bpmnEngine.deployBpmn(processFile, deployment.policy)); - result.addTimeExecution(System.currentTimeMillis() - begin); - } catch (AutomatorException e) { - result.addError(null, "Can't deploy process [" + deployment.processFile + "] " + e.getMessage()); - } - } else { - logger.info("RunScenario: can't Deploy ({}), not the same server", deployment.processFile); + /** + * Execute the scenario. + * A scenario is composed of + * - deployment + * - execution (which contains the verifications) + * <p> + * these steps are controlled by the runParameters + * + * @return tue result object + */ + public RunResult runScenario() { + RunResult result = new RunResult(this); + + // control + if (scenario.typeScenario == null) { + result.addError(null, "TypeScenario undefined"); } - } - } - return result; - } - - /** - * Execute the scenario. - * Note: this method is multi thread safe. - * Note: if the execution has verification AND runParameters.execution == true, then the verification is started - * - * @return the execution - */ - public RunResult runExecutions() { - RunResult result = new RunResult(this); - result.setStartDate(new Date()); - - // the scenario can be an Execution or a Flow - if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.UNIT)) { - // each execution is run in a different thread - ExecutorService executor = Executors.newFixedThreadPool(runParameters.getNumberOfThreadsPerScenario()); - - List<Future<?>> listFutures = new ArrayList<>(); - logger.info("RunScenario: ------ execution UNIT scenario [{}] {} execution on {} Threads", scenario.getName(), - scenario.getExecutions().size(), runParameters.getNumberOfThreadsPerScenario()); - - for (int i = 0; i < scenario.getExecutions().size(); i++) { - ScenarioExecution scnExecution = scenario.getExecutions().get(i); - ScnExecutionCallable scnExecutionCallable = new ScnExecutionCallable("Agent-" + i, this, scnExecution, - runParameters); - - listFutures.add(executor.submit(scnExecutionCallable)); - } - - // wait the end of all executions - try { - for (Future<?> f : listFutures) { - Object scnRunResult = f.get(); - result.add((RunResult) scnRunResult); + // ------------ unit scenario + if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.UNIT)) { + if (scenario.getExecutions() == null || scenario.getExecutions().isEmpty()) { + result.addError(null, "TypeScenario[" + Scenario.TYPESCENARIO.UNIT + "] must have a list of [executions]"); + return result; + } + // force information in execution + for (ScenarioExecution execution : scenario.getExecutions()) { + execution.setNumberProcessInstances(1); + execution.setNumberOfThreads(1); + } + // Verification must be move to true + runParameters.setVerification(true); + + + // ------------- flow scenario + } else if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.FLOW)) { + if (scenario.getFlowControl() == null) + result.addError(null, "TypeScenario[" + Scenario.TYPESCENARIO.FLOW + "] must have a list of [flowControl]"); + if (scenario.getFlows() == null || scenario.getFlows().isEmpty()) + result.addError(null, "TypeScenario[" + Scenario.TYPESCENARIO.FLOW + "] must have a list of [flows]"); } + if (result.hasErrors()) + return result; - } catch (ExecutionException ee) { - result.addError(null, "Error during executing in parallel " + ee.getMessage()); + logger.info("RunScenario: ------ Deployment ({})", runParameters.isDeploymentProcess()); + if (runParameters.isDeploymentProcess()) + result.merge(runDeployment()); + logger.info("RunScenario: ------ End deployment "); - } catch (Exception e) { - result.addError(null, "Error during executing in parallel " + e.getMessage()); - } - logger.info("RunScenario: ------ End execution"); + // verification is inside execution + result.merge(runExecutions()); + return result; } - if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.FLOW)) { - logger.info("RunScenario: ------ execution FLOW scenario [{}]", scenario.getName()); - RunScenarioFlows scenarioFlows = new RunScenarioFlows(serviceAccess, this); - scenarioFlows.execute(result); - logger.info("RunScenario: ------ End execution"); + + /** + * run only the deployments on the process - test to verify the engine is performed + * + * @return result of deployment + */ + public RunResult runDeployment() { + RunResult result = new RunResult(this); + + // first, do we have to deploy something? + if (scenario.getDeployments() != null) { + for (ScenarioDeployment deployment : scenario.getDeployments()) { + + boolean sameTypeServer = false; + if (deployment.serverType.equals(BpmnEngineList.CamundaEngine.CAMUNDA_7)) { + sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineList.CamundaEngine.CAMUNDA_7); + } else if (deployment.serverType.equals(BpmnEngineList.CamundaEngine.CAMUNDA_8)) { + sameTypeServer = bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineList.CamundaEngine.CAMUNDA_8) + || bpmnEngine.getTypeCamundaEngine().equals(BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS); + } + if (sameTypeServer) { + try { + long begin = System.currentTimeMillis(); + File processFile = ScenarioTool.loadFile(deployment.processFile, this); + logger.info("Deploy process[{}] on {}", processFile.getName(), bpmnEngine.getSignature()); + result.addDeploymentProcessId(bpmnEngine.deployBpmn(processFile, deployment.policy)); + result.addTimeExecution(System.currentTimeMillis() - begin); + } catch (AutomatorException e) { + result.addError(null, "Can't deploy process [" + deployment.processFile + "] " + e.getMessage()); + } + } else { + logger.info("RunScenario: can't Deploy ({}), not the same server", deployment.processFile); + + } + } + } + return result; } - return result; - } + /** + * Execute the scenario. + * Note: this method is multi thread safe. + * Note: if the execution has verification AND runParameters.execution == true, then the verification is started + * + * @return the execution + */ + public RunResult runExecutions() { + RunResult result = new RunResult(this); + result.setStartDate(new Date()); + + // the scenario can be an Execution or a Flow + if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.UNIT)) { + + // each execution is run in a different thread + ExecutorService executor = Executors.newFixedThreadPool(scenario.getExecutions().size()); + + List<Future<?>> listFutures = new ArrayList<>(); + logger.info("RunScenario: ------ execution UNIT scenario [{}] {} execution on {} Threads", scenario.getName(), + scenario.getExecutions().size(), runParameters.getNumberOfThreadsPerScenario()); + + for (int i = 0; i < scenario.getExecutions().size(); i++) { + ScenarioExecution scnExecution = scenario.getExecutions().get(i); + ScnExecutionCallable scnExecutionCallable = new ScnExecutionCallable("Agent-" + i, this, scnExecution, + runParameters); + + listFutures.add(executor.submit(scnExecutionCallable)); + } + + // wait the end of all executions + try { + for (Future<?> f : listFutures) { + Object scnRunResult = f.get(); + // We want to keep separate all results, in case of a Unit Test + result.add((RunResult) scnRunResult); + } + + } catch (ExecutionException ee) { + result.addError(null, "Error during executing in parallel " + ee.getMessage()); + + } catch (Exception e) { + result.addError(null, "Error during executing in parallel " + e.getMessage()); + } + logger.info("RunScenario: ------ End execution"); + } + if (scenario.typeScenario.equals(Scenario.TYPESCENARIO.FLOW)) { + logger.info("RunScenario: ------ execution FLOW scenario [{}]", scenario.getName()); + RunScenarioFlows scenarioFlows = new RunScenarioFlows(serviceAccess, this); + scenarioFlows.execute(result); + logger.info("RunScenario: ------ End execution"); + } - /** - * for one execution, run verifications - * - * @param scnExecution execution to check - * @return result of execution - */ - public RunResult runVerifications(ScenarioExecution scnExecution) { - RunResult result = new RunResult(this); + return result; + } - RunScenarioVerification verifications = new RunScenarioVerification(scnExecution); - result.add(verifications.runVerifications(this, result.getFirstProcessInstanceId())); - return result; + /** + * for one execution, run verifications + * + * @param scnExecution execution to check + * @return result of execution + */ + public RunResult runVerifications(ScenarioExecution scnExecution) { + RunResult result = new RunResult(this); - } + RunScenarioVerification verifications = new RunScenarioVerification(scnExecution); + result.merge(verifications.runVerifications(this, result.getFirstProcessInstanceId())); + return result; - public Scenario getScenario() { - return scenario; - } + } - public BpmnEngine getBpmnEngine() { - return bpmnEngine; - } + public Scenario getScenario() { + return scenario; + } - public RunParameters getRunParameters() { - return runParameters; - } + public BpmnEngine getBpmnEngine() { + return bpmnEngine; + } - public ServiceAccess getServiceAccess() { - return serviceAccess; - } + public RunParameters getRunParameters() { + return runParameters; + } + public ServiceAccess getServiceAccess() { + return serviceAccess; + } - /* ******************************************************************** */ - /* */ - /* Callable class */ - /* */ - /* Each execution are executed in different thread */ - /* ******************************************************************** */ - private static class ScnExecutionCallable implements Callable { - private final String agentName; - private final ScenarioExecution scnExecution; - private final RunScenario runScenario; - private final RunParameters runParameters; + /* ******************************************************************** */ + /* */ + /* Callable class */ + /* */ + /* Each execution are executed in different thread */ + /* ******************************************************************** */ - private RunResult scnRunResult; + private static class ScnExecutionCallable implements Callable { + private final String agentName; + private final ScenarioExecution scnExecution; + private final RunScenario runScenario; + private final RunParameters runParameters; - ScnExecutionCallable(String agentName, - RunScenario runScenario, - ScenarioExecution scnExecution, - RunParameters runParameters) { - this.agentName = agentName; - this.runScenario = runScenario; - this.scnExecution = scnExecution; - this.runParameters = runParameters; - } + private RunResult scnRunResult; - @Override - public Object call() { - RunScenarioUnit scnRunExecution = new RunScenarioUnit(runScenario, scnExecution); - scnRunExecution.setAgentName(agentName); + ScnExecutionCallable(String agentName, + RunScenario runScenario, + ScenarioExecution scnExecution, + RunParameters runParameters) { + this.agentName = agentName; + this.runScenario = runScenario; + this.scnExecution = scnExecution; + this.runParameters = runParameters; + } - /** - * Execution AND verifications are processed - * An execution may be MULTIPLE process instance, and each must be verified - */ - scnRunResult = scnRunExecution.runExecution(); + @Override + public Object call() { + RunScenarioUnit scnRunExecution = new RunScenarioUnit(runScenario, scnExecution); + scnRunExecution.setAgentName(agentName); - return scnRunResult; - } + /** + * Execution AND verifications are processed + * An execution may be MULTIPLE process instance, and each must be verified + */ + scnRunResult = scnRunExecution.runExecution(); - public RunResult getScnRunResult() { - return scnRunResult; + return scnRunResult; + } + + public RunResult getScnRunResult() { + return scnRunResult; + } } - } } diff --git a/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java b/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java index c76088e..962f4b1 100644 --- a/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java +++ b/src/main/java/org/camunda/automator/engine/RunZeebeOperation.java @@ -14,32 +14,32 @@ import java.util.Map; public class RunZeebeOperation { - private static final Logger logger = LoggerFactory.getLogger(RunZeebeOperation.class); + private static final Logger logger = LoggerFactory.getLogger(RunZeebeOperation.class); - // Static method only - private RunZeebeOperation() { - } + // Static method only + private RunZeebeOperation() { + } - /** - * Resolve variables - */ - public static Map<String, Object> getVariablesStep(RunScenario runScenario, ScenarioStep step, int index) - throws AutomatorException { - Map<String, Object> variablesCompleted = new HashMap<>(); - variablesCompleted.putAll(step.getVariables()); + /** + * Resolve variables + */ + public static Map<String, Object> getVariablesStep(RunScenario runScenario, ScenarioStep step, int index) + throws AutomatorException { + Map<String, Object> variablesCompleted = new HashMap<>(); + variablesCompleted.putAll(step.getVariables()); - // execute all operations now - for (Map.Entry<String, String> entryOperation : step.getVariablesOperations().entrySet()) { - if (runScenario.getRunParameters().showLevelDebug()) - logger.info("Scenario Key[{}] Value[{}] Step {}", entryOperation.getKey(), entryOperation.getValue(), - step.getInformation()); - variablesCompleted.put(entryOperation.getKey(), - runScenario.getServiceAccess().serviceDataOperation.execute(entryOperation.getValue(), runScenario, - "Step " + step.getInformation(), index)); - } - if (runScenario.getRunParameters().showLevelDebug() && !variablesCompleted.isEmpty()) - logger.info("SetVariable [{}] {}", step.getVariables(), step.getInformation()); + // execute all operations now + for (Map.Entry<String, String> entryOperation : step.getVariablesOperations().entrySet()) { + if (runScenario.getRunParameters().showLevelDebug()) + logger.info("Scenario Key[{}] Value[{}] Step {}", entryOperation.getKey(), entryOperation.getValue(), + step.getInformation()); + variablesCompleted.put(entryOperation.getKey(), + runScenario.getServiceAccess().serviceDataOperation.execute(entryOperation.getValue(), runScenario, + "Step " + step.getInformation(), index)); + } + if (runScenario.getRunParameters().showLevelDebug() && !variablesCompleted.isEmpty()) + logger.info("SetVariable [{}] {}", step.getVariables(), step.getInformation()); - return variablesCompleted; - } + return variablesCompleted; + } } diff --git a/src/main/java/org/camunda/automator/engine/SchedulerExecution.java b/src/main/java/org/camunda/automator/engine/SchedulerExecution.java index 41ed4a5..878a3aa 100644 --- a/src/main/java/org/camunda/automator/engine/SchedulerExecution.java +++ b/src/main/java/org/camunda/automator/engine/SchedulerExecution.java @@ -16,23 +16,23 @@ @ConfigurationProperties(prefix = "automator.scheduler") public class SchedulerExecution { - @Value("${automator.scheduler.scenario-path:''}") - public String scenarioPath; + @Value("${automator.scheduler.scenario-path:''}") + public String scenarioPath; - // https://www.baeldung.com/spring-boot-yaml-list - // @Value("${automator.scheduler.colors}") - @Autowired - BpmnEngineList bpmnEngineConfiguration; - Logger logger = LoggerFactory.getLogger(SchedulerExecution.class); - @Autowired - ServiceAccess serviceAccess; + // https://www.baeldung.com/spring-boot-yaml-list + // @Value("${automator.scheduler.colors}") + @Autowired + BpmnEngineList bpmnEngineConfiguration; + Logger logger = LoggerFactory.getLogger(SchedulerExecution.class); + @Autowired + ServiceAccess serviceAccess; - @PostConstruct - public void init() { - // We run the CLI, do nothing - if (AutomatorCLI.isRunningCLI) - return; - logger.info("SchedulerExecution soon"); - } + @PostConstruct + public void init() { + // We run the CLI, do nothing + if (AutomatorCLI.isRunningCLI) + return; + logger.info("SchedulerExecution soon"); + } } diff --git a/src/main/java/org/camunda/automator/engine/flow/CreateProcessInstanceThread.java b/src/main/java/org/camunda/automator/engine/flow/CreateProcessInstanceThread.java index d71e754..a5b1367 100644 --- a/src/main/java/org/camunda/automator/engine/flow/CreateProcessInstanceThread.java +++ b/src/main/java/org/camunda/automator/engine/flow/CreateProcessInstanceThread.java @@ -18,203 +18,203 @@ import java.util.stream.Collectors; public class CreateProcessInstanceThread { - private final int executionBatchNumber; - private final ScenarioStep scenarioStep; - private final RunScenario runScenario; - private final RunResult runResult; - private final Logger logger = LoggerFactory.getLogger(CreateProcessInstanceThread.class); - private final List<StartProcess> listStartProcess = new ArrayList<>(); - - /** - * @param executionBatchNumber Each time a new batch is running, this number increase - * @param scenarioStep scenario step - * @param runScenario scenario - * @param runResult result to fulfill - */ - public CreateProcessInstanceThread(int executionBatchNumber, - ScenarioStep scenarioStep, - RunScenario runScenario, - RunResult runResult) { - this.executionBatchNumber = executionBatchNumber; - this.scenarioStep = scenarioStep; - this.runScenario = runScenario; - this.runResult = runResult; - } - - /** - * After the duration, we stop - * - * @param durationToCreateProcessInstances maximum duration to produce all PI - */ - public void createProcessInstances(Duration durationToCreateProcessInstances) { - - int nbThreads = scenarioStep.getNbThreads() == 0 ? 1 : scenarioStep.getNbThreads(); - - // the configuration may overload this value - Integer configurationNbThreads = runScenario.getRunParameters().getStartEventNbThreads(); - String additionalComment = ""; - if (configurationNbThreads != null) { - additionalComment = "(nbThreads overrided by the configuration: " + configurationNbThreads + ")"; - nbThreads = configurationNbThreads.intValue(); - } - - ExecutorService executor = Executors.newFixedThreadPool(nbThreads); - int totalNumberOfPi = 0; - - int processInstancePerThread = (int) Math.ceil(1.0 * scenarioStep.getNumberOfExecutions() / nbThreads); - logger.info("StartNbThreads Step:[{}] PI:{} Duration:[{}] Thread:{} {} PI/thread:{}", scenarioStep.getTaskId(), - scenarioStep.getNumberOfExecutions(), durationToCreateProcessInstances, nbThreads, additionalComment, - processInstancePerThread); - // Submit tasks to the executor - for (int i = 0; i < nbThreads; i++) { - int numberOfProcessInstanceToStart = Math.min(processInstancePerThread, - scenarioStep.getNumberOfExecutions() - totalNumberOfPi); - totalNumberOfPi += numberOfProcessInstanceToStart; - StartProcess task = new StartProcess(executionBatchNumber, i, numberOfProcessInstanceToStart, - durationToCreateProcessInstances, scenarioStep, runScenario, runResult); - executor.submit(task); - listStartProcess.add(task); - } - // Shut down the executor and wait for all tasks to complete - executor.shutdown(); - try { - executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); - } catch (InterruptedException e) { - logger.error("Error during waiting for the end of all tasks"); - } - } - - public List<String> getListProcessInstances() { - return listStartProcess.stream().flatMap(t -> t.listProcessInstances.stream()).collect(Collectors.toList()); - } - - public int getNumberOfRunningThreads() { - return (int) listStartProcess.stream().filter(t -> t.isRunning()).count(); - } - - public int getTotalCreation() { - return listStartProcess.stream().mapToInt(t -> t.nbCreation).sum(); - } - - public int getTotalFailed() { - return listStartProcess.stream().mapToInt(t -> t.nbFailed).sum(); - } - - /** - * return true if the creation overload the durationToCreate: we can't create all PI in the duration - * - * @return true if it wasn't possible to create all PI during the duration - */ - public boolean isOverload() { - return listStartProcess.stream().anyMatch(t -> t.isOverload); - } - - /** - * This subclass start numberOfProcessInstanceToStart of process instances. - * Multiple threads doing the same operation are running at the same time. - */ - private class StartProcess implements Runnable { + private final int executionBatchNumber; private final ScenarioStep scenarioStep; - private final RunResult runResult; private final RunScenario runScenario; - private final int executionBatchNumber; - private final int indexInBatch; - int numberOfProcessInstanceToStart; - List<String> listProcessInstances = new ArrayList<>(); - int nbCreation = 0; - int nbFailed = 0; + private final RunResult runResult; + private final Logger logger = LoggerFactory.getLogger(CreateProcessInstanceThread.class); + private final List<StartProcess> listStartProcess = new ArrayList<>(); + + /** + * @param executionBatchNumber Each time a new batch is running, this number increase + * @param scenarioStep scenario step + * @param runScenario scenario + * @param runResult result to fulfill + */ + public CreateProcessInstanceThread(int executionBatchNumber, + ScenarioStep scenarioStep, + RunScenario runScenario, + RunResult runResult) { + this.executionBatchNumber = executionBatchNumber; + this.scenarioStep = scenarioStep; + this.runScenario = runScenario; + this.runResult = runResult; + } + /** - * the batch number + * After the duration, we stop + * + * @param durationToCreateProcessInstances maximum duration to produce all PI */ - boolean isOverload = false; + public void createProcessInstances(Duration durationToCreateProcessInstances) { + + int nbThreads = scenarioStep.getNbThreads() == 0 ? 1 : scenarioStep.getNbThreads(); + + // the configuration may overload this value + Integer configurationNbThreads = runScenario.getRunParameters().getStartEventNbThreads(); + String additionalComment = ""; + if (configurationNbThreads != null) { + additionalComment = "(nbThreads overrided by the configuration: " + configurationNbThreads + ")"; + nbThreads = configurationNbThreads.intValue(); + } - boolean isRunning = false; + ExecutorService executor = Executors.newFixedThreadPool(nbThreads); + int totalNumberOfPi = 0; + + int processInstancePerThread = (int) Math.ceil(1.0 * scenarioStep.getNumberOfExecutions() / nbThreads); + logger.info("StartNbThreads Step:[{}] PI:{} Duration:[{}] Thread:{} {} PI/thread:{}", scenarioStep.getTaskId(), + scenarioStep.getNumberOfExecutions(), durationToCreateProcessInstances, nbThreads, additionalComment, + processInstancePerThread); + // Submit tasks to the executor + for (int i = 0; i < nbThreads; i++) { + int numberOfProcessInstanceToStart = Math.min(processInstancePerThread, + scenarioStep.getNumberOfExecutions() - totalNumberOfPi); + totalNumberOfPi += numberOfProcessInstanceToStart; + StartProcess task = new StartProcess(executionBatchNumber, i, numberOfProcessInstanceToStart, + durationToCreateProcessInstances, scenarioStep, runScenario, runResult); + executor.submit(task); + listStartProcess.add(task); + } + // Shut down the executor and wait for all tasks to complete + executor.shutdown(); + try { + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + logger.error("Error during waiting for the end of all tasks"); + } + } + + public List<String> getListProcessInstances() { + return listStartProcess.stream().flatMap(t -> t.listProcessInstances.stream()).collect(Collectors.toList()); + } + + public int getNumberOfRunningThreads() { + return (int) listStartProcess.stream().filter(t -> t.isRunning()).count(); + } - Duration durationToCreateProcessInstances; + public int getTotalCreation() { + return listStartProcess.stream().mapToInt(t -> t.nbCreation).sum(); + } + + public int getTotalFailed() { + return listStartProcess.stream().mapToInt(t -> t.nbFailed).sum(); + } /** - * @param executionBatchNumber batch number executed - * @param indexInBatch the component number, when multiple component where generated to handle the flow - * @param numberOfProcessInstanceToStart number of process instance to start by this object - * @param durationToCreateProcessInstances duration max allowed to create process instance - * @param scenarioStep step to use to create the process instance - * @param runScenario scenario to use - * @param runResult result object to save information + * return true if the creation overload the durationToCreate: we can't create all PI in the duration + * + * @return true if it wasn't possible to create all PI during the duration */ - public StartProcess(int executionBatchNumber, - int indexInBatch, - int numberOfProcessInstanceToStart, - Duration durationToCreateProcessInstances, - ScenarioStep scenarioStep, - RunScenario runScenario, - RunResult runResult) { - this.executionBatchNumber = executionBatchNumber; - this.indexInBatch = indexInBatch; - this.durationToCreateProcessInstances = durationToCreateProcessInstances; - this.numberOfProcessInstanceToStart = numberOfProcessInstanceToStart; - this.runResult = runResult; - this.runScenario = runScenario; - this.scenarioStep = scenarioStep; + public boolean isOverload() { + return listStartProcess.stream().anyMatch(t -> t.isOverload); } /** - * This thread will create numberOfProcessInstanceToStart, but it monitor the time, and if the time is over - * the Duration, it stop + * This subclass start numberOfProcessInstanceToStart of process instances. + * Multiple threads doing the same operation are running at the same time. */ - @Override - public void run() { - isRunning = true; - boolean alreadyLoggedError = false; - isOverload = false; - long begin = System.currentTimeMillis(); - for (int i = 0; i < numberOfProcessInstanceToStart; i++) { - - // operation - try { - Map<String, Object> variables = RunZeebeOperation.getVariablesStep(runScenario, scenarioStep, indexInBatch); - String processInstance = runScenario.getBpmnEngine() - .createProcessInstance(scenarioStep.getProcessId(), scenarioStep.getTaskId(), // activityId - variables); - - if (runScenario.getRunParameters().showLevelDebug()) - logger.info("batch_#{} Create ProcessInstance:{} Variables {}", executionBatchNumber, processInstance, - variables); - - if (listProcessInstances.size() < 21) - listProcessInstances.add(processInstance); - nbCreation++; - runResult.registerAddProcessInstance(scenarioStep.getProcessId(), true); - - } catch (AutomatorException e) { - if (!alreadyLoggedError) - runResult.addError(scenarioStep, - "batch_#" + executionBatchNumber + "-" + scenarioStep.getId() + " Error at creation: [" + e.getMessage() - + "]"); - alreadyLoggedError = true; - nbFailed++; - runResult.registerAddProcessInstance(scenarioStep.getProcessId(), false); + private class StartProcess implements Runnable { + private final ScenarioStep scenarioStep; + private final RunResult runResult; + private final RunScenario runScenario; + private final int executionBatchNumber; + private final int indexInBatch; + int numberOfProcessInstanceToStart; + List<String> listProcessInstances = new ArrayList<>(); + int nbCreation = 0; + int nbFailed = 0; + /** + * the batch number + */ + boolean isOverload = false; + + boolean isRunning = false; + + Duration durationToCreateProcessInstances; + + /** + * @param executionBatchNumber batch number executed + * @param indexInBatch the component number, when multiple component where generated to handle the flow + * @param numberOfProcessInstanceToStart number of process instance to start by this object + * @param durationToCreateProcessInstances duration max allowed to create process instance + * @param scenarioStep step to use to create the process instance + * @param runScenario scenario to use + * @param runResult result object to save information + */ + public StartProcess(int executionBatchNumber, + int indexInBatch, + int numberOfProcessInstanceToStart, + Duration durationToCreateProcessInstances, + ScenarioStep scenarioStep, + RunScenario runScenario, + RunResult runResult) { + this.executionBatchNumber = executionBatchNumber; + this.indexInBatch = indexInBatch; + this.durationToCreateProcessInstances = durationToCreateProcessInstances; + this.numberOfProcessInstanceToStart = numberOfProcessInstanceToStart; + this.runResult = runResult; + this.runScenario = runScenario; + this.scenarioStep = scenarioStep; } - // do we have to stop the execution? - long currentTimeMillis = System.currentTimeMillis(); - Duration durationCurrent = durationToCreateProcessInstances.minusMillis(currentTimeMillis - begin); - if (durationCurrent.isNegative()) { - // log only at the debug mode (thread per thread), in monitoring log only at batch level - if (runScenario.getRunParameters().showLevelDebug()) { - // take too long to create the required process instance, so stop now. - logger.info("batch_#{} {} Over the duration. Created {} when expected {} in {} ms", executionBatchNumber, - scenarioStep.getId(), nbCreation, numberOfProcessInstanceToStart, currentTimeMillis - begin); - } - isOverload = true; - break; - } - } - isRunning = false; - } + /** + * This thread will create numberOfProcessInstanceToStart, but it monitor the time, and if the time is over + * the Duration, it stop + */ + @Override + public void run() { + isRunning = true; + boolean alreadyLoggedError = false; + isOverload = false; + long begin = System.currentTimeMillis(); + for (int i = 0; i < numberOfProcessInstanceToStart; i++) { + + // operation + try { + Map<String, Object> variables = RunZeebeOperation.getVariablesStep(runScenario, scenarioStep, indexInBatch); + String processInstance = runScenario.getBpmnEngine() + .createProcessInstance(scenarioStep.getProcessId(), scenarioStep.getTaskId(), // activityId + variables); + + if (runScenario.getRunParameters().showLevelDebug()) + logger.info("batch_#{} Create ProcessInstance:{} Variables {}", executionBatchNumber, processInstance, + variables); + + if (listProcessInstances.size() < 21) + listProcessInstances.add(processInstance); + nbCreation++; + runResult.registerAddProcessInstance(scenarioStep.getProcessId(), true); + + } catch (AutomatorException e) { + if (!alreadyLoggedError) + runResult.addError(scenarioStep, + "batch_#" + executionBatchNumber + "-" + scenarioStep.getId() + " Error at creation: [" + e.getMessage() + + "]"); + alreadyLoggedError = true; + nbFailed++; + runResult.registerAddProcessInstance(scenarioStep.getProcessId(), false); + } + // do we have to stop the execution? + long currentTimeMillis = System.currentTimeMillis(); + Duration durationCurrent = durationToCreateProcessInstances.minusMillis(currentTimeMillis - begin); + if (durationCurrent.isNegative()) { + // log only at the debug mode (thread per thread), in monitoring log only at batch level + if (runScenario.getRunParameters().showLevelDebug()) { + // take too long to create the required process instance, so stop now. + logger.info("batch_#{} {} Over the duration. Created {} when expected {} in {} ms", executionBatchNumber, + scenarioStep.getId(), nbCreation, numberOfProcessInstanceToStart, currentTimeMillis - begin); + } + isOverload = true; + break; + } + } + + isRunning = false; + } - public boolean isRunning() { - return isRunning; + public boolean isRunning() { + return isRunning; + } } - } } diff --git a/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java b/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java index 4456ebb..866b177 100644 --- a/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java +++ b/src/main/java/org/camunda/automator/engine/flow/FixedBackoffSupplier.java @@ -10,14 +10,14 @@ public class FixedBackoffSupplier implements BackoffSupplier { - private long fixedBackOffDelay = 0; + private long fixedBackOffDelay = 0; - public FixedBackoffSupplier(long fixedBackOffDelay) { - this.fixedBackOffDelay = fixedBackOffDelay; - } + public FixedBackoffSupplier(long fixedBackOffDelay) { + this.fixedBackOffDelay = fixedBackOffDelay; + } - @Override - public long supplyRetryDelay(long currentRetryDelay) { - return fixedBackOffDelay; - } + @Override + public long supplyRetryDelay(long currentRetryDelay) { + return fixedBackOffDelay; + } } \ No newline at end of file diff --git a/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java b/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java index fb853a6..6dbf3a3 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java @@ -14,295 +14,291 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class RunObjectives { - private final BpmnEngine bpmnEngine; - private final Map<String, RunResult.RecordCreationPI> recordCreationPIMap; - private final Map<Integer, List<SavePhoto>> flowRateMnObjective = new HashMap<>(); - private final List<ScenarioFlowControl.Objective> listObjectives; - Logger logger = LoggerFactory.getLogger(RunObjectives.class); - private DateFilter startDateFilter; - private DateFilter endDateFilter; - private long lastHeartBeat; + private final BpmnEngine bpmnEngine; + private final Map<String, RunResult.RecordCreationPI> recordCreationPIMap; + private final Map<Integer, List<SavePhoto>> flowRateMnObjective = new HashMap<>(); + private final List<ScenarioFlowControl.Objective> listObjectives; + Logger logger = LoggerFactory.getLogger(RunObjectives.class); + private DateFilter startDateFilter; + private DateFilter endDateFilter; + private long lastHeartBeat; - public RunObjectives(List<ScenarioFlowControl.Objective> listObjectives, - BpmnEngine bpmnEngine, - Map<String, RunResult.RecordCreationPI> recordCreationPIMap) { - this.listObjectives = listObjectives; - this.bpmnEngine = bpmnEngine; - this.recordCreationPIMap = recordCreationPIMap; + public RunObjectives(List<ScenarioFlowControl.Objective> listObjectives, + BpmnEngine bpmnEngine, + Map<String, RunResult.RecordCreationPI> recordCreationPIMap) { + this.listObjectives = listObjectives; + this.bpmnEngine = bpmnEngine; + this.recordCreationPIMap = recordCreationPIMap; - for (int i = 0; i < listObjectives.size(); i++) { - listObjectives.get(i).index = i; + for (int i = 0; i < listObjectives.size(); i++) { + listObjectives.get(i).index = i; + } } - } - public void setStartDate(Date startTestDate) { - this.startDateFilter = new DateFilter(startTestDate); - this.lastHeartBeat = System.currentTimeMillis(); - } + public void setStartDate(Date startTestDate) { + this.startDateFilter = new DateFilter(startTestDate); + this.lastHeartBeat = System.currentTimeMillis(); + } - public void setEndDate(Date endTestDate) { - this.endDateFilter = new DateFilter(endTestDate); - } + public void setEndDate(Date endTestDate) { + this.endDateFilter = new DateFilter(endTestDate); + } - /** - * heartbeat - */ - public void heartBeat() { - long currentTime = System.currentTimeMillis(); - // only one minutes - if (currentTime - lastHeartBeat < 1000 * 60) - return; + /** + * heartbeat + */ + public void heartBeat() { + long currentTime = System.currentTimeMillis(); + // only one minutes + if (currentTime - lastHeartBeat < 1000 * 60) + return; - // one minutes: do we have a FLOWRATEUSERTASKMN objective - for (ScenarioFlowControl.Objective objective : listObjectives) { - if (ScenarioFlowControl.Objective.TYPEOBJECTIVE.FLOWRATEUSERTASKMN.equals(objective.type)) { - // get the value - SavePhoto currentPhoto = new SavePhoto(); - try { - currentPhoto.nbOfTasks = bpmnEngine.countNumberOfTasks(objective.processId, objective.taskId); - } catch (AutomatorException e) { - logger.error("Can't get NumberOfTask "); + // one minutes: do we have a FLOWRATEUSERTASKMN objective + for (ScenarioFlowControl.Objective objective : listObjectives) { + if (ScenarioFlowControl.Objective.TYPEOBJECTIVE.FLOWRATEUSERTASKMN.equals(objective.type)) { + // get the value + SavePhoto currentPhoto = new SavePhoto(); + try { + currentPhoto.nbOfTasks = bpmnEngine.countNumberOfTasks(objective.processId, objective.taskId); + } catch (AutomatorException e) { + logger.error("Can't get NumberOfTask "); + } + List<SavePhoto> listValues = flowRateMnObjective.getOrDefault(objective.index, new ArrayList<>()); + SavePhoto previousPhoto = listValues.isEmpty() ? new SavePhoto() : listValues.get(listValues.size() - 1); + currentPhoto.delta = currentPhoto.nbOfTasks - previousPhoto.nbOfTasks; + listValues.add(currentPhoto); + flowRateMnObjective.put(objective.index, listValues); + logger.info("heartBeat: FlowRateUserTaskMn [{}] prev [{}} current [{}] delta [{}] expected [{}] in {} s", + objective.getInformation(), previousPhoto.nbOfTasks, currentPhoto.nbOfTasks, currentPhoto.delta, + objective.value, (currentTime - lastHeartBeat) / 1000); + } } - List<SavePhoto> listValues = flowRateMnObjective.getOrDefault(objective.index, new ArrayList<>()); - SavePhoto previousPhoto = listValues.isEmpty() ? new SavePhoto() : listValues.get(listValues.size() - 1); - currentPhoto.delta = currentPhoto.nbOfTasks - previousPhoto.nbOfTasks; - listValues.add(currentPhoto); - flowRateMnObjective.put(objective.index, listValues); - logger.info("heartBeat: FlowRateUserTaskMn [{}] prev [{}} current [{}] delta [{}] expected [{}] in {} s", - objective.getInformation(), previousPhoto.nbOfTasks, currentPhoto.nbOfTasks, currentPhoto.delta, - objective.value, (currentTime - lastHeartBeat) / 1000); - } + lastHeartBeat = currentTime; } - lastHeartBeat = currentTime; - } - /** - * Check the objective, and return an analysis string; If the string is empty, the objectif is reach - * - * @return empty if the objective is Ok, else an analysis - */ - public List<ObjectiveResult> check() { - List<ObjectiveResult> listCheck = new ArrayList<>(); - for (ScenarioFlowControl.Objective objective : listObjectives) { - if (objective.type == null) { - logger.error("Objective {} does not have a type", objective.getInformation()); - ObjectiveResult objectiveResult = new ObjectiveResult(objective); - objectiveResult.success = false; - objectiveResult.analysis = "Error: Objective " + objective.getInformation() + " does not have a type"; - listCheck.add(objectiveResult); - continue; - } - listCheck.add(switch (objective.type) { - case CREATED -> checkObjectiveCreated(objective); - case ENDED -> checkObjectiveEnded(objective); - case USERTASK -> checkObjectiveUserTask(objective); - case FLOWRATEUSERTASKMN -> checkObjectiveFlowRate(objective); - }); + /** + * Check the objective, and return an analysis string; If the string is empty, the objectif is reach + * + * @return empty if the objective is Ok, else an analysis + */ + public List<ObjectiveResult> check() { + List<ObjectiveResult> listCheck = new ArrayList<>(); + for (ScenarioFlowControl.Objective objective : listObjectives) { + if (objective.type == null) { + logger.error("Objective {} does not have a type", objective.getInformation()); + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.success = false; + objectiveResult.analysis = "Error: Objective " + objective.getInformation() + " does not have a type"; + listCheck.add(objectiveResult); + continue; + } + listCheck.add(switch (objective.type) { + case CREATED -> checkObjectiveCreated(objective); + case ENDED -> checkObjectiveEnded(objective); + case USERTASK -> checkObjectiveUserTask(objective); + case FLOWRATEUSERTASKMN -> checkObjectiveFlowRate(objective); + }); + } + return listCheck; } - return listCheck; - } - /** - * Creation: does the number of process instance was created? - * - * @param objective objective to reach - * @return result - */ - private ObjectiveResult checkObjectiveCreated(ScenarioFlowControl.Objective objective) { - ObjectiveResult objectiveResult = new ObjectiveResult(objective); - objectiveResult.objectiveValue = objective.value; - if (objective.value <= 0) { - objectiveResult.success = true; - objectiveResult.analysis += "No value to reach"; - return objectiveResult; - } - try { - long processInstancesCreatedAPI = bpmnEngine.countNumberOfProcessInstancesCreated(objective.processId, - startDateFilter, endDateFilter); - RunResult.RecordCreationPI recordCreation = recordCreationPIMap.getOrDefault(objective.processId, - new RunResult.RecordCreationPI(objective.processId)); + /** + * Creation: does the number of process instance was created? + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveCreated(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.objectiveValue = objective.value; + if (objective.value <= 0) { + objectiveResult.success = true; + objectiveResult.analysis += "No value to reach"; + return objectiveResult; + } + try { + long processInstancesCreatedAPI = bpmnEngine.countNumberOfProcessInstancesCreated(objective.processId, + startDateFilter, endDateFilter); + RunResult.RecordCreationPI recordCreation = recordCreationPIMap.getOrDefault(objective.processId, + new RunResult.RecordCreationPI(objective.processId)); - objectiveResult.recordedSuccessValue = recordCreation.nbCreated; - objectiveResult.recordedFailValue = recordCreation.nbFailed; + objectiveResult.recordedSuccessValue = recordCreation.nbCreated; + objectiveResult.recordedFailValue = recordCreation.nbFailed; - int percent = (int) (100.0 * objectiveResult.recordedSuccessValue / (objective.value == 0 ? 1 : objective.value)); + int percent = (int) (100.0 * objectiveResult.recordedSuccessValue / (objective.value == 0 ? 1 : objective.value)); - objectiveResult.analysis += "Objective " + objective.getInformation() // informatin - + ": Goal[" + objective.value // objective - + "] Created(zeebeAPI)[" + processInstancesCreatedAPI // Value by the API, not really accurate - + "] Created(AutomatorRecord)[" + objectiveResult.recordedSuccessValue // value recorded by automator - + " (" + percent + " % )" // percent based on the recorded value - + " CreateFail(AutomatorRecord)[" + objectiveResult.recordedFailValue + "]"; + objectiveResult.analysis += "Objective " + objective.getInformation() // informatin + + ": Goal[" + objective.value // objective + + "] Created(zeebeAPI)[" + processInstancesCreatedAPI // Value by the API, not really accurate + + "] Created(AutomatorRecord)[" + objectiveResult.recordedSuccessValue // value recorded by automator + + " (" + percent + " % )" // percent based on the recorded value + + " CreateFail(AutomatorRecord)[" + objectiveResult.recordedFailValue + "]"; - if (objectiveResult.recordedSuccessValue < objective.value) { - objectiveResult.success = false; - } - } catch (AutomatorException e) { - objectiveResult.success = false; - objectiveResult.analysis += "Can't search countNumberOfProcessInstancesCreated " + e.getMessage(); + if (objectiveResult.recordedSuccessValue < objective.value) { + objectiveResult.success = false; + } + } catch (AutomatorException e) { + objectiveResult.success = false; + objectiveResult.analysis += "Can't search countNumberOfProcessInstancesCreated " + e.getMessage(); + } + return objectiveResult; } - return objectiveResult; - } - /** - * ObjectiveEnded : does process ended? - * - * @param objective objective to reach - * @return result - */ - private ObjectiveResult checkObjectiveEnded(ScenarioFlowControl.Objective objective) { - ObjectiveResult objectiveResult = new ObjectiveResult(objective); - objectiveResult.objectiveValue = objective.value; - if (objective.value <= 0) { - objectiveResult.success = true; - objectiveResult.analysis += "No value to reach"; - return objectiveResult; - } - try { - objectiveResult.recordedSuccessValue = bpmnEngine.countNumberOfProcessInstancesEnded(objective.processId, - startDateFilter, endDateFilter); - if (objectiveResult.recordedSuccessValue < objective.value) { - objectiveResult.analysis += - "Fail: " + objective.getInformation() + " : " + objective.value + " ended expected, " - + objectiveResult.recordedSuccessValue + " created (" + (int) ( - 100.0 * objectiveResult.recordedSuccessValue / objective.value) + " %), "; - objectiveResult.success = false; - } + /** + * ObjectiveEnded : does process ended? + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveEnded(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.objectiveValue = objective.value; + if (objective.value <= 0) { + objectiveResult.success = true; + objectiveResult.analysis += "No value to reach"; + return objectiveResult; + } + try { + objectiveResult.recordedSuccessValue = bpmnEngine.countNumberOfProcessInstancesEnded(objective.processId, + startDateFilter, endDateFilter); + if (objectiveResult.recordedSuccessValue < objective.value) { + objectiveResult.analysis += + "Fail: " + objective.getInformation() + " : " + objective.value + " ended expected, " + + objectiveResult.recordedSuccessValue + " created (" + (int) ( + 100.0 * objectiveResult.recordedSuccessValue / objective.value) + " %), "; + objectiveResult.success = false; + } - } catch (AutomatorException e) { - objectiveResult.success = false; - objectiveResult.analysis += "Can't search NumberOfProcessInstanceEnded: " + e.getMessage(); + } catch (AutomatorException e) { + objectiveResult.success = false; + objectiveResult.analysis += "Can't search NumberOfProcessInstanceEnded: " + e.getMessage(); + } + return objectiveResult; } - return objectiveResult; - } - /** - * UserTask: does user tasks are present? - * - * @param objective objective to reach - * @return result - */ - private ObjectiveResult checkObjectiveUserTask(ScenarioFlowControl.Objective objective) { - ObjectiveResult objectiveResult = new ObjectiveResult(objective); - objectiveResult.objectiveValue = objective.value; - if (objective.value <= 0) { - objectiveResult.success = true; - objectiveResult.analysis += "No value to reach"; - return objectiveResult; - } - try { - objectiveResult.recordedSuccessValue = bpmnEngine.countNumberOfTasks(objective.processId, objective.taskId); - if (objectiveResult.recordedSuccessValue < objective.value) { - objectiveResult.analysis += - "Fail: " + objective.getInformation() + " : [" + objective.value + "] tasks expected, "; - objectiveResult.analysis += - objectiveResult.recordedSuccessValue + " found (" + (int) (100.0 * objectiveResult.recordedSuccessValue - / objective.value) + " %), "; - objectiveResult.success = false; - } - } catch (AutomatorException e) { - objectiveResult.success = false; - objectiveResult.analysis += "Can't search NumberOfProcessInstanceEnded: " + e.getMessage(); + /** + * UserTask: does user tasks are present? + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveUserTask(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + objectiveResult.objectiveValue = objective.value; + if (objective.value <= 0) { + objectiveResult.success = true; + objectiveResult.analysis += "No value to reach"; + return objectiveResult; + } + try { + objectiveResult.recordedSuccessValue = bpmnEngine.countNumberOfTasks(objective.processId, objective.taskId); + if (objectiveResult.recordedSuccessValue < objective.value) { + objectiveResult.analysis += + "Fail: " + objective.getInformation() + " : [" + objective.value + "] tasks expected, "; + objectiveResult.analysis += + objectiveResult.recordedSuccessValue + " found (" + (int) (100.0 * objectiveResult.recordedSuccessValue + / objective.value) + " %), "; + objectiveResult.success = false; + } + } catch (AutomatorException e) { + objectiveResult.success = false; + objectiveResult.analysis += "Can't search NumberOfProcessInstanceEnded: " + e.getMessage(); + } + return objectiveResult; } - return objectiveResult; - } - /** - * FlowRate - * - * @param objective objective to reach - * @return result - */ - private ObjectiveResult checkObjectiveFlowRate(ScenarioFlowControl.Objective objective) { - ObjectiveResult objectiveResult = new ObjectiveResult(objective); - // check all values - try { - long lowThreshold = (long) (((double) objective.value) * (1.0 - - ((double) objective.getStandardDeviation()) / 100.0)); - objectiveResult.objectiveValue = objective.value; - objectiveResult.analysis += - "Threshold[" + objective.value + "] standardDeviation[" + objective.getStandardDeviation() + "] LowThreshold[" - + lowThreshold + "]"; - long sumValues = 0; - List<SavePhoto> listValues = flowRateMnObjective.getOrDefault(objective.index, new ArrayList<>()); - if (listValues.isEmpty()) { - objectiveResult.analysis += "No values"; - objectiveResult.success = false; - return objectiveResult; - } + /** + * FlowRate + * + * @param objective objective to reach + * @return result + */ + private ObjectiveResult checkObjectiveFlowRate(ScenarioFlowControl.Objective objective) { + ObjectiveResult objectiveResult = new ObjectiveResult(objective); + // check all values + try { + long lowThreshold = (long) (((double) objective.value) * (1.0 + - ((double) objective.getStandardDeviation()) / 100.0)); + objectiveResult.objectiveValue = objective.value; + objectiveResult.analysis += + "Threshold[" + objective.value + "] standardDeviation[" + objective.getStandardDeviation() + "] LowThreshold[" + + lowThreshold + "]"; + long sumValues = 0; + List<SavePhoto> listValues = flowRateMnObjective.getOrDefault(objective.index, new ArrayList<>()); + if (listValues.isEmpty()) { + objectiveResult.analysis += "No values"; + objectiveResult.success = false; + return objectiveResult; + } - StringBuilder valuesString = new StringBuilder(); - int numberUnderThreshold = 0; - int count = 0; - for (SavePhoto photo : listValues) { - sumValues += photo.delta; - count++; - if (count == 50) { - valuesString.append("... TooManyValues["); - valuesString.append(listValues.size()); - valuesString.append("]"); - } - if (count < 50) { - valuesString.append(photo.delta); - valuesString.append(","); - } + StringBuilder valuesString = new StringBuilder(); + int numberUnderThreshold = 0; + int count = 0; + for (SavePhoto photo : listValues) { + sumValues += photo.delta; + count++; + if (count == 50) { + valuesString.append("... TooManyValues["); + valuesString.append(listValues.size()); + valuesString.append("]"); + } + if (count < 50) { + valuesString.append(photo.delta); + valuesString.append(","); + } - if (photo.delta < lowThreshold) { - numberUnderThreshold++; + if (photo.delta < lowThreshold) { + numberUnderThreshold++; + } + } + if (numberUnderThreshold > 0) { + objectiveResult.analysis += + "NumberOrValueUnderThreshold[" + numberUnderThreshold + "], values: " + valuesString; + objectiveResult.success = false; + } + // the total must be at the value + long averageValue = (long) (((double) sumValues) / listValues.size()); + objectiveResult.recordedSuccessValue = averageValue; + if (averageValue < objective.value) { + objectiveResult.analysis += "AverageUnderObjective[" + averageValue + "]"; + objectiveResult.success = false; + } else { + objectiveResult.analysis += "AverageReach[" + averageValue + "]"; + } + } catch (Exception e) { + logger.error("Error during checkFlowRateObjective {}", e.getMessage()); + objectiveResult.success = false; } - } - if (numberUnderThreshold > 0) { - objectiveResult.analysis += - "NumberOrValueUnderThreshold[" + numberUnderThreshold + "], values: " + valuesString; - objectiveResult.success = false; - } - // the total must be at the value - long averageValue = (long) (((double) sumValues) / listValues.size()); - objectiveResult.recordedSuccessValue = averageValue; - if (averageValue < objective.value) { - objectiveResult.analysis += "AverageUnderObjective[" + averageValue + "]"; - objectiveResult.success = false; - } else { - objectiveResult.analysis += "AverageReach[" + averageValue + "]"; - } - } catch (Exception e) { - logger.error("Error during checkFlowRateObjective {}", e.getMessage()); - objectiveResult.success = false; - } - return objectiveResult; + return objectiveResult; - } + } - public static class ObjectiveResult { - public String analysis = ""; - public boolean success = true; - public long objectiveValue; - public long recordedSuccessValue; - public long recordedFailValue; - ScenarioFlowControl.Objective objective; + public static class ObjectiveResult { + public String analysis = ""; + public boolean success = true; + public long objectiveValue; + public long recordedSuccessValue; + public long recordedFailValue; + ScenarioFlowControl.Objective objective; - public ObjectiveResult(ScenarioFlowControl.Objective objective) { - this.objective = objective; + public ObjectiveResult(ScenarioFlowControl.Objective objective) { + this.objective = objective; + } } - } - /** - * Key is the Objective Index - * Value is a list of two information: - * - the reference value in the slot - * - the - */ - public static class SavePhoto { - public long nbOfTasks = 0; - public long delta = 0; + /** + * Key is the Objective Index + * Value is a list of two information: + * - the reference value in the slot + * - the + */ + public static class SavePhoto { + public long nbOfTasks = 0; + public long delta = 0; - } + } } diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java index 8dca818..73d830a 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowBasic.java @@ -14,68 +14,68 @@ public abstract class RunScenarioFlowBasic { - protected final RunResult runResult; - private final ScenarioStep scenarioStep; - private final RunScenario runScenario; + protected final RunResult runResult; + private final ScenarioStep scenarioStep; + private final RunScenario runScenario; - RunScenarioFlowBasic(ScenarioStep scenarioStep, RunScenario runScenario, RunResult runResult) { - this.scenarioStep = scenarioStep; - this.runScenario = runScenario; - this.runResult = runResult; - } + RunScenarioFlowBasic(ScenarioStep scenarioStep, RunScenario runScenario, RunResult runResult) { + this.scenarioStep = scenarioStep; + this.runScenario = runScenario; + this.runResult = runResult; + } - /** - * Return an uniq ID of the step - * - * @return the ID of the step - */ - public String getId() { - return scenarioStep.getId(); - } + /** + * Return an uniq ID of the step + * + * @return the ID of the step + */ + public String getId() { + return scenarioStep.getId(); + } - /** - * the task return the topic to address: - * - topic for a service task - * - taskId for a user task - */ - public abstract String getTopic(); + /** + * the task return the topic to address: + * - topic for a service task + * - taskId for a user task + */ + public abstract String getTopic(); - public RunScenario getRunScenario() { - return runScenario; - } + public RunScenario getRunScenario() { + return runScenario; + } - /** - * The flow return the runResult given at the execution - * - * @return result - */ - public RunResult getRunResult() { - return runResult; - } + /** + * The flow return the runResult given at the execution + * + * @return result + */ + public RunResult getRunResult() { + return runResult; + } - /** - * The flow execute a step - return it - * - * @return scenarioStep - */ - public ScenarioStep getScenarioStep() { - return scenarioStep; - } + /** + * The flow execute a step - return it + * + * @return scenarioStep + */ + public ScenarioStep getScenarioStep() { + return scenarioStep; + } - /** - * Start the execution. Attention, only errors must be reported in the result - */ - public abstract void execute(); + /** + * Start the execution. Attention, only errors must be reported in the result + */ + public abstract void execute(); - public abstract STATUS getStatus(); + public abstract STATUS getStatus(); - public abstract int getCurrentNumberOfThreads(); + public abstract int getCurrentNumberOfThreads(); - /** - * The flow must stop now - */ - public abstract void pleaseStop(); + /** + * The flow must stop now + */ + public abstract void pleaseStop(); - public enum STATUS {RUNNING, STOPPING, STOPPED} + public enum STATUS {RUNNING, STOPPING, STOPPED} } diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java index c951b2c..85e5396 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java @@ -33,247 +33,249 @@ import java.util.concurrent.Semaphore; public class RunScenarioFlowServiceTask extends RunScenarioFlowBasic { - private static final TrackActiveWorker trackActiveWorkers = new TrackActiveWorker(); - private static final TrackActiveWorker trackAsynchronousWorkers = new TrackActiveWorker(); - private final TaskScheduler scheduler; - private final Semaphore semaphore; - Logger logger = LoggerFactory.getLogger(RunScenarioFlowServiceTask.class); - private BpmnEngine.RegisteredTask registeredTask; - private boolean stopping; - @Autowired - private BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy; - - public RunScenarioFlowServiceTask(TaskScheduler scheduler, - ScenarioStep scenarioStep, - RunScenario runScenario, - RunResult runResult) { - super(scenarioStep, runScenario, runResult); - this.scheduler = scheduler; - this.semaphore = new Semaphore(Math.max(1, scenarioStep.getNbTokens())); - } - - @Override - public String getTopic() { - return getScenarioStep().getTopic(); - } - - @Override - public void execute() { - registerWorker(); - } - - @Override - public void pleaseStop() { - logger.info("Ask Stopping [" + getId() + "]"); - stopping = true; - if (registeredTask == null || (registeredTask.isNull())) - return; - if (registeredTask.isClosed()) { - return; + private static final TrackActiveWorker trackActiveWorkers = new TrackActiveWorker(); + private static final TrackActiveWorker trackAsynchronousWorkers = new TrackActiveWorker(); + private final TaskScheduler scheduler; + private final Semaphore semaphore; + Logger logger = LoggerFactory.getLogger(RunScenarioFlowServiceTask.class); + private BpmnEngine.RegisteredTask registeredTask; + private boolean stopping; + + private BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy=null; + + public RunScenarioFlowServiceTask(TaskScheduler scheduler, + ScenarioStep scenarioStep, + RunScenario runScenario, + RunResult runResult) { + super(scenarioStep, runScenario, runResult); + this.scheduler = scheduler; + this.semaphore = new Semaphore(Math.max(1, scenarioStep.getNbTokens())); } - registeredTask.close(); - - Duration durationSleep = getScenarioStep().getWaitingTimeDuration(Duration.ZERO); - long expectedEndTime = System.currentTimeMillis() + durationSleep.toMillis(); - while (!registeredTask.isClosed() && System.currentTimeMillis() < expectedEndTime) { - registeredTask.close(); - try { - Thread.sleep(500); - } catch (Exception e) { - // do nothing - } - } - logger.info("[" + getId() + "] " + (registeredTask.isClosed() ? "stopped" : "Fail to stop")); - - registeredTask = null; - } - - @Override - public STATUS getStatus() { - if (registeredTask == null) - return STATUS.STOPPED; - if (stopping) - return STATUS.STOPPING; - return STATUS.RUNNING; - } - - @Override - public int getCurrentNumberOfThreads() { - return trackActiveWorkers.getCounter() + trackAsynchronousWorkers.getCounter(); - } - - /** - * Register the worker - */ - - private void registerWorker() { - BpmnEngine bpmnEngine = getRunScenario().getBpmnEngine(); - - Duration durationSleep = getScenarioStep().getWaitingTimeDuration(Duration.ZERO); - durationSleep = durationSleep.plusSeconds(10); - - if (getRunScenario().getRunParameters().showLevelMonitoring()) { - logger.info("Start service TaskId[{}] Topic[{}] StreamEnabled:{} DurationSleep[{} ms]", - getScenarioStep().getTaskId(), getScenarioStep().getTopic(), getScenarioStep().isStreamEnabled(), - durationSleep.toMillis()); - } - - registeredTask = bpmnEngine.registerServiceTask(getId(), // workerId - getScenarioStep().getTopic(), // topic - getScenarioStep().isStreamEnabled(), // stream - durationSleep, // lock time - new SimpleDelayHandler(this), new FixedBackoffSupplier(getScenarioStep().getFixedBackOffDelay())); - } - private static class TrackActiveWorker { - public int counter = 0; - - public synchronized void movement(int movement) { - counter += movement; + @Override + public String getTopic() { + return getScenarioStep().getTopic(); } - public int getCounter() { - return counter; - } - } - - /** - * C7, C8 Handler - */ - public class SimpleDelayHandler implements ExternalTaskHandler, JobHandler { - private final RunScenarioFlowServiceTask flowServiceTask; - private final Duration durationSleep; - - public SimpleDelayHandler(RunScenarioFlowServiceTask flowServiceTask) { - this.flowServiceTask = flowServiceTask; - durationSleep = flowServiceTask.getScenarioStep().getWaitingTimeDuration(Duration.ZERO); + @Override + public void execute() { + registerWorker(); } - /* C7 Management */ @Override - public void execute(org.camunda.bpm.client.task.ExternalTask externalTask, - ExternalTaskService externalTaskService) { - switch (getScenarioStep().getModeExecution()) { - case CLASSICAL, WAIT -> - manageWaitExecution(externalTask, externalTaskService, null, null, durationSleep.toMillis()); - case THREAD, ASYNCHRONOUS -> manageAsynchronousExecution(externalTask, externalTaskService, null, null); - case THREADTOKEN, ASYNCHRONOUSLIMITED -> - manageAsynchronousLimitedExecution(externalTask, externalTaskService, null, null); - } + public void pleaseStop() { + logger.info("Ask Stopping [" + getId() + "]"); + stopping = true; + if (registeredTask == null || (registeredTask.isNull())) + return; + if (registeredTask.isClosed()) { + return; + } + registeredTask.close(); + + Duration durationSleep = getScenarioStep().getWaitingTimeDuration(Duration.ZERO); + long expectedEndTime = System.currentTimeMillis() + durationSleep.toMillis(); + while (!registeredTask.isClosed() && System.currentTimeMillis() < expectedEndTime) { + registeredTask.close(); + try { + Thread.sleep(500); + } catch (Exception e) { + // do nothing + } + } + logger.info("[" + getId() + "] " + (registeredTask.isClosed() ? "stopped" : "Fail to stop")); + + registeredTask = null; } - /* C8 management */ @Override - public void handle(JobClient jobClient, ActivatedJob activatedJob) throws Exception { - switch (getScenarioStep().getModeExecution()) { - case CLASSICAL, WAIT -> manageWaitExecution(null, null, jobClient, activatedJob, durationSleep.toMillis()); - case THREAD, ASYNCHRONOUS -> manageAsynchronousExecution(null, null, jobClient, activatedJob); - case THREADTOKEN, ASYNCHRONOUSLIMITED -> manageAsynchronousLimitedExecution(null, null, jobClient, activatedJob); - } + public STATUS getStatus() { + if (registeredTask == null) + return STATUS.STOPPED; + if (stopping) + return STATUS.STOPPING; + return STATUS.RUNNING; } - private void manageWaitExecution(org.camunda.bpm.client.task.ExternalTask externalTask, - ExternalTaskService externalTaskService, - JobClient jobClient, - ActivatedJob activatedJob, - long waitTimeInMs) { - long begin = System.currentTimeMillis(); - Map<String, Object> variables = new HashMap<>(); - Map<String, Object> currentVariables = new HashMap<>(); - try { - if (getRunScenario().getRunParameters().isDeepTracking()) - trackActiveWorkers.movement(1); - - if (waitTimeInMs > 0) - Thread.sleep(waitTimeInMs); - - variables = RunZeebeOperation.getVariablesStep(flowServiceTask.getRunScenario(), - flowServiceTask.getScenarioStep(), 0); - - /** This should be moved to the Camunda Engine implementation */ - /* C7 */ - if (externalTask != null) { - currentVariables = externalTask.getAllVariables(); - externalTaskService.complete(externalTask, variables); - } - /* C8 */ - if (jobClient != null) { - currentVariables = activatedJob.getVariablesAsMap(); - CompleteJobCommandStep1 completeCommand = jobClient.newCompleteCommand(activatedJob.getKey()); - CommandWrapper command = new RefactoredCommandWrapper((FinalCommandStep) completeCommand, - activatedJob.getDeadline(), activatedJob.toString(), exceptionHandlingStrategy); - - command.executeAsync(); - } + @Override + public int getCurrentNumberOfThreads() { + return trackActiveWorkers.getCounter() + trackAsynchronousWorkers.getCounter(); + } - flowServiceTask.runResult.registerAddStepExecution(); + /** + * Register the worker + */ - } catch (Exception e) { - logger.error("Error task[{}] PI[{}] : {}", flowServiceTask.getId(), - (externalTask != null ? externalTask.getProcessDefinitionKey() : activatedJob.getProcessInstanceKey()), - e.getMessage()); + private void registerWorker() { + BpmnEngine bpmnEngine = getRunScenario().getBpmnEngine(); - flowServiceTask.runResult.registerAddErrorStepExecution(); + Duration durationSleep = getScenarioStep().getWaitingTimeDuration(Duration.ZERO); + durationSleep = durationSleep.plusSeconds(10); - } - long end = System.currentTimeMillis(); + if (getRunScenario().getRunParameters().showLevelMonitoring()) { + logger.info("Start service TaskId[{}] Topic[{}] StreamEnabled:{} DurationSleep[{} ms]", + getScenarioStep().getTaskId(), getScenarioStep().getTopic(), getScenarioStep().isStreamEnabled(), + durationSleep.toMillis()); + } - if (getRunScenario().getRunParameters().isDeepTracking()) - trackActiveWorkers.movement(-1); + registeredTask = bpmnEngine.registerServiceTask(getId(), // workerId + getScenarioStep().getTopic(), // topic + getScenarioStep().isStreamEnabled(), // stream + durationSleep, // lock time + new SimpleDelayHandler(this), new FixedBackoffSupplier(getScenarioStep().getFixedBackOffDelay())); + } - if (getRunScenario().getRunParameters().showLevelInfo()) { - logger.info("Executed task[{}] in {} ms PI[{}] CurrentVariable {} Variable {} Sleep [{} s]", getId(), - end - begin, - (externalTask != null ? externalTask.getProcessDefinitionKey() : activatedJob.getProcessInstanceKey()), - currentVariables, variables, durationSleep.getSeconds()); + private static class TrackActiveWorker { + public int counter = 0; - } + public synchronized void movement(int movement) { + counter += movement; + } + public int getCounter() { + return counter; + } } - private void manageAsynchronousExecution(org.camunda.bpm.client.task.ExternalTask externalTask, - ExternalTaskService externalTaskService, - JobClient jobClient, - ActivatedJob activatedJob) { - if (getRunScenario().getRunParameters().isDeepTracking()) - trackAsynchronousWorkers.movement(1); - flowServiceTask.scheduler.schedule(new Runnable() { - @Override - public void run() { - manageWaitExecution(externalTask, externalTaskService, jobClient, activatedJob, 0); - if (getRunScenario().getRunParameters().isDeepTracking()) - trackAsynchronousWorkers.movement(-1); + /** + * C7, C8 Handler + */ + public class SimpleDelayHandler implements ExternalTaskHandler, JobHandler { + private final RunScenarioFlowServiceTask flowServiceTask; + private final Duration durationSleep; + + public SimpleDelayHandler(RunScenarioFlowServiceTask flowServiceTask) { + this.flowServiceTask = flowServiceTask; + durationSleep = flowServiceTask.getScenarioStep().getWaitingTimeDuration(Duration.ZERO); } - }, Instant.now().plusMillis(durationSleep.toMillis())); - } - private void manageAsynchronousLimitedExecution(org.camunda.bpm.client.task.ExternalTask externalTask, - ExternalTaskService externalTaskService, - JobClient jobClient, - ActivatedJob activatedJob) { - // we register - try { - flowServiceTask.semaphore.acquire(); - if (getRunScenario().getRunParameters().showLevelMonitoring()) { - logger.info("task[{}] Semaphore acquire", getId()); + /* C7 Management */ + @Override + public void execute(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService) { + switch (getScenarioStep().getModeExecution()) { + case CLASSICAL, WAIT -> + manageWaitExecution(externalTask, externalTaskService, null, null, durationSleep.toMillis()); + case THREAD, ASYNCHRONOUS -> manageAsynchronousExecution(externalTask, externalTaskService, null, null); + case THREADTOKEN, ASYNCHRONOUSLIMITED -> + manageAsynchronousLimitedExecution(externalTask, externalTaskService, null, null); + } } - } catch (Exception e) { - return; - } - // Ok, now we can run that asynchronous - flowServiceTask.scheduler.schedule(new Runnable() { + + /* C8 management */ @Override - public void run() { - manageWaitExecution(externalTask, externalTaskService, jobClient, activatedJob, 0); - flowServiceTask.semaphore.release(); - if (getRunScenario().getRunParameters().showLevelMonitoring()) { - logger.info("task[{}] Semaphore release", getId()); - } + public void handle(JobClient jobClient, ActivatedJob activatedJob) throws Exception { + switch (getScenarioStep().getModeExecution()) { + case CLASSICAL, WAIT -> + manageWaitExecution(null, null, jobClient, activatedJob, durationSleep.toMillis()); + case THREAD, ASYNCHRONOUS -> manageAsynchronousExecution(null, null, jobClient, activatedJob); + case THREADTOKEN, ASYNCHRONOUSLIMITED -> + manageAsynchronousLimitedExecution(null, null, jobClient, activatedJob); + } } - }, Instant.now().plusMillis(durationSleep.toMillis())); - } + private void manageWaitExecution(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService, + JobClient jobClient, + ActivatedJob activatedJob, + long waitTimeInMs) { + long begin = System.currentTimeMillis(); + Map<String, Object> variables = new HashMap<>(); + Map<String, Object> currentVariables = new HashMap<>(); + try { + if (getRunScenario().getRunParameters().isDeepTracking()) + trackActiveWorkers.movement(1); + + if (waitTimeInMs > 0) + Thread.sleep(waitTimeInMs); + + variables = RunZeebeOperation.getVariablesStep(flowServiceTask.getRunScenario(), + flowServiceTask.getScenarioStep(), 0); + + /** This should be moved to the Camunda Engine implementation */ + /* C7 */ + if (externalTask != null) { + currentVariables = externalTask.getAllVariables(); + externalTaskService.complete(externalTask, variables); + } + /* C8 */ + if (jobClient != null) { + currentVariables = activatedJob.getVariablesAsMap(); + CompleteJobCommandStep1 completeCommand = jobClient.newCompleteCommand(activatedJob.getKey()); + CommandWrapper command = new RefactoredCommandWrapper((FinalCommandStep) completeCommand, + activatedJob.getDeadline(), activatedJob.toString(), exceptionHandlingStrategy); + + command.executeAsync(); + } + + flowServiceTask.runResult.registerAddStepExecution(); + + } catch (Exception e) { + logger.error("Error task[{}] PI[{}] : {}", flowServiceTask.getId(), + (externalTask != null ? externalTask.getProcessDefinitionKey() : activatedJob.getProcessInstanceKey()), + e.getMessage()); + + flowServiceTask.runResult.registerAddErrorStepExecution(); + + } + long end = System.currentTimeMillis(); + + if (getRunScenario().getRunParameters().isDeepTracking()) + trackActiveWorkers.movement(-1); + + if (getRunScenario().getRunParameters().showLevelInfo()) { + logger.info("Executed task[{}] in {} ms PI[{}] CurrentVariable {} Variable {} Sleep [{} s]", getId(), + end - begin, + (externalTask != null ? externalTask.getProcessDefinitionKey() : activatedJob.getProcessInstanceKey()), + currentVariables, variables, durationSleep.getSeconds()); + + } - } + } + + private void manageAsynchronousExecution(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService, + JobClient jobClient, + ActivatedJob activatedJob) { + if (getRunScenario().getRunParameters().isDeepTracking()) + trackAsynchronousWorkers.movement(1); + flowServiceTask.scheduler.schedule(new Runnable() { + @Override + public void run() { + manageWaitExecution(externalTask, externalTaskService, jobClient, activatedJob, 0); + if (getRunScenario().getRunParameters().isDeepTracking()) + trackAsynchronousWorkers.movement(-1); + } + }, Instant.now().plusMillis(durationSleep.toMillis())); + } + + private void manageAsynchronousLimitedExecution(org.camunda.bpm.client.task.ExternalTask externalTask, + ExternalTaskService externalTaskService, + JobClient jobClient, + ActivatedJob activatedJob) { + // we register + try { + flowServiceTask.semaphore.acquire(); + if (getRunScenario().getRunParameters().showLevelMonitoring()) { + logger.info("task[{}] Semaphore acquire", getId()); + } + } catch (Exception e) { + return; + } + // Ok, now we can run that asynchronous + flowServiceTask.scheduler.schedule(new Runnable() { + @Override + public void run() { + manageWaitExecution(externalTask, externalTaskService, jobClient, activatedJob, 0); + flowServiceTask.semaphore.release(); + if (getRunScenario().getRunParameters().showLevelMonitoring()) { + logger.info("task[{}] Semaphore release", getId()); + } + } + }, Instant.now().plusMillis(durationSleep.toMillis())); + + } + + } } \ No newline at end of file diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java index c1cbbe2..c172353 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowStartEvent.java @@ -19,186 +19,186 @@ import java.util.stream.Collectors; public class RunScenarioFlowStartEvent extends RunScenarioFlowBasic { - private final TaskScheduler scheduler; - Logger logger = LoggerFactory.getLogger(RunScenarioFlowStartEvent.class); - StartEventRunnable startEventRunnable; - private boolean stopping; - private boolean isRunning; - /** - * Each time we run a batch of start, execution Number increase - */ - private int executionBatchNumber = 1; - - public RunScenarioFlowStartEvent(TaskScheduler scheduler, - ScenarioStep scenarioStep, - RunScenario runScenario, - RunResult runResult) { - super(scenarioStep, runScenario, runResult); - this.scheduler = scheduler; - } - - @Override - public String getTopic() { - return getScenarioStep().getTaskId(); - } - - @Override - public void execute() { - stopping = false; - isRunning = true; - - startEventRunnable = new StartEventRunnable(scheduler, getScenarioStep(), getRunScenario(), this, runResult); - - startEventRunnable.start(); - } - - @Override - public void pleaseStop() { - this.stopping = true; - } - - public RunScenarioFlowBasic.STATUS getStatus() { - if (!isRunning) - return RunScenarioFlowBasic.STATUS.STOPPED; - if (stopping) { - return RunScenarioFlowBasic.STATUS.STOPPING; + private final TaskScheduler scheduler; + Logger logger = LoggerFactory.getLogger(RunScenarioFlowStartEvent.class); + StartEventRunnable startEventRunnable; + private boolean stopping; + private boolean isRunning; + /** + * Each time we run a batch of start, execution Number increase + */ + private int executionBatchNumber = 1; + + public RunScenarioFlowStartEvent(TaskScheduler scheduler, + ScenarioStep scenarioStep, + RunScenario runScenario, + RunResult runResult) { + super(scenarioStep, runScenario, runResult); + this.scheduler = scheduler; } - return RunScenarioFlowBasic.STATUS.RUNNING; - } - - @Override - public int getCurrentNumberOfThreads() { - try { - return startEventRunnable == null ? 0 : startEventRunnable.getNumberOfRunningThreads(); - } catch (Exception e) { - // do nothing - logger.error("During getCurrentNumberOfThreads : {}", e); - return 0; + + @Override + public String getTopic() { + return getScenarioStep().getTaskId(); } - } - @Override - public RunResult getRunResult() { - return runResult; - } + @Override + public void execute() { + stopping = false; + isRunning = true; - public enum STATUS {RUNNING, STOPPING, STOPPED} + startEventRunnable = new StartEventRunnable(scheduler, getScenarioStep(), getRunScenario(), this, runResult); - /** - * StartEventRunnable - */ - class StartEventRunnable implements Runnable { + startEventRunnable.start(); + } - private final TaskScheduler scheduler; - private final ScenarioStep scenarioStep; - private final RunResult runResult; - private final RunScenario runScenario; - private final RunScenarioFlowStartEvent flowStartEvent; - - private int nbOverloaded = 0; - private int totalCreation = 0; - private int totalFailed = 0; - - private CreateProcessInstanceThread createProcessInstanceThread = null; - - public StartEventRunnable(TaskScheduler scheduler, - ScenarioStep scenarioStep, - RunScenario runScenario, - RunScenarioFlowStartEvent flowStartEvent, - RunResult runResult) { - this.scheduler = scheduler; - this.scenarioStep = scenarioStep; - this.runResult = runResult; - this.runScenario = runScenario; - this.flowStartEvent = flowStartEvent; + @Override + public void pleaseStop() { + this.stopping = true; } - /** - * Start it in a new tread - */ - public void start() { - scheduler.schedule(this, Instant.now()); + public RunScenarioFlowBasic.STATUS getStatus() { + if (!isRunning) + return RunScenarioFlowBasic.STATUS.STOPPED; + if (stopping) { + return RunScenarioFlowBasic.STATUS.STOPPING; + } + return RunScenarioFlowBasic.STATUS.RUNNING; } @Override - public void run() { - if (flowStartEvent.stopping) { - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("Stop now [" + getId() + "]"); - if (nbOverloaded > 0) - runResult.addError(scenarioStep, - - "Overloaded:" + nbOverloaded + " TotalCreation:" + totalCreation // total creation we see - + " TheoricNumberExpectred:" + (scenarioStep.getNumberOfExecutions() * executionBatchNumber) - // expected - - + " Process[" + scenarioStep.getProcessId() + "] Can't create PI at the required frequency"); - if (totalFailed > 0) - runResult.addError(scenarioStep, - "Failed " + totalFailed + " Process[" + scenarioStep.getProcessId() + "] Can't create PI "); - + public int getCurrentNumberOfThreads() { + try { + return startEventRunnable == null ? 0 : startEventRunnable.getNumberOfRunningThreads(); + } catch (Exception e) { + // do nothing + logger.error("During getCurrentNumberOfThreads : {}", e); + return 0; } - // notify my parent that I stop now - flowStartEvent.isRunning = false; - return; - } - executionBatchNumber++; - - Duration durationToCreateProcessInstances = Duration.parse(scenarioStep.getFrequency()); - - long begin = System.currentTimeMillis(); - boolean isOverloadSection = false; - - // generate process instance in multiple threads - - createProcessInstanceThread = new CreateProcessInstanceThread(executionBatchNumber, scenarioStep, runScenario, - runResult); - - // creates all process instances, return when finish OR when duration is reach - createProcessInstanceThread.createProcessInstances(durationToCreateProcessInstances); - - // Now collect data for the running time - totalCreation += createProcessInstanceThread.getTotalCreation(); - totalFailed += createProcessInstanceThread.getTotalFailed(); - List<String> listProcessInstances = createProcessInstanceThread.getListProcessInstances(); - long end = System.currentTimeMillis(); - - // do we have to stop the execution? - if (createProcessInstanceThread.isOverload()) { - // take too long to create the required process instance, so stop now. - nbOverloaded++; - isOverloadSection = true; - } - - // calculate the time to wait now - Duration durationToWait = durationToCreateProcessInstances.minusMillis(end - begin); - if (durationToWait.isNegative()) { - durationToWait = Duration.ZERO; - - } - - // report now - if (runScenario.getRunParameters().showLevelMonitoring() || createProcessInstanceThread.isOverload()) { - logger.info("Step #{}-{}" + " Create (real/scenario)[{}/{} {}]" // Overload marker - + " Failed[{}] in {} ms " // time of operation - + " Sleep[{} s] ", // end message - executionBatchNumber, getId(), createProcessInstanceThread.getTotalCreation(), - scenarioStep.getNumberOfExecutions(), (isOverloadSection ? "OVERLOAD" : ""), - createProcessInstanceThread.getTotalFailed(), (end - begin), durationToWait.getSeconds()); - } - if (runScenario.getRunParameters().showLevelInfo()) { - logger.info(" listPI(first20): " + listProcessInstances.stream().collect(Collectors.joining(","))); - - } - - // Wait to restart - scheduler.schedule(this, Instant.now().plusMillis(durationToWait.toMillis())); + } + @Override + public RunResult getRunResult() { + return runResult; } - public int getNumberOfRunningThreads() { - return createProcessInstanceThread == null ? 0 : createProcessInstanceThread.getNumberOfRunningThreads(); + public enum STATUS {RUNNING, STOPPING, STOPPED} + + /** + * StartEventRunnable + */ + class StartEventRunnable implements Runnable { + + private final TaskScheduler scheduler; + private final ScenarioStep scenarioStep; + private final RunResult runResult; + private final RunScenario runScenario; + private final RunScenarioFlowStartEvent flowStartEvent; + + private int nbOverloaded = 0; + private int totalCreation = 0; + private int totalFailed = 0; + + private CreateProcessInstanceThread createProcessInstanceThread = null; + + public StartEventRunnable(TaskScheduler scheduler, + ScenarioStep scenarioStep, + RunScenario runScenario, + RunScenarioFlowStartEvent flowStartEvent, + RunResult runResult) { + this.scheduler = scheduler; + this.scenarioStep = scenarioStep; + this.runResult = runResult; + this.runScenario = runScenario; + this.flowStartEvent = flowStartEvent; + } + + /** + * Start it in a new tread + */ + public void start() { + scheduler.schedule(this, Instant.now()); + } + + @Override + public void run() { + if (flowStartEvent.stopping) { + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("Stop now [" + getId() + "]"); + if (nbOverloaded > 0) + runResult.addError(scenarioStep, + + "Overloaded:" + nbOverloaded + " TotalCreation:" + totalCreation // total creation we see + + " TheoricNumberExpectred:" + (scenarioStep.getNumberOfExecutions() * executionBatchNumber) + // expected + + + " Process[" + scenarioStep.getProcessId() + "] Can't create PI at the required frequency"); + if (totalFailed > 0) + runResult.addError(scenarioStep, + "Failed " + totalFailed + " Process[" + scenarioStep.getProcessId() + "] Can't create PI "); + + } + // notify my parent that I stop now + flowStartEvent.isRunning = false; + return; + } + executionBatchNumber++; + + Duration durationToCreateProcessInstances = Duration.parse(scenarioStep.getFrequency()); + + long begin = System.currentTimeMillis(); + boolean isOverloadSection = false; + + // generate process instance in multiple threads + + createProcessInstanceThread = new CreateProcessInstanceThread(executionBatchNumber, scenarioStep, runScenario, + runResult); + + // creates all process instances, return when finish OR when duration is reach + createProcessInstanceThread.createProcessInstances(durationToCreateProcessInstances); + + // Now collect data for the running time + totalCreation += createProcessInstanceThread.getTotalCreation(); + totalFailed += createProcessInstanceThread.getTotalFailed(); + List<String> listProcessInstances = createProcessInstanceThread.getListProcessInstances(); + long end = System.currentTimeMillis(); + + // do we have to stop the execution? + if (createProcessInstanceThread.isOverload()) { + // take too long to create the required process instance, so stop now. + nbOverloaded++; + isOverloadSection = true; + } + + // calculate the time to wait now + Duration durationToWait = durationToCreateProcessInstances.minusMillis(end - begin); + if (durationToWait.isNegative()) { + durationToWait = Duration.ZERO; + + } + + // report now + if (runScenario.getRunParameters().showLevelMonitoring() || createProcessInstanceThread.isOverload()) { + logger.info("Step #{}-{}" + " Create (real/scenario)[{}/{} {}]" // Overload marker + + " Failed[{}] in {} ms " // time of operation + + " Sleep[{} s] ", // end message + executionBatchNumber, getId(), createProcessInstanceThread.getTotalCreation(), + scenarioStep.getNumberOfExecutions(), (isOverloadSection ? "OVERLOAD" : ""), + createProcessInstanceThread.getTotalFailed(), (end - begin), durationToWait.getSeconds()); + } + if (runScenario.getRunParameters().showLevelInfo()) { + logger.info(" listPI(first20): " + listProcessInstances.stream().collect(Collectors.joining(","))); + + } + + // Wait to restart + scheduler.schedule(this, Instant.now().plusMillis(durationToWait.toMillis())); + + } + + public int getNumberOfRunningThreads() { + return createProcessInstanceThread == null ? 0 : createProcessInstanceThread.getNumberOfRunningThreads(); + } } - } } diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowUserTask.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowUserTask.java index 2e37042..f9044c7 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowUserTask.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowUserTask.java @@ -15,124 +15,124 @@ public class RunScenarioFlowUserTask extends RunScenarioFlowBasic { - private final TaskScheduler scheduler; - Logger logger = LoggerFactory.getLogger(RunScenarioFlowUserTask.class); - private STATUS status = STATUS.RUNNING; - - public RunScenarioFlowUserTask(TaskScheduler scheduler, - ScenarioStep scenarioStep, - int index, - RunScenario runScenario, - RunResult runResult) { - super(scenarioStep, runScenario, runResult); - this.scheduler = scheduler; - - } - - @Override - public String getTopic() { - return getScenarioStep().getTaskId(); - } - - @Override - public void execute() { - RunScenarioFlowUserTask.UserTaskRunnable startEventRunnable = new RunScenarioFlowUserTask.UserTaskRunnable( - scheduler, getScenarioStep(), runResult, getRunScenario(), this); - scheduler.schedule(startEventRunnable, Instant.now()); - } - - @Override - public STATUS getStatus() { - return status; - } - - @Override - public int getCurrentNumberOfThreads() { - return 1; - } - - @Override - public void pleaseStop() { - logger.info("Ask Stopping [" + getId() + "]"); - - status = STATUS.STOPPING; - // wait 1 second - try { - Thread.sleep(1000); - } catch (Exception e) { - // do nothing + private final TaskScheduler scheduler; + Logger logger = LoggerFactory.getLogger(RunScenarioFlowUserTask.class); + private STATUS status = STATUS.RUNNING; + + public RunScenarioFlowUserTask(TaskScheduler scheduler, + ScenarioStep scenarioStep, + int index, + RunScenario runScenario, + RunResult runResult) { + super(scenarioStep, runScenario, runResult); + this.scheduler = scheduler; + + } + + @Override + public String getTopic() { + return getScenarioStep().getTaskId(); } - } - /** - * StartEventRunnable - */ - class UserTaskRunnable implements Runnable { + @Override + public void execute() { + RunScenarioFlowUserTask.UserTaskRunnable startEventRunnable = new RunScenarioFlowUserTask.UserTaskRunnable( + scheduler, getScenarioStep(), runResult, getRunScenario(), this); + scheduler.schedule(startEventRunnable, Instant.now()); + } - private final TaskScheduler scheduler; - private final ScenarioStep scenarioStep; - private final RunResult runResult; - private final RunScenario runScenario; - private final RunScenarioFlowUserTask flowUserTask; - - private final int nbOverloaded = 0; - private final int totalCreation = 0; - private final int totalCreationGoal = 0; - private final int totalFailed = 0; - - public UserTaskRunnable(TaskScheduler scheduler, - ScenarioStep scenarioStep, - RunResult runResult, - RunScenario runScenario, - RunScenarioFlowUserTask flowUserTask) { - this.scheduler = scheduler; - this.scenarioStep = scenarioStep; - this.runResult = runResult; - this.runScenario = runScenario; - this.flowUserTask = flowUserTask; + @Override + public STATUS getStatus() { + return status; } @Override - public void run() { - Long waitingTimeInMs = null; - if (getScenarioStep().getWaitingTime() != null) { - Duration duration = Duration.parse(getScenarioStep().getWaitingTime()); - waitingTimeInMs = duration.toMillis(); - } - if (waitingTimeInMs == null) - waitingTimeInMs = 1L; - - long beginTimeWait = System.currentTimeMillis(); - while (flowUserTask.status == STATUS.RUNNING) { + public int getCurrentNumberOfThreads() { + return 1; + } + + @Override + public void pleaseStop() { + logger.info("Ask Stopping [" + getId() + "]"); + + status = STATUS.STOPPING; + // wait 1 second try { - List<String> listActivities; - do { + Thread.sleep(1000); + } catch (Exception e) { + // do nothing + } + } - listActivities = getRunScenario().getBpmnEngine().searchUserTasks(getScenarioStep().getTaskId(), 10); + /** + * StartEventRunnable + */ + class UserTaskRunnable implements Runnable { + + private final TaskScheduler scheduler; + private final ScenarioStep scenarioStep; + private final RunResult runResult; + private final RunScenario runScenario; + private final RunScenarioFlowUserTask flowUserTask; + + private final int nbOverloaded = 0; + private final int totalCreation = 0; + private final int totalCreationGoal = 0; + private final int totalFailed = 0; + + public UserTaskRunnable(TaskScheduler scheduler, + ScenarioStep scenarioStep, + RunResult runResult, + RunScenario runScenario, + RunScenarioFlowUserTask flowUserTask) { + this.scheduler = scheduler; + this.scenarioStep = scenarioStep; + this.runResult = runResult; + this.runScenario = runScenario; + this.flowUserTask = flowUserTask; + } - if (listActivities.isEmpty()) { - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - // nothing to do here - } + @Override + public void run() { + Long waitingTimeInMs = null; + if (getScenarioStep().getWaitingTime() != null) { + Duration duration = Duration.parse(getScenarioStep().getWaitingTime()); + waitingTimeInMs = duration.toMillis(); } - } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); - - if (!listActivities.isEmpty()) { - for (String taskInstanceId : listActivities) { - getRunScenario().getBpmnEngine() - .executeUserTask(taskInstanceId, getScenarioStep().getUserId(), - RunZeebeOperation.getVariablesStep(getRunScenario(), getScenarioStep(), 0)); + if (waitingTimeInMs == null) + waitingTimeInMs = 1L; + + long beginTimeWait = System.currentTimeMillis(); + while (flowUserTask.status == STATUS.RUNNING) { + try { + List<String> listActivities; + do { + + listActivities = getRunScenario().getBpmnEngine().searchUserTasks(getScenarioStep().getTaskId(), 10); + + if (listActivities.isEmpty()) { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + // nothing to do here + } + } + } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); + + if (!listActivities.isEmpty()) { + for (String taskInstanceId : listActivities) { + getRunScenario().getBpmnEngine() + .executeUserTask(taskInstanceId, getScenarioStep().getUserId(), + RunZeebeOperation.getVariablesStep(getRunScenario(), getScenarioStep(), 0)); + } + } + } catch (AutomatorException e) { + logger.error("Error task[" + getScenarioStep().getTaskId() + " : " + e.getMessage()); + + getRunResult().registerAddErrorStepExecution(); + } } - } - } catch (AutomatorException e) { - logger.error("Error task[" + getScenarioStep().getTaskId() + " : " + e.getMessage()); - - getRunResult().registerAddErrorStepExecution(); + flowUserTask.status = STATUS.STOPPED; } - } - flowUserTask.status = STATUS.STOPPED; } - } } diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java index 6692cd3..62ce0a5 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlows.java @@ -16,378 +16,372 @@ import org.slf4j.LoggerFactory; import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; public class RunScenarioFlows { - private final ServiceAccess serviceAccess; - private final RunScenario runScenario; - private final Map<String, Long> previousValueMap = new HashMap<>(); - Logger logger = LoggerFactory.getLogger(RunScenarioFlows.class); - - public RunScenarioFlows(ServiceAccess serviceAccess, RunScenario runScenario) { - this.serviceAccess = serviceAccess; - this.runScenario = runScenario; - } - - /** - * Execute the scenario flow - * - * @param runResult result to populate - */ - public void execute(RunResult runResult) { - // Create one executor per flow - RunScenarioWarmingUp runScenarioWarmingUp = new RunScenarioWarmingUp(serviceAccess, runScenario); - Map<String, RunResult.RecordCreationPI> recordCreationPIMap = new HashMap<>(); - if (runScenario.getScenario().getFlowControl() == null) { - runResult.addError(null, - "Scenario does not declare a [FlowControl] section. This section is mandatory for a Flow Scenario"); - return; + private final ServiceAccess serviceAccess; + private final RunScenario runScenario; + private final Map<String, Long> previousValueMap = new HashMap<>(); + Logger logger = LoggerFactory.getLogger(RunScenarioFlows.class); + + public RunScenarioFlows(ServiceAccess serviceAccess, RunScenario runScenario) { + this.serviceAccess = serviceAccess; + this.runScenario = runScenario; } - List<ScenarioFlowControl.Objective> listObjectives = runScenario.getScenario().getFlowControl().getObjectives(); - if (listObjectives == null) - listObjectives = Collections.emptyList(); - - RunObjectives runObjectives = new RunObjectives(listObjectives, runScenario.getBpmnEngine(), recordCreationPIMap); - - logger.info("ScenarioFlow: ------ WarmingUp"); - runScenarioWarmingUp.warmingUp(runResult); - - Date startTestDate = new Date(); - runObjectives.setStartDate(startTestDate); - - logger.info("ScenarioFlow: ------ Start"); - List<RunScenarioFlowBasic> listFlows = startExecution(runScenarioWarmingUp.getListWarmingUpTask()); - - waitEndExecution(runObjectives, startTestDate, listFlows); - - Date endTestDate = new Date(); - runObjectives.setEndDate(endTestDate); - logger.info("ScenarioFlow: ------ Stop"); - - stopExecution(listFlows); - - logger.info("ScenarioFlow: ------ CollectData"); - collectInformation(listFlows, runResult, recordCreationPIMap); - - // Check with Objective now - logger.info("ScenarioFlow: ------ CheckObjectives"); - checkObjectives(runObjectives, startTestDate, endTestDate, runResult); - - logger.info("ScenarioFlow: ------ TheEnd"); - } - - /** - * Start execution - * - * @return list of Flow started - */ - private List<RunScenarioFlowBasic> startExecution(List<RunScenarioFlowBasic> listWarmingTask) { - List<RunScenarioFlowBasic> listFlows = new ArrayList<>(); - for (ScenarioStep scenarioStep : runScenario.getScenario().getFlows()) { - switch (scenarioStep.getType()) { - case STARTEVENT -> { - if (!runScenario.getRunParameters().isCreation()) { - logger.info("According configuration, STARTEVENT[" + scenarioStep.getProcessId() + "] is fully disabled"); - } else { - RunScenarioFlowStartEvent runStartEvent = new RunScenarioFlowStartEvent( - serviceAccess.getTaskScheduler(scenarioStep.getProcessId()), scenarioStep, runScenario, - new RunResult(runScenario)); - runStartEvent.execute(); - listFlows.add(runStartEvent); + /** + * Execute the scenario flow + * + * @param runResult result to populate + */ + public void execute(RunResult runResult) { + // Create one executor per flow + RunScenarioWarmingUp runScenarioWarmingUp = new RunScenarioWarmingUp(serviceAccess, runScenario); + Map<String, RunResult.RecordCreationPI> recordCreationPIMap = new HashMap<>(); + if (runScenario.getScenario().getFlowControl() == null) { + runResult.addError(null, + "Scenario does not declare a [FlowControl] section. This section is mandatory for a Flow Scenario"); + return; } - } - - case SERVICETASK -> { - Optional<RunScenarioFlowBasic> runServiceTaskOp = getFromList(listWarmingTask, scenarioStep.getTopic()); - - if (!runScenario.getRunParameters().isServiceTask()) { - logger.info("According configuration, SERVICETASK[{}] is fully disabled", scenarioStep.getTopic()); - if (runServiceTaskOp.isPresent()) - runServiceTaskOp.get().pleaseStop(); - } else if (runScenario.getRunParameters().blockExecutionServiceTask(scenarioStep.getTopic())) { - logger.info("According configuration, SERVICETASK[{}] is disabled (only acceptable {})", - scenarioStep.getTopic(), runScenario.getRunParameters().getFilterServiceTask()); - if (runServiceTaskOp.isPresent()) - runServiceTaskOp.get().pleaseStop(); - } else { - if (runServiceTaskOp.isEmpty()) { - - RunScenarioFlowServiceTask runServiceTask = new RunScenarioFlowServiceTask( - serviceAccess.getTaskScheduler("serviceTask"), scenarioStep, runScenario, new RunResult(runScenario)); - - runServiceTask.execute(); - listFlows.add(runServiceTask); - } else { - listFlows.add(runServiceTaskOp.get()); - } - } - } - - case USERTASK -> { - Optional<RunScenarioFlowBasic> runUserTaskOpt = getFromList(listWarmingTask, scenarioStep.getTaskId()); - - if (!runScenario.getRunParameters().isUserTask()) { - logger.info("According configuration, USERTASK[{}] is fully disabled", scenarioStep.getTaskId()); - if (runUserTaskOpt.isPresent()) - runUserTaskOpt.get().pleaseStop(); - } else { - if (runUserTaskOpt.isEmpty()) { - - RunScenarioFlowUserTask runUserTask = new RunScenarioFlowUserTask( - serviceAccess.getTaskScheduler("userTask"), scenarioStep, 0, runScenario, new RunResult(runScenario)); - - runUserTask.execute(); - listFlows.add(runUserTask); - } else { - listFlows.add(runUserTaskOpt.get()); - } - } - } - } - } - return listFlows; - } - - private Optional<RunScenarioFlowBasic> getFromList(List<RunScenarioFlowBasic> listTasks, String topic) { - return listTasks.stream().filter(t -> t.getTopic().equals(topic)).findFirst(); - } - - /** - * Wait end of execution. according to the time in the scenario, wait this time - * - * @param runObjectives checkObjectif: we may have a Flow Objectives - * @param listFlows list of flows to monitor the execution - */ - private void waitEndExecution(RunObjectives runObjectives, Date startTestDate, List<RunScenarioFlowBasic> listFlows) { - // Then wait the delay, and kill everything after - Duration durationExecution = runScenario.getScenario().getFlowControl().getDuration(); - Duration durationWarmingUp = Duration.ZERO; - // if this server didn't do the warmingUp, then other server did it: we have to keep this time into account - if (!runScenario.getRunParameters().isWarmingUp()) { - // is the scenario has a warming up defined? - if (runScenario.getScenario().getWarmingUp() != null) - durationWarmingUp = runScenario.getScenario().getWarmingUp().getDuration(); + + List<ScenarioFlowControl.Objective> listObjectives = runScenario.getScenario().getFlowControl().getObjectives(); + if (listObjectives == null) + listObjectives = Collections.emptyList(); + + RunObjectives runObjectives = new RunObjectives(listObjectives, runScenario.getBpmnEngine(), recordCreationPIMap); + + logger.info("ScenarioFlow: ------ WarmingUp"); + runScenarioWarmingUp.warmingUp(runResult); + + Date startTestDate = new Date(); + runObjectives.setStartDate(startTestDate); + + logger.info("ScenarioFlow: ------ Start"); + List<RunScenarioFlowBasic> listFlows = startExecution(runScenarioWarmingUp.getListWarmingUpTask()); + + waitEndExecution(runObjectives, startTestDate, listFlows); + + Date endTestDate = new Date(); + runObjectives.setEndDate(endTestDate); + logger.info("ScenarioFlow: ------ Stop"); + + stopExecution(listFlows); + + logger.info("ScenarioFlow: ------ CollectData"); + collectInformation(listFlows, runResult, recordCreationPIMap); + + // Check with Objective now + logger.info("ScenarioFlow: ------ CheckObjectives"); + checkObjectives(runObjectives, startTestDate, endTestDate, runResult); + + logger.info("ScenarioFlow: ------ TheEnd"); } - long endTimeExpected = - startTestDate.getTime() + durationExecution.getSeconds() * 1000 + durationWarmingUp.getSeconds() * 1000; - - logger.info("Start: FixedWarmingUp {} s ExecutionDuration {} s (total {} s)", durationWarmingUp.getSeconds(), - durationExecution.getSeconds(), durationWarmingUp.getSeconds() + durationExecution.getSeconds()); - - while (System.currentTimeMillis() < endTimeExpected) { - long currentTime = System.currentTimeMillis(); - long sleepTime = Math.min(30 * 1000L, endTimeExpected - currentTime); - try { - Thread.sleep(sleepTime); - } catch (InterruptedException e) { - } - int advancement = (int) (100.0 * (currentTime - startTestDate.getTime()) / (endTimeExpected - - startTestDate.getTime())); - runObjectives.heartBeat(); - logRealTime(listFlows, endTimeExpected - System.currentTimeMillis(), advancement); + /** + * Start execution + * + * @return list of Flow started + */ + private List<RunScenarioFlowBasic> startExecution(List<RunScenarioFlowBasic> listWarmingTask) { + List<RunScenarioFlowBasic> listFlows = new ArrayList<>(); + for (ScenarioStep scenarioStep : runScenario.getScenario().getFlows()) { + switch (scenarioStep.getType()) { + case STARTEVENT -> { + if (!runScenario.getRunParameters().isCreation()) { + logger.info("According configuration, STARTEVENT[" + scenarioStep.getProcessId() + "] is fully disabled"); + } else { + RunScenarioFlowStartEvent runStartEvent = new RunScenarioFlowStartEvent( + serviceAccess.getTaskScheduler(scenarioStep.getProcessId()), scenarioStep, runScenario, + new RunResult(runScenario)); + runStartEvent.execute(); + listFlows.add(runStartEvent); + } + } + + case SERVICETASK -> { + Optional<RunScenarioFlowBasic> runServiceTaskOp = getFromList(listWarmingTask, scenarioStep.getTopic()); + + if (!runScenario.getRunParameters().isServiceTask()) { + logger.info("According configuration, SERVICETASK[{}] is fully disabled", scenarioStep.getTopic()); + if (runServiceTaskOp.isPresent()) + runServiceTaskOp.get().pleaseStop(); + } else if (runScenario.getRunParameters().blockExecutionServiceTask(scenarioStep.getTopic())) { + logger.info("According configuration, SERVICETASK[{}] is disabled (only acceptable {})", + scenarioStep.getTopic(), runScenario.getRunParameters().getFilterServiceTask()); + if (runServiceTaskOp.isPresent()) + runServiceTaskOp.get().pleaseStop(); + } else { + if (runServiceTaskOp.isEmpty()) { + + RunScenarioFlowServiceTask runServiceTask = new RunScenarioFlowServiceTask( + serviceAccess.getTaskScheduler("serviceTask"), scenarioStep, runScenario, new RunResult(runScenario)); + + runServiceTask.execute(); + listFlows.add(runServiceTask); + } else { + listFlows.add(runServiceTaskOp.get()); + } + } + } + + case USERTASK -> { + Optional<RunScenarioFlowBasic> runUserTaskOpt = getFromList(listWarmingTask, scenarioStep.getTaskId()); + + if (!runScenario.getRunParameters().isUserTask()) { + logger.info("According configuration, USERTASK[{}] is fully disabled", scenarioStep.getTaskId()); + if (runUserTaskOpt.isPresent()) + runUserTaskOpt.get().pleaseStop(); + } else { + if (runUserTaskOpt.isEmpty()) { + + RunScenarioFlowUserTask runUserTask = new RunScenarioFlowUserTask( + serviceAccess.getTaskScheduler("userTask"), scenarioStep, 0, runScenario, new RunResult(runScenario)); + + runUserTask.execute(); + listFlows.add(runUserTask); + } else { + listFlows.add(runUserTaskOpt.get()); + } + } + } + } + } + return listFlows; } - } - - /** - * Stop the execution - * - * @param listFlows list of flows to stop - */ - private void stopExecution(List<RunScenarioFlowBasic> listFlows) { - logger.info("End - wait end FlowBasic"); - // now, stop all executions - for (RunScenarioFlowBasic flowBasic : listFlows) { - flowBasic.pleaseStop(); + + private Optional<RunScenarioFlowBasic> getFromList(List<RunScenarioFlowBasic> listTasks, String topic) { + return listTasks.stream().filter(t -> t.getTopic().equals(topic)).findFirst(); } - // wait the end of all executions - long numberOfActives = listFlows.size(); - int count = 0; - while (numberOfActives > 0 && count < 100) { - count++; - numberOfActives = listFlows.stream() - .filter(t -> !t.getStatus().equals(RunScenarioFlowBasic.STATUS.STOPPED)) - .count(); - if (numberOfActives > 0) - try { - Thread.sleep(2000); - } catch (Exception e) { - numberOfActives = 0; + + /** + * Wait end of execution. according to the time in the scenario, wait this time + * + * @param runObjectives checkObjectif: we may have a Flow Objectives + * @param listFlows list of flows to monitor the execution + */ + private void waitEndExecution(RunObjectives runObjectives, Date startTestDate, List<RunScenarioFlowBasic> listFlows) { + // Then wait the delay, and kill everything after + Duration durationExecution = runScenario.getScenario().getFlowControl().getDuration(); + Duration durationWarmingUp = Duration.ZERO; + // if this server didn't do the warmingUp, then other server did it: we have to keep this time into account + if (!runScenario.getRunParameters().isWarmingUp()) { + // is the scenario has a warming up defined? + if (runScenario.getScenario().getWarmingUp() != null) + durationWarmingUp = runScenario.getScenario().getWarmingUp().getDuration(); + } + + long endTimeExpected = + startTestDate.getTime() + durationExecution.getSeconds() * 1000 + durationWarmingUp.getSeconds() * 1000; + + logger.info("Start: FixedWarmingUp {} s ExecutionDuration {} s (total {} s)", durationWarmingUp.getSeconds(), + durationExecution.getSeconds(), durationWarmingUp.getSeconds() + durationExecution.getSeconds()); + + while (System.currentTimeMillis() < endTimeExpected) { + long currentTime = System.currentTimeMillis(); + long sleepTime = Math.min(30 * 1000L, endTimeExpected - currentTime); + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + } + int advancement = (int) (100.0 * (currentTime - startTestDate.getTime()) / (endTimeExpected + - startTestDate.getTime())); + runObjectives.heartBeat(); + logRealTime(listFlows, endTimeExpected - System.currentTimeMillis(), advancement); } } - } - - /** - * Collect multiple information - * - * @param listFlows list of flow - * @param runResult runResult to populate - * @param recordCreationPIMap statistics - */ - private void collectInformation(List<RunScenarioFlowBasic> listFlows, - RunResult runResult, - Map<String, RunResult.RecordCreationPI> recordCreationPIMap) { - // Collect information - logger.info("CollectData : listFlows[{}]", listFlows.size()); - for (RunScenarioFlowBasic flowBasic : listFlows) { - RunResult runResultFlow = flowBasic.getRunResult(); - runResult.add(runResultFlow); - if (flowBasic instanceof RunScenarioFlowStartEvent) { - String processId = flowBasic.getScenarioStep().getProcessId(); - RunResult.RecordCreationPI recordFlow = runResultFlow.getRecordCreationPI().get(processId); - RunResult.RecordCreationPI recordCreationPI = recordCreationPIMap.getOrDefault(processId, - new RunResult.RecordCreationPI(processId)); - - recordCreationPI.add(recordFlow); - recordCreationPIMap.put(processId, recordCreationPI); - logger.info("CollectData : StartEvent, processId[{}] PICreated[{}] PIFailed[{}]", processId, - recordFlow.nbCreated, recordFlow.nbFailed); - } - } - } - - /** - * Check the objective of the scenario - * - * @param startTestDate date when the test start - * @param endTestDate date when the test end - * @param runResult result to populate - */ - private void checkObjectives(RunObjectives runObjectives, Date startTestDate, Date endTestDate, RunResult runResult) { - - // Objectives ask Operate, which get the result with a delay. So, wait 1 mn - logger.info("CollectingData... (sleep 30s)"); - try { - Thread.sleep(30 * 1000L); - } catch (InterruptedException e) { - // do nothing + + /** + * Stop the execution + * + * @param listFlows list of flows to stop + */ + private void stopExecution(List<RunScenarioFlowBasic> listFlows) { + logger.info("End - wait end FlowBasic"); + // now, stop all executions + for (RunScenarioFlowBasic flowBasic : listFlows) { + flowBasic.pleaseStop(); + } + // wait the end of all executions + long numberOfActives = listFlows.size(); + int count = 0; + while (numberOfActives > 0 && count < 100) { + count++; + numberOfActives = listFlows.stream() + .filter(t -> !t.getStatus().equals(RunScenarioFlowBasic.STATUS.STOPPED)) + .count(); + if (numberOfActives > 0) + try { + Thread.sleep(2000); + } catch (Exception e) { + numberOfActives = 0; + } + } } - List<RunObjectives.ObjectiveResult> listCheckResult = runObjectives.check(); - for (RunObjectives.ObjectiveResult checkResult : listCheckResult) { - if (checkResult.success) { - logger.info("Objective: SUCCESS type[{}] label[{}} processId[{}] reach/objective {}/{} analysis [{}}", - checkResult.objective.type, checkResult.objective.label, checkResult.objective.processId, - checkResult.recordedSuccessValue, checkResult.objective.value, checkResult.analysis); - } else { - // do not need to log the error, already done - runResult.addError(null, - "Objective: FAIL " + checkResult.objective.getInformation() + " type[" + checkResult.objective.type - + "] processId[" + checkResult.objective.processId // ProcessID - + "] reach/objective " + checkResult.recordedSuccessValue // Reach - + "/" + checkResult.objective.value // Objective - + " " + checkResult.analysis); - } + /** + * Collect multiple information + * + * @param listFlows list of flow + * @param runResult runResult to populate + * @param recordCreationPIMap statistics + */ + private void collectInformation(List<RunScenarioFlowBasic> listFlows, + RunResult runResult, + Map<String, RunResult.RecordCreationPI> recordCreationPIMap) { + // Collect information + logger.info("CollectData : listFlows[{}]", listFlows.size()); + for (RunScenarioFlowBasic flowBasic : listFlows) { + RunResult runResultFlow = flowBasic.getRunResult(); + runResult.merge(runResultFlow); + if (flowBasic instanceof RunScenarioFlowStartEvent) { + String processId = flowBasic.getScenarioStep().getProcessId(); + RunResult.RecordCreationPI recordFlow = runResultFlow.getRecordCreationPI().get(processId); + RunResult.RecordCreationPI recordCreationPI = recordCreationPIMap.getOrDefault(processId, + new RunResult.RecordCreationPI(processId)); + + recordCreationPI.add(recordFlow); + recordCreationPIMap.put(processId, recordCreationPI); + logger.info("CollectData : StartEvent, processId[{}] PICreated[{}] PIFailed[{}]", processId, + recordFlow.nbCreated, recordFlow.nbFailed); + } + } } - } - - /** - * Log to see the advancement - * - * @param listFlows list flows running - * @param percentAdvancement percentAdvancement of the test, according the timeframe - */ - private void logRealTime(List<RunScenarioFlowBasic> listFlows, long timeToFinishInMs, int percentAdvancement) { - logger.info("------------ Log advancement at {} ----- {} %, end in {} s", new Date(), percentAdvancement, - timeToFinishInMs / 1000); - - for (RunScenarioFlowBasic flowBasic : listFlows) { - - RunResult runResultFlow = flowBasic.getRunResult(); - int currentNumberOfThreads = flowBasic.getCurrentNumberOfThreads(); - // logs only flow with a result or currently active - if (runResultFlow.getRecordCreationPIAllProcesses() + runResultFlow.getNumberOfSteps() - + runResultFlow.getNumberOfErrorSteps() == 0 && currentNumberOfThreads == 0) - continue; - long previousValue = previousValueMap.getOrDefault(flowBasic.getId(), 0L); - - ScenarioStep scenarioStep = flowBasic.getScenarioStep(); - String key = "[" + flowBasic.getId() + "] " + flowBasic.getStatus().toString() + " currentNbThreads[" - + currentNumberOfThreads + "] "; - key += switch (scenarioStep.getType()) { - case STARTEVENT -> "PI[" + runResultFlow.getRecordCreationPI() + "] delta[" + ( - runResultFlow.getRecordCreationPI().get(flowBasic.getScenarioStep().getProcessId()).nbCreated - - previousValue) + "]"; - case SERVICETASK -> - "StepsExecuted[" + runResultFlow.getNumberOfSteps() + "] delta [" + (runResultFlow.getNumberOfSteps() - - previousValue) + "] StepsErrors[" + runResultFlow.getNumberOfErrorSteps() + "]"; - case USERTASK -> - "StepsExecuted[" + runResultFlow.getNumberOfSteps() + "] delta [" + (runResultFlow.getNumberOfSteps() - - previousValue) + "] StepsErrors[" + runResultFlow.getNumberOfErrorSteps() + "]"; - - default -> "]"; - }; - logger.info(key); - switch (scenarioStep.getType()) { - case STARTEVENT -> previousValueMap.put(flowBasic.getId(), - runResultFlow.getRecordCreationPI().get(flowBasic.getScenarioStep().getProcessId()).nbCreated); - - case SERVICETASK -> previousValueMap.put(flowBasic.getId(), (long) runResultFlow.getNumberOfSteps()); - - case USERTASK -> previousValueMap.put(flowBasic.getId(), (long) runResultFlow.getNumberOfSteps()); - - default -> { - } - } + /** + * Check the objective of the scenario + * + * @param startTestDate date when the test start + * @param endTestDate date when the test end + * @param runResult result to populate + */ + private void checkObjectives(RunObjectives runObjectives, Date startTestDate, Date endTestDate, RunResult runResult) { + + // Objectives ask Operate, which get the result with a delay. So, wait 1 mn + logger.info("CollectingData... (sleep 30s)"); + try { + Thread.sleep(30 * 1000L); + } catch (InterruptedException e) { + // do nothing + } + + List<RunObjectives.ObjectiveResult> listCheckResult = runObjectives.check(); + for (RunObjectives.ObjectiveResult checkResult : listCheckResult) { + if (checkResult.success) { + logger.info("Objective: SUCCESS type[{}] label[{}} processId[{}] reach/objective {}/{} analysis [{}}", + checkResult.objective.type, checkResult.objective.label, checkResult.objective.processId, + checkResult.recordedSuccessValue, checkResult.objective.value, checkResult.analysis); + } else { + // do not need to log the error, already done + runResult.addError(null, + "Objective: FAIL " + checkResult.objective.getInformation() + " type[" + checkResult.objective.type + + "] processId[" + checkResult.objective.processId // ProcessID + + "] reach/objective " + checkResult.recordedSuccessValue // Reach + + "/" + checkResult.objective.value // Objective + + " " + checkResult.analysis); + } + } } - int nbThreadsServiceTask = 0; - int nbThreadsAutomator = 0; - int nbThreadsTimeWaiting = 0; - int nbThreadsWaiting = 0; - int nbThreadsTimeRunnable = 0; - for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) { - boolean isZeebe = false; - boolean isServiceTask = false; - boolean isAutomator = false; - for (StackTraceElement ste : entry.getValue()) { - if (ste.getClassName().contains("io.camunda")) - isZeebe = true; - else if (ste.getClassName().contains(RunScenarioFlowServiceTask.SimpleDelayHandler.class.getName())) - isServiceTask = true; - else if (ste.getClassName().contains(".automator.")) - isAutomator = true; - - // org.camunda.automator.engine.flow.RunScenarioFlowServiceTask$SimpleDelayCompletionHandler - } - if (!isZeebe && !isServiceTask && !isAutomator) - continue; - - if (isServiceTask) - nbThreadsServiceTask++; - else if (isAutomator) - nbThreadsAutomator++; - else - // TIME_WAITING: typical for the FlowServiceTask with a sleep - if (entry.getKey().getState().equals(Thread.State.TIMED_WAITING)) { - // is the thread is running the service task (with a Thread.sleep? - nbThreadsTimeWaiting++; - } else if (entry.getKey().getState().equals(Thread.State.WAITING)) { - nbThreadsTimeWaiting++; - } else if (entry.getKey().getState().equals(Thread.State.RUNNABLE)) { - nbThreadsTimeRunnable++; - } else { - logger.info(" {} {}", entry.getKey(), entry.getKey().getState()); - for (StackTraceElement ste : entry.getValue()) { - logger.info("\tat {}", ste); - } + /** + * Log to see the advancement + * + * @param listFlows list flows running + * @param percentAdvancement percentAdvancement of the test, according the timeframe + */ + private void logRealTime(List<RunScenarioFlowBasic> listFlows, long timeToFinishInMs, int percentAdvancement) { + logger.info("------------ Log advancement at {} ----- {} %, end in {} s", new Date(), percentAdvancement, + timeToFinishInMs / 1000); + + for (RunScenarioFlowBasic flowBasic : listFlows) { + + RunResult runResultFlow = flowBasic.getRunResult(); + int currentNumberOfThreads = flowBasic.getCurrentNumberOfThreads(); + // logs only flow with a result or currently active + if (runResultFlow.getRecordCreationPIAllProcesses() + runResultFlow.getNumberOfSteps() + + runResultFlow.getNumberOfErrorSteps() == 0 && currentNumberOfThreads == 0) + continue; + long previousValue = previousValueMap.getOrDefault(flowBasic.getId(), 0L); + + ScenarioStep scenarioStep = flowBasic.getScenarioStep(); + String key = "[" + flowBasic.getId() + "] " + flowBasic.getStatus().toString() + " currentNbThreads[" + + currentNumberOfThreads + "] "; + key += switch (scenarioStep.getType()) { + case STARTEVENT -> "PI[" + runResultFlow.getRecordCreationPI() + "] delta[" + ( + runResultFlow.getRecordCreationPI().get(flowBasic.getScenarioStep().getProcessId()).nbCreated + - previousValue) + "]"; + case SERVICETASK -> + "StepsExecuted[" + runResultFlow.getNumberOfSteps() + "] delta [" + (runResultFlow.getNumberOfSteps() + - previousValue) + "] StepsErrors[" + runResultFlow.getNumberOfErrorSteps() + "]"; + case USERTASK -> + "StepsExecuted[" + runResultFlow.getNumberOfSteps() + "] delta [" + (runResultFlow.getNumberOfSteps() + - previousValue) + "] StepsErrors[" + runResultFlow.getNumberOfErrorSteps() + "]"; + + default -> "]"; + }; + logger.info(key); + switch (scenarioStep.getType()) { + case STARTEVENT -> previousValueMap.put(flowBasic.getId(), + runResultFlow.getRecordCreationPI().get(flowBasic.getScenarioStep().getProcessId()).nbCreated); + + case SERVICETASK -> previousValueMap.put(flowBasic.getId(), (long) runResultFlow.getNumberOfSteps()); + + case USERTASK -> previousValueMap.put(flowBasic.getId(), (long) runResultFlow.getNumberOfSteps()); + + default -> { + } + } + + } + int nbThreadsServiceTask = 0; + int nbThreadsAutomator = 0; + int nbThreadsTimeWaiting = 0; + int nbThreadsWaiting = 0; + int nbThreadsTimeRunnable = 0; + for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) { + boolean isZeebe = false; + boolean isServiceTask = false; + boolean isAutomator = false; + for (StackTraceElement ste : entry.getValue()) { + if (ste.getClassName().contains("io.camunda")) + isZeebe = true; + else if (ste.getClassName().contains(RunScenarioFlowServiceTask.SimpleDelayHandler.class.getName())) + isServiceTask = true; + else if (ste.getClassName().contains(".automator.")) + isAutomator = true; + + // org.camunda.automator.engine.flow.RunScenarioFlowServiceTask$SimpleDelayCompletionHandler + } + if (!isZeebe && !isServiceTask && !isAutomator) + continue; + + if (isServiceTask) + nbThreadsServiceTask++; + else if (isAutomator) + nbThreadsAutomator++; + else + // TIME_WAITING: typical for the FlowServiceTask with a sleep + if (entry.getKey().getState().equals(Thread.State.TIMED_WAITING)) { + // is the thread is running the service task (with a Thread.sleep? + nbThreadsTimeWaiting++; + } else if (entry.getKey().getState().equals(Thread.State.WAITING)) { + nbThreadsTimeWaiting++; + } else if (entry.getKey().getState().equals(Thread.State.RUNNABLE)) { + nbThreadsTimeRunnable++; + } else { + logger.info(" {} {}", entry.getKey(), entry.getKey().getState()); + for (StackTraceElement ste : entry.getValue()) { + logger.info("\tat {}", ste); + } + + } } + BpmnEngine bpmnEngine = runScenario.getBpmnEngine(); + int workerExecutionThreads = bpmnEngine.getWorkerExecutionThreads(); + if (nbThreadsServiceTask + nbThreadsTimeWaiting + nbThreadsWaiting + nbThreadsTimeRunnable + nbThreadsAutomator > 0) + logger.info( + "Threads: ServiceTaskExecution (ThreadService/maxJobActive) [{}/{}] {} % Automator[{}] TIME_WAITING[{}] WAITING[{}] RUNNABLE[{}] ", + nbThreadsServiceTask, workerExecutionThreads, + workerExecutionThreads == 0 ? 0 : (int) (100.0 * nbThreadsServiceTask / workerExecutionThreads), + nbThreadsAutomator, nbThreadsTimeWaiting, nbThreadsWaiting, nbThreadsTimeRunnable); } - BpmnEngine bpmnEngine = runScenario.getBpmnEngine(); - int workerExecutionThreads = bpmnEngine.getWorkerExecutionThreads(); - if (nbThreadsServiceTask + nbThreadsTimeWaiting + nbThreadsWaiting + nbThreadsTimeRunnable + nbThreadsAutomator > 0) - logger.info( - "Threads: ServiceTaskExecution (ThreadService/maxJobActive) [{}/{}] {} % Automator[{}] TIME_WAITING[{}] WAITING[{}] RUNNABLE[{}] ", - nbThreadsServiceTask, workerExecutionThreads, - workerExecutionThreads == 0 ? 0 : (int) (100.0 * nbThreadsServiceTask / workerExecutionThreads), - nbThreadsAutomator, nbThreadsTimeWaiting, nbThreadsWaiting, nbThreadsTimeRunnable); - } } diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java index f010767..5d23b79 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java @@ -27,307 +27,307 @@ import java.util.stream.Stream; public class RunScenarioWarmingUp { - private final ServiceAccess serviceAccess; - private final RunScenario runScenario; - Logger logger = LoggerFactory.getLogger(RunScenarioWarmingUp.class); - - List<RunScenarioFlowServiceTask> listWarmingUpServiceTask = new ArrayList<>(); - List<RunScenarioFlowUserTask> listWarmingUpUserTask = new ArrayList<>(); - - RunScenarioWarmingUp(ServiceAccess serviceAccess, RunScenario runScenario) { - this.serviceAccess = serviceAccess; - this.runScenario = runScenario; - } - - /** - * warmingUp - * Do it! - * - * @param runResult populate the runResult - */ - public void warmingUp(RunResult runResult) { - ScenarioWarmingUp warmingUp = runScenario.getScenario().getWarmingUp(); - if (warmingUp == null) { - logger.info("WarmingUp not present in scenario"); - return; - } - if (!runScenario.getRunParameters().isWarmingUp()) { - logger.info("WarmingUp present, but not allowed to start"); - return; - } - long beginTime = System.currentTimeMillis(); - - // If no duration is set, then 10 Mn max - long endWarmingUp = - beginTime + (warmingUp.getDuration().toMillis() > 0 ? warmingUp.getDuration().toMillis() : 1000 * 60 * 10); - - listWarmingUpServiceTask.clear(); - listWarmingUpUserTask.clear(); - List<StartEventWarmingUpRunnable> listWarmingUpStartEvent = new ArrayList<>(); - List<ScenarioStep> listOperationWarmingUp = warmingUp.getOperations(); - if (warmingUp.useServiceTasks && runScenario.getRunParameters().isServiceTask()) { - listOperationWarmingUp.addAll(runScenario.getScenario() - .getFlows() - .stream() - .filter(t -> t.getType().equals(ScenarioStep.Step.SERVICETASK)) - .toList()); - } - if (warmingUp.useUserTasks && runScenario.getRunParameters().isUserTask()) { - listOperationWarmingUp.addAll(runScenario.getScenario() - .getFlows() - .stream() - .filter(t -> t.getType().equals(ScenarioStep.Step.USERTASK)) - .toList()); - } + private final ServiceAccess serviceAccess; + private final RunScenario runScenario; + Logger logger = LoggerFactory.getLogger(RunScenarioWarmingUp.class); - logger.info("WarmingUp: Start ---- {} operations (Scenario/Policy: serviceTask:{}/{} userTask: {}/{})", - listOperationWarmingUp.size(), // size of operations - warmingUp.useServiceTasks, // scenario allow service task? - runScenario.getRunParameters().isServiceTask(), // pod can run service task? - warmingUp.useUserTasks, runScenario.getRunParameters().isUserTask() // pod can run User Task? - ); - - for (ScenarioStep scenarioStep : listOperationWarmingUp) { - switch (scenarioStep.getType()) { - case STARTEVENT -> { - logger.info("WarmingUp: StartEvent GeneratePI[{}] Frequency[{}] EndWarmingUp[{}]", - scenarioStep.getNumberOfExecutions(), scenarioStep.getFrequency(), scenarioStep.getEndWarmingUp()); - StartEventWarmingUpRunnable startEventWarmingUpRunnable = new StartEventWarmingUpRunnable( - serviceAccess.getTaskScheduler("warmingUp"), scenarioStep, 0, runScenario, runResult); - listWarmingUpStartEvent.add(startEventWarmingUpRunnable); - startEventWarmingUpRunnable.start(); - } - case SERVICETASK -> { - logger.info("WarmingUp: Start Service Task topic[{}]", scenarioStep.getTopic()); - RunScenarioFlowServiceTask task = new RunScenarioFlowServiceTask(serviceAccess.getTaskScheduler("serviceTask"), - scenarioStep, runScenario, new RunResult(runScenario)); - task.execute(); - listWarmingUpServiceTask.add(task); - } - case USERTASK -> { - logger.info("WarmingUp: Start User Task taskId[{}]", scenarioStep.getTaskId()); - RunScenarioFlowUserTask userTask = new RunScenarioFlowUserTask(serviceAccess.getTaskScheduler("userTask"), - scenarioStep, 0, runScenario, new RunResult(runScenario)); - userTask.execute(); - listWarmingUpUserTask.add(userTask); - } - default -> logger.info("WarmingUp: Unknown [{}]", scenarioStep.getType()); - - } + List<RunScenarioFlowServiceTask> listWarmingUpServiceTask = new ArrayList<>(); + List<RunScenarioFlowUserTask> listWarmingUpUserTask = new ArrayList<>(); + + RunScenarioWarmingUp(ServiceAccess serviceAccess, RunScenario runScenario) { + this.serviceAccess = serviceAccess; + this.runScenario = runScenario; } - // check if we reach the end of the warming up - boolean warmingUpIsFinish = false; - while (!warmingUpIsFinish) { - long currentTime = System.currentTimeMillis(); - String analysis = "Limit warmupDuration in " + (endWarmingUp - currentTime) / 1000 + " s, "; - if (currentTime >= endWarmingUp) { - analysis += "OVER_MAXIMUM"; - warmingUpIsFinish = true; - } - boolean allIsFinished = true; - for (StartEventWarmingUpRunnable startRunnable : listWarmingUpStartEvent) { - analysis += "/ warmingUp[" + startRunnable.scenarioStep.getTaskId() + "] instanceCreated[" - + startRunnable.nbInstancesCreated + "]"; - - // the warmup finished boolean is not made here: each runnable (separate thread) check it. - if (startRunnable.warmingUpFinished) { - analysis += " FINISH " + startRunnable.warmingUpFinishedAnalysis; - } else { - analysis += " NOT_FINISH " + startRunnable.warmingUpNotFinishedAnalysis; - allIsFinished = false; + /** + * warmingUp + * Do it! + * + * @param runResult populate the runResult + */ + public void warmingUp(RunResult runResult) { + ScenarioWarmingUp warmingUp = runScenario.getScenario().getWarmingUp(); + if (warmingUp == null) { + logger.info("WarmingUp not present in scenario"); + return; } - - } - if (allIsFinished) { - warmingUpIsFinish = true; - } - logger.info("WarmingUpFinished? {} analysis:[{}]", warmingUpIsFinish, analysis); - if (!warmingUpIsFinish) { - try { - Thread.sleep(1000L * 15); - } catch (InterruptedException e) { - // do not care + if (!runScenario.getRunParameters().isWarmingUp()) { + logger.info("WarmingUp present, but not allowed to start"); + return; + } + long beginTime = System.currentTimeMillis(); + + // If no duration is set, then 10 Mn max + long endWarmingUp = + beginTime + (warmingUp.getDuration().toMillis() > 0 ? warmingUp.getDuration().toMillis() : 1000 * 60 * 10); + + listWarmingUpServiceTask.clear(); + listWarmingUpUserTask.clear(); + List<StartEventWarmingUpRunnable> listWarmingUpStartEvent = new ArrayList<>(); + List<ScenarioStep> listOperationWarmingUp = warmingUp.getOperations(); + if (warmingUp.useServiceTasks && runScenario.getRunParameters().isServiceTask()) { + listOperationWarmingUp.addAll(runScenario.getScenario() + .getFlows() + .stream() + .filter(t -> t.getType().equals(ScenarioStep.Step.SERVICETASK)) + .toList()); + } + if (warmingUp.useUserTasks && runScenario.getRunParameters().isUserTask()) { + listOperationWarmingUp.addAll(runScenario.getScenario() + .getFlows() + .stream() + .filter(t -> t.getType().equals(ScenarioStep.Step.USERTASK)) + .toList()); } - } - } - - // stop everything - for (StartEventWarmingUpRunnable startRunnable : listWarmingUpStartEvent) { - startRunnable.pleaseStop(true); - } - // now warmup is finished - logger.info("WarmingUp: Complete ----"); - } + logger.info("WarmingUp: Start ---- {} operations (Scenario/Policy: serviceTask:{}/{} userTask: {}/{})", + listOperationWarmingUp.size(), // size of operations + warmingUp.useServiceTasks, // scenario allow service task? + runScenario.getRunParameters().isServiceTask(), // pod can run service task? + warmingUp.useUserTasks, runScenario.getRunParameters().isUserTask() // pod can run User Task? + ); + + for (ScenarioStep scenarioStep : listOperationWarmingUp) { + switch (scenarioStep.getType()) { + case STARTEVENT -> { + logger.info("WarmingUp: StartEvent GeneratePI[{}] Frequency[{}] EndWarmingUp[{}]", + scenarioStep.getNumberOfExecutions(), scenarioStep.getFrequency(), scenarioStep.getEndWarmingUp()); + StartEventWarmingUpRunnable startEventWarmingUpRunnable = new StartEventWarmingUpRunnable( + serviceAccess.getTaskScheduler("warmingUp"), scenarioStep, 0, runScenario, runResult); + listWarmingUpStartEvent.add(startEventWarmingUpRunnable); + startEventWarmingUpRunnable.start(); + } + case SERVICETASK -> { + logger.info("WarmingUp: Start Service Task topic[{}]", scenarioStep.getTopic()); + RunScenarioFlowServiceTask task = new RunScenarioFlowServiceTask(serviceAccess.getTaskScheduler("serviceTask"), + scenarioStep, runScenario, new RunResult(runScenario)); + task.execute(); + listWarmingUpServiceTask.add(task); + } + case USERTASK -> { + logger.info("WarmingUp: Start User Task taskId[{}]", scenarioStep.getTaskId()); + RunScenarioFlowUserTask userTask = new RunScenarioFlowUserTask(serviceAccess.getTaskScheduler("userTask"), + scenarioStep, 0, runScenario, new RunResult(runScenario)); + userTask.execute(); + listWarmingUpUserTask.add(userTask); + } + default -> logger.info("WarmingUp: Unknown [{}]", scenarioStep.getType()); + + } + } - public List<RunScenarioFlowServiceTask> getListWarmingUpServiceTask() { - return listWarmingUpServiceTask; - } + // check if we reach the end of the warming up + boolean warmingUpIsFinish = false; + while (!warmingUpIsFinish) { + long currentTime = System.currentTimeMillis(); + String analysis = "Limit warmupDuration in " + (endWarmingUp - currentTime) / 1000 + " s, "; + if (currentTime >= endWarmingUp) { + analysis += "OVER_MAXIMUM"; + warmingUpIsFinish = true; + } + boolean allIsFinished = true; + for (StartEventWarmingUpRunnable startRunnable : listWarmingUpStartEvent) { + analysis += "/ warmingUp[" + startRunnable.scenarioStep.getTaskId() + "] instanceCreated[" + + startRunnable.nbInstancesCreated + "]"; + + // the warmup finished boolean is not made here: each runnable (separate thread) check it. + if (startRunnable.warmingUpFinished) { + analysis += " FINISH " + startRunnable.warmingUpFinishedAnalysis; + } else { + analysis += " NOT_FINISH " + startRunnable.warmingUpNotFinishedAnalysis; + allIsFinished = false; + } + + } + if (allIsFinished) { + warmingUpIsFinish = true; + } + logger.info("WarmingUpFinished? {} analysis:[{}]", warmingUpIsFinish, analysis); + if (!warmingUpIsFinish) { + try { + Thread.sleep(1000L * 15); + } catch (InterruptedException e) { + // do not care + } + } + } - public List<RunScenarioFlowUserTask> getListWarmingUpUserTask() { - return listWarmingUpUserTask; - } + // stop everything + for (StartEventWarmingUpRunnable startRunnable : listWarmingUpStartEvent) { + startRunnable.pleaseStop(true); + } - public List<RunScenarioFlowBasic> getListWarmingUpTask() { - return Stream.concat(listWarmingUpServiceTask.stream(), listWarmingUpUserTask.stream()) - .collect(Collectors.toList()); - } + // now warmup is finished + logger.info("WarmingUp: Complete ----"); + } - /** - * StartEventRunnable - * Must be runnable because we will schedule it. - */ - class StartEventWarmingUpRunnable implements Runnable { + public List<RunScenarioFlowServiceTask> getListWarmingUpServiceTask() { + return listWarmingUpServiceTask; + } - private final TaskScheduler scheduler; - private final ScenarioStep scenarioStep; - private final RunScenario runScenario; - private final RunResult runResult; - public boolean stop = false; - public boolean warmingUpFinished = false; - public String warmingUpFinishedAnalysis = ""; - public String warmingUpNotFinishedAnalysis = "Not verified yet"; - public int nbInstancesCreated = 0; - private int nbOverloaded = 0; - private int executionBatchNumber = 1; - - public StartEventWarmingUpRunnable(TaskScheduler scheduler, - ScenarioStep scenarioStep, - int index, - RunScenario runScenario, - RunResult runResult) { - this.scheduler = scheduler; - this.scenarioStep = scenarioStep; - this.runScenario = runScenario; - this.runResult = runResult; + public List<RunScenarioFlowUserTask> getListWarmingUpUserTask() { + return listWarmingUpUserTask; } - public void pleaseStop(boolean stop) { - this.stop = stop; + public List<RunScenarioFlowBasic> getListWarmingUpTask() { + return Stream.concat(listWarmingUpServiceTask.stream(), listWarmingUpUserTask.stream()) + .collect(Collectors.toList()); } /** - * Start it in a new tread + * StartEventRunnable + * Must be runnable because we will schedule it. */ - public void start() { - scheduler.schedule(this, Instant.now()); - - } - - @Override - public void run() { - if (stop) { - return; - } - executionBatchNumber++; - // check if the condition is reach - CheckFunctionResult checkFunctionResult = null; - if (scenarioStep.getEndWarmingUp() != null) { - checkFunctionResult = endCheckFunction(scenarioStep.getEndWarmingUp(), runResult); - if (checkFunctionResult.goalReach) { - warmingUpFinishedAnalysis += "GoalReach[" + checkFunctionResult.analysis + "]"; - warmingUpNotFinishedAnalysis = ""; - warmingUpFinished = true; - return; - } else { - warmingUpNotFinishedAnalysis = checkFunctionResult.analysis(); + class StartEventWarmingUpRunnable implements Runnable { + + private final TaskScheduler scheduler; + private final ScenarioStep scenarioStep; + private final RunScenario runScenario; + private final RunResult runResult; + public boolean stop = false; + public boolean warmingUpFinished = false; + public String warmingUpFinishedAnalysis = ""; + public String warmingUpNotFinishedAnalysis = "Not verified yet"; + public int nbInstancesCreated = 0; + private int nbOverloaded = 0; + private int executionBatchNumber = 1; + + public StartEventWarmingUpRunnable(TaskScheduler scheduler, + ScenarioStep scenarioStep, + int index, + RunScenario runScenario, + RunResult runResult) { + this.scheduler = scheduler; + this.scenarioStep = scenarioStep; + this.runScenario = runScenario; + this.runResult = runResult; } - } - // continue to generate PI - long begin = System.currentTimeMillis(); - CreateProcessInstanceThread createProcessInstanceThread = new CreateProcessInstanceThread(executionBatchNumber, - scenarioStep, runScenario, runResult); - - Duration durationWarmup; - if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { - durationWarmup = Duration.ofHours(1); - } else { - durationWarmup = Duration.parse(scenarioStep.getFrequency()); - } - - createProcessInstanceThread.createProcessInstances(durationWarmup); - - long end = System.currentTimeMillis(); - // one step generation? - if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("WarmingUp:StartEvent Create[{}] in {} ms (oneShoot) listPI(max20): ", - scenarioStep.getNumberOfExecutions(), (end - begin), - createProcessInstanceThread.getListProcessInstances().stream().collect(Collectors.joining(","))); + + public void pleaseStop(boolean stop) { + this.stop = stop; } - warmingUpFinishedAnalysis += "GoalOneShoot"; - warmingUpFinished = true; - return; - } - - if (createProcessInstanceThread.isOverload()) { - nbOverloaded++; - } - Duration durationToWait; - if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { - durationToWait = Duration.ZERO; - } else { - durationToWait = durationWarmup.minusMillis(end - begin); - if (durationToWait.isNegative()) { - durationToWait = Duration.ZERO; + + /** + * Start it in a new tread + */ + public void start() { + scheduler.schedule(this, Instant.now()); + } - } - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("Warmingup batch_#{} Create real/scenario[{}/{}] in {} ms Sleep[{} s] {}", // log - executionBatchNumber, // batch - createProcessInstanceThread.getTotalCreation(), scenarioStep.getNumberOfExecutions(), - // Number of creation request - (end - begin), // duration - durationToWait.getSeconds(), // Sleep for the frequency - (checkFunctionResult == null ? "" : "EndWarmingUp:" + checkFunctionResult.analysis)); - } - scheduler.schedule(this, Instant.now().plusMillis(durationToWait.toMillis())); - } - /** - * Check the function - * - * @param function function to check - * @param runResult runResult to fulfill information - * @return status - */ - private CheckFunctionResult endCheckFunction(String function, RunResult runResult) { - try { - int posParenthesis = function.indexOf("("); - String functionName = function.substring(0, posParenthesis); - String parameters = function.substring(posParenthesis + 1); - parameters = parameters.substring(0, parameters.length() - 1); - StringTokenizer st = new StringTokenizer(parameters, ","); - if ("UserTaskThreshold".equalsIgnoreCase(functionName)) { - String taskId = st.hasMoreTokens() ? st.nextToken() : ""; - Integer threshold = st.hasMoreTokens() ? Integer.valueOf(st.nextToken()) : 0; - long value = runScenario.getBpmnEngine().countNumberOfTasks(runScenario.getScenario().getProcessId(), taskId); - return new CheckFunctionResult(value >= threshold, - "Task[" + taskId + "] value [" + value + "] / threshold[" + threshold + "]"); - } else if ("EndEventThreshold".equalsIgnoreCase(functionName)) { - String endId = st.hasMoreTokens() ? st.nextToken() : ""; - Integer threshold = st.hasMoreTokens() ? Integer.valueOf(st.nextToken()) : 0; - - long value = runScenario.getBpmnEngine() - .countNumberOfProcessInstancesEnded(runScenario.getScenario().getProcessId(), - new DateFilter(runResult.getStartDate()), new DateFilter(new Date())); - return new CheckFunctionResult(value >= threshold, - "End[" + endId + "] value [" + value + "] / threshold[" + threshold + "]"); + @Override + public void run() { + if (stop) { + return; + } + executionBatchNumber++; + // check if the condition is reach + CheckFunctionResult checkFunctionResult = null; + if (scenarioStep.getEndWarmingUp() != null) { + checkFunctionResult = endCheckFunction(scenarioStep.getEndWarmingUp(), runResult); + if (checkFunctionResult.goalReach) { + warmingUpFinishedAnalysis += "GoalReach[" + checkFunctionResult.analysis + "]"; + warmingUpNotFinishedAnalysis = ""; + warmingUpFinished = true; + return; + } else { + warmingUpNotFinishedAnalysis = checkFunctionResult.analysis(); + } + } + // continue to generate PI + long begin = System.currentTimeMillis(); + CreateProcessInstanceThread createProcessInstanceThread = new CreateProcessInstanceThread(executionBatchNumber, + scenarioStep, runScenario, runResult); + + Duration durationWarmup; + if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { + durationWarmup = Duration.ofHours(1); + } else { + durationWarmup = Duration.parse(scenarioStep.getFrequency()); + } + + createProcessInstanceThread.createProcessInstances(durationWarmup); + + long end = System.currentTimeMillis(); + // one step generation? + if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("WarmingUp:StartEvent Create[{}] in {} ms (oneShoot) listPI(max20): ", + scenarioStep.getNumberOfExecutions(), (end - begin), + createProcessInstanceThread.getListProcessInstances().stream().collect(Collectors.joining(","))); + } + warmingUpFinishedAnalysis += "GoalOneShoot"; + warmingUpFinished = true; + return; + } + + if (createProcessInstanceThread.isOverload()) { + nbOverloaded++; + } + Duration durationToWait; + if (scenarioStep.getFrequency() == null || scenarioStep.getFrequency().isEmpty()) { + durationToWait = Duration.ZERO; + } else { + durationToWait = durationWarmup.minusMillis(end - begin); + if (durationToWait.isNegative()) { + durationToWait = Duration.ZERO; + } + } + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("Warmingup batch_#{} Create real/scenario[{}/{}] in {} ms Sleep[{} s] {}", // log + executionBatchNumber, // batch + createProcessInstanceThread.getTotalCreation(), scenarioStep.getNumberOfExecutions(), + // Number of creation request + (end - begin), // duration + durationToWait.getSeconds(), // Sleep for the frequency + (checkFunctionResult == null ? "" : "EndWarmingUp:" + checkFunctionResult.analysis)); + } + scheduler.schedule(this, Instant.now().plusMillis(durationToWait.toMillis())); + } + /** + * Check the function + * + * @param function function to check + * @param runResult runResult to fulfill information + * @return status + */ + private CheckFunctionResult endCheckFunction(String function, RunResult runResult) { + try { + int posParenthesis = function.indexOf("("); + String functionName = function.substring(0, posParenthesis); + String parameters = function.substring(posParenthesis + 1); + parameters = parameters.substring(0, parameters.length() - 1); + StringTokenizer st = new StringTokenizer(parameters, ","); + if ("UserTaskThreshold".equalsIgnoreCase(functionName)) { + String taskId = st.hasMoreTokens() ? st.nextToken() : ""; + Integer threshold = st.hasMoreTokens() ? Integer.valueOf(st.nextToken()) : 0; + long value = runScenario.getBpmnEngine().countNumberOfTasks(runScenario.getScenario().getProcessId(), taskId); + return new CheckFunctionResult(value >= threshold, + "Task[" + taskId + "] value [" + value + "] / threshold[" + threshold + "]"); + } else if ("EndEventThreshold".equalsIgnoreCase(functionName)) { + String endId = st.hasMoreTokens() ? st.nextToken() : ""; + Integer threshold = st.hasMoreTokens() ? Integer.valueOf(st.nextToken()) : 0; + + long value = runScenario.getBpmnEngine() + .countNumberOfProcessInstancesEnded(runScenario.getScenario().getProcessId(), + new DateFilter(runResult.getStartDate()), new DateFilter(new Date())); + return new CheckFunctionResult(value >= threshold, + "End[" + endId + "] value [" + value + "] / threshold[" + threshold + "]"); + + } + logger.error("Unknown function [{}]", functionName); + return new CheckFunctionResult(false, "Unknown function"); + } catch (AutomatorException e) { + logger.error("Error during warmingup {}", e.getMessage()); + return new CheckFunctionResult(false, "Exception " + e.getMessage()); + } } - logger.error("Unknown function [{}]", functionName); - return new CheckFunctionResult(false, "Unknown function"); - } catch (AutomatorException e) { - logger.error("Error during warmingup {}", e.getMessage()); - return new CheckFunctionResult(false, "Exception " + e.getMessage()); - } - } - /** - * UserTaskTask(<taskId>,<numberOfTaskExpected>) - */ - public record CheckFunctionResult(boolean goalReach, String analysis) { + /** + * UserTaskTask(<taskId>,<numberOfTaskExpected>) + */ + public record CheckFunctionResult(boolean goalReach, String analysis) { + } } - } } diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java index 3932074..d4c20f2 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java @@ -21,210 +21,216 @@ * one ExecutionStep in a runScenario */ public class RunScenarioUnit { - private final Logger logger = LoggerFactory.getLogger(RunScenarioUnit.class); - private final RunScenario runScenario; - private final ScenarioExecution scnExecution; - private String agentName = ""; - - public RunScenarioUnit(RunScenario runScenario, ScenarioExecution scnExecution) { - this.runScenario = runScenario; - this.scnExecution = scnExecution; - } - - public void setAgentName(String name) { - this.agentName = name; - } - - /** - * Execute the scenario. - * Note: this method is multi thread safe. - * Each execution has its own Thread - * - * @return the execution - */ - public RunResult runExecution() { - RunResult resultExecution = new RunResult(runScenario); - - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("ScnRunExecution." + agentName + ": Start Execution [" + scnExecution.getName() + "] "); + private final Logger logger = LoggerFactory.getLogger(RunScenarioUnit.class); + private final RunScenario runScenario; + private final ScenarioExecution scnExecution; + private String agentName = ""; + + public RunScenarioUnit(RunScenario runScenario, ScenarioExecution scnExecution) { + this.runScenario = runScenario; + this.scnExecution = scnExecution; } - ExecutorService executor = Executors.newFixedThreadPool(scnExecution.getNumberOfThreads()); - List<Future<?>> listFutures = new ArrayList<>(); - - for (int i = 0; i < scnExecution.getNumberProcessInstances(); i++) { - ScnThreadExecutionCallable scnExecutionCallable = new ScnThreadExecutionCallable("AutomatorThread-" + i, this, - runScenario.getRunParameters()); - - listFutures.add(executor.submit(scnExecutionCallable)); + public void setAgentName(String name) { + this.agentName = name; } - // wait the end of all executions - try { - for (Future<?> f : listFutures) { - Object scnRunResult = f.get(); - resultExecution.add((RunResult) scnRunResult); - - } + /** + * Execute one execution on the scenario + * Note: this method is multi thread safe. + * Each execution has its own newFixedThreadPool + * + * @return the execution + */ + public RunResult runExecution() { + RunResult resultExecution = new RunResult(runScenario, scnExecution); - } catch (Exception e) { - resultExecution.addError(null, "Error during executing in parallel " + e.getMessage()); - } + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("ScnRunExecution.{} Start Execution [{}]", agentName, scnExecution.getName()); + } + ExecutorService executor = Executors.newFixedThreadPool(scnExecution.getNumberOfThreads()); - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("ScnRunExecution." + agentName + ": End Execution [" + scnExecution.getName() + "] success? " - + resultExecution.isSuccess()); - } - return resultExecution; - } + List<Future<?>> listFutures = new ArrayList<>(); + for (int i = 0; i < scnExecution.getNumberProcessInstances(); i++) { + ScnThreadExecutionCallable scnExecutionCallable = new ScnThreadExecutionCallable("AutomatorThread-" + scnExecution.getName() + "-" + i, this, + runScenario.getRunParameters()); + listFutures.add(executor.submit(scnExecutionCallable)); + } + // wait the end of all executions + try { + for (Future<?> f : listFutures) { + Object scnRunResult = f.get(); + resultExecution.merge((RunResult) scnRunResult); + } + } catch (Exception e) { + resultExecution.addError(null, "Error during executing in parallel " + e.getMessage()); + } - /* ******************************************************************** */ - /* */ - /* ScnThreadCallable : execute one Execution per thread */ - /* */ - /* ******************************************************************** */ + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("ScnRunExecution.{}: End Execution [{}] success? {}", + agentName, + scnExecution.getName(), + resultExecution.isSuccess()); + } + return resultExecution; + } - private class ScnThreadExecutionCallable implements Callable { - private final String agentName; - private final RunScenarioUnit scnRunExecution; - private final RunParameters runParameters; - private RunResult scnRunResult; - ScnThreadExecutionCallable(String agentName, RunScenarioUnit scnRunExecution, RunParameters runParameters) { - this.agentName = agentName; - this.scnRunExecution = scnRunExecution; - this.runParameters = runParameters; - } - /** - * run one execution. - * - * @return the result object - * @throws Exception in case of error - */ - public Object call() throws Exception { - scnRunResult = new RunResult(scnRunExecution.runScenario); - if (runParameters.isExecution()) - runExecution(); - - // two uses case here: - // Execution AND verifications: for each process Instance created, a verification is running - // Only VERIFICATION: the verification ojbect define a filter to search existing process instance. Verification is perform againts this list - if (runParameters.isVerification() && (scnExecution.getVerifications() != null)) { - if (runParameters.isExecution()) { - // we just finish executing process instance, so wait 30 S to let the engine finish - try { - Thread.sleep(30 * 1000); - } catch (Exception e) { - // nothing to do - } - runVerifications(); - } else { - // use the search criteria - if (scnExecution.getVerifications().getSearchProcessInstanceByVariable().isEmpty()) { - scnRunResult.addVerification(null, false, "No Search Instance by Variable is defined"); - } else { - List<BpmnEngine.ProcessDescription> listProcessInstances = runScenario.getBpmnEngine() - .searchProcessInstanceByVariable(scnExecution.getScnHead().getProcessId(), - scnExecution.getVerifications().getSearchProcessInstanceByVariable(), 100); - - for (BpmnEngine.ProcessDescription processInstance : listProcessInstances) { - scnRunResult.addProcessInstanceId(scnExecution.getScnHead().getProcessId(), - processInstance.processInstanceId); - } - runVerifications(); - } - } - } - // we finish with this process instance - if (scnRunResult.getFirstProcessInstanceId() != null && runParameters.isClearAllAfter()) - runScenario.getBpmnEngine() - .endProcessInstance(scnRunResult.getFirstProcessInstanceId(), runParameters.isClearAllAfter()); - return scnRunResult; - } - public void runExecution() { + /* ******************************************************************** */ + /* */ + /* ScnThreadCallable : execute one Execution per thread */ + /* */ + /* ******************************************************************** */ - RunScenarioUnitServiceTask serviceTask = new RunScenarioUnitServiceTask(runScenario); - RunScenarioUnitUserTask userTask = new RunScenarioUnitUserTask(runScenario); - RunScenarioUnitStartEvent startEvent = new RunScenarioUnitStartEvent(runScenario); + private class ScnThreadExecutionCallable implements Callable { + private final String agentName; + private final RunScenarioUnit scnRunExecution; + private final RunParameters runParameters; - if (scnRunExecution.runScenario.getRunParameters().showLevelMonitoring()) - logger.info( - "ScnRunExecution.StartExecution [" + scnRunExecution.runScenario.getScenario().getName() + "] agent[" - + agentName + "]"); + private RunResult scnRunResult; - for (ScenarioStep step : scnExecution.getSteps()) { + ScnThreadExecutionCallable(String agentName, RunScenarioUnit scnRunExecution, RunParameters runParameters) { + this.agentName = agentName; + this.scnRunExecution = scnRunExecution; + this.runParameters = runParameters; + } - if (scnRunExecution.runScenario.getRunParameters().showLevelDebug()) - logger.info( - "ScnRunExecution.StartExecution.Execute [" + scnRunExecution.runScenario.getScenario().getName() + "." - + step.getTaskId() + " agent[" + agentName + "]"); + /** + * run one execution. + * + * @return the result object + * @throws Exception in case of error + */ + public Object call() throws Exception { + scnRunResult = new RunResult(scnRunExecution.runScenario); + if (runParameters.isExecution()) + runExecution(); + + // two uses case here: + // Execution AND verifications: for each process Instance created, a verification is running + // Only VERIFICATION: the verification ojbect define a filter to search existing process instance. Verification is perform againts this list + if (runParameters.isVerification() && (scnExecution.getVerifications() != null)) { + if (runParameters.isExecution()) { + // we just finish executing process instance, so wait 30 S to let the engine finish + try { + logger.info("Wait 10 s to let Operate collect data"); + Thread.sleep(10 * 1000); + } catch (Exception e) { + // nothing to do + } + runVerifications(); + } else { + // use the search criteria + if (scnExecution.getVerifications().getSearchProcessInstanceByVariable().isEmpty()) { + scnRunResult.addVerification(null, false, "No Search Instance by Variable is defined"); + } else { + List<BpmnEngine.ProcessDescription> listProcessInstances = runScenario.getBpmnEngine() + .searchProcessInstanceByVariable(scnExecution.getScnHead().getProcessId(), + scnExecution.getVerifications().getSearchProcessInstanceByVariable(), 100); + + for (BpmnEngine.ProcessDescription processInstance : listProcessInstances) { + scnRunResult.addProcessInstanceId(scnExecution.getScnHead().getProcessId(), + processInstance.processInstanceId); + } + runVerifications(); + } + } - try { - step.checkConsistence(); - } catch (AutomatorException e) { - scnRunResult.addError(step, e.getMessage()); - continue; - } - long timeBegin = System.currentTimeMillis(); - if (step.getType() == null) { - scnRunResult.addError(step, "Unknown type"); - continue; - } - switch (step.getType()) { - case STARTEVENT -> { - if (scnRunExecution.runScenario.getRunParameters().isCreation()) - scnRunResult = startEvent.startEvent(scnRunResult, step); - } - case USERTASK -> { - // wait for the user Task - if (scnRunExecution.runScenario.getRunParameters().isUserTask()) - scnRunResult = userTask.executeUserTask(step, scnRunResult); - } - case SERVICETASK -> { - // wait for the user Task - if (scnRunExecution.runScenario.getRunParameters().isServiceTask()) { - scnRunResult = serviceTask.executeServiceTask(step, scnRunResult); - } + } + // we finish with this process instance + if (scnRunResult.getFirstProcessInstanceId() != null && runParameters.isClearAllAfter()) + runScenario.getBpmnEngine() + .endProcessInstance(scnRunResult.getFirstProcessInstanceId(), runParameters.isClearAllAfter()); + return scnRunResult; } - case ENDEVENT -> { + public void runExecution() { + + RunScenarioUnitServiceTask serviceTask = new RunScenarioUnitServiceTask(runScenario); + RunScenarioUnitUserTask userTask = new RunScenarioUnitUserTask(runScenario); + RunScenarioUnitStartEvent startEvent = new RunScenarioUnitStartEvent(runScenario); + + if (scnRunExecution.runScenario.getRunParameters().showLevelMonitoring()) + logger.info( + "ScnRunExecution.StartExecution [{}] agent[{}]", + scnRunExecution.runScenario.getScenario().getName(), + agentName); + + for (ScenarioStep step : scnExecution.getSteps()) { + + if (scnRunExecution.runScenario.getRunParameters().showLevelDebug()) + logger.info( + "ScnRunExecution.StartExecution.Execute [{}.{} agent[{}]", + scnRunExecution.runScenario.getScenario().getName(), + step.getTaskId(), + agentName); + + try { + step.checkConsistence(); + } catch (AutomatorException e) { + scnRunResult.addError(step, e.getMessage()); + continue; + } + long timeBegin = System.currentTimeMillis(); + if (step.getType() == null) { + scnRunResult.addError(step, "Unknown type"); + continue; + } + switch (step.getType()) { + case STARTEVENT -> { + if (scnRunExecution.runScenario.getRunParameters().isCreation()) + scnRunResult = startEvent.startEvent(scnRunResult, step); + } + case USERTASK -> { + // wait for the user Task + if (scnRunExecution.runScenario.getRunParameters().isUserTask()) + scnRunResult = userTask.executeUserTask(step, scnRunResult); + } + case SERVICETASK -> { + // wait for the user Task + if (scnRunExecution.runScenario.getRunParameters().isServiceTask()) { + scnRunResult = serviceTask.executeServiceTask(step, scnRunResult); + } + } + + case ENDEVENT -> { + } + + case MESSAGE -> { + } + } + long timeEnd = System.currentTimeMillis(); + scnRunResult.addStepExecution(step, timeEnd - timeBegin); + + if (!scnRunResult.isSuccess() && ScenarioExecution.Policy.STOPATFIRSTERROR.equals(scnExecution.getPolicy())) + return; + } + if (scnRunExecution.runScenario.getRunParameters().showLevelMonitoring()) + logger.info("ScnRunExecution.EndExecution [" + scnExecution.getName() + "] agent[" + agentName + "]"); } - case MESSAGE -> { - } + /** + * Run the verification just after the execution, on the process instances created + */ + public void runVerifications() { + RunScenarioVerification verifications = new RunScenarioVerification(scnExecution); + for (String processInstanceId : scnRunResult.getProcessInstanceId()) { + scnRunResult.merge(verifications.runVerifications(scnRunExecution.runScenario, processInstanceId)); + } } - long timeEnd = System.currentTimeMillis(); - scnRunResult.addStepExecution(step, timeEnd - timeBegin); - - if (!scnRunResult.isSuccess() && ScenarioExecution.Policy.STOPATFIRSTERROR.equals(scnExecution.getPolicy())) - return; - } - if (scnRunExecution.runScenario.getRunParameters().showLevelMonitoring()) - logger.info("ScnRunExecution.EndExecution [" + scnExecution.getName() + "] agent[" + agentName + "]"); - } - /** - * Run the verification just after the execution, on the process instances created - */ - public void runVerifications() { - RunScenarioVerification verifications = new RunScenarioVerification(scnExecution); - for (String processInstanceId : scnRunResult.getProcessInstanceId()) { - scnRunResult.add(verifications.runVerifications(scnRunExecution.runScenario, processInstanceId)); - } - } + public RunResult getScnRunResult() { + return scnRunResult; + } - public RunResult getScnRunResult() { - return scnRunResult; } - - } } diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java index 3430f2e..3773877 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java @@ -13,75 +13,75 @@ public class RunScenarioUnitServiceTask { - private final Logger logger = LoggerFactory.getLogger(RunScenarioUnitServiceTask.class); + private final Logger logger = LoggerFactory.getLogger(RunScenarioUnitServiceTask.class); - private final RunScenario runScenario; + private final RunScenario runScenario; - protected RunScenarioUnitServiceTask(RunScenario runScenario) { - this.runScenario = runScenario; - } - - /** - * Execute User task - * - * @param result result to complete and return - * @param step step to execute - * @return result completed - */ - public RunResult executeServiceTask(ScenarioStep step, RunResult result) { - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("Service TaskId[{}]", step.getTaskId()); - } - - if (step.getDelay() != null) { - Duration duration = Duration.parse(step.getDelay()); - try { - Thread.sleep(duration.toMillis()); - } catch (InterruptedException e) { - // nothing to do - } + protected RunScenarioUnitServiceTask(RunScenario runScenario) { + this.runScenario = runScenario; } - Long waitingTimeInMs = null; - if (step.getWaitingTime() != null) { - Duration duration = Duration.parse(step.getWaitingTime()); - waitingTimeInMs = duration.toMillis(); - } - if (waitingTimeInMs == null) - waitingTimeInMs = 5L * 60 * 1000; - - for (int index = 0; index < step.getNumberOfExecutions(); index++) { - long beginTimeWait = System.currentTimeMillis(); - try { - List<String> listActivities; - do { - listActivities = runScenario.getBpmnEngine() - .searchServiceTasks(result.getFirstProcessInstanceId(), step.getTaskId(), step.getTopic(), 1); + /** + * Execute User task + * + * @param result result to complete and return + * @param step step to execute + * @return result completed + */ + public RunResult executeServiceTask(ScenarioStep step, RunResult result) { + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("Service TaskId[{}]", step.getTaskId()); + } - if (listActivities.isEmpty()) { + if (step.getDelay() != null) { + Duration duration = Duration.parse(step.getDelay()); try { - Thread.sleep(500); + Thread.sleep(duration.toMillis()); } catch (InterruptedException e) { + // nothing to do } - } - } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); + } + Long waitingTimeInMs = null; + if (step.getWaitingTime() != null) { + Duration duration = Duration.parse(step.getWaitingTime()); + waitingTimeInMs = duration.toMillis(); + } + if (waitingTimeInMs == null) + waitingTimeInMs = 5L * 60 * 1000; - if (listActivities.isEmpty()) { - result.addError(step, "No service task show up task[" + step.getTaskId() + "] processInstance[" - + result.getFirstProcessInstanceId() + "]"); - return result; + for (int index = 0; index < step.getNumberOfExecutions(); index++) { + long beginTimeWait = System.currentTimeMillis(); + try { + List<String> listActivities; + do { + + listActivities = runScenario.getBpmnEngine() + .searchServiceTasks(result.getFirstProcessInstanceId(), step.getTaskId(), step.getTopic(), 1); + + if (listActivities.isEmpty()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); + + if (listActivities.isEmpty()) { + result.addError(step, "No service task show up task[" + step.getTaskId() + "] processInstance[" + + result.getFirstProcessInstanceId() + "]"); + return result; + } + // this is a unit test : there is only one thread, index=1 + runScenario.getBpmnEngine() + .executeServiceTask(listActivities.get(0), step.getUserId(), + RunZeebeOperation.getVariablesStep(runScenario, step, 1)); + } catch (AutomatorException e) { + result.addError(step, e.getMessage()); + return result; + } } - // this is a unit test : there is only one thread, index=1 - runScenario.getBpmnEngine() - .executeServiceTask(listActivities.get(0), step.getUserId(), - RunZeebeOperation.getVariablesStep(runScenario, step, 1)); - } catch (AutomatorException e) { - result.addError(step, e.getMessage()); - return result; - } - } - return result; + return result; - } + } } diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitStartEvent.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitStartEvent.java index 8353349..4129e7d 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitStartEvent.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitStartEvent.java @@ -12,38 +12,38 @@ public class RunScenarioUnitStartEvent { - private final Logger logger = LoggerFactory.getLogger(RunScenarioUnitStartEvent.class); - - private final RunScenario runScenario; - - protected RunScenarioUnitStartEvent(RunScenario runScenario) { - this.runScenario = runScenario; - } - - /** - * Start Event - * - * @param result result to complete and return - * @param step step to execute - * @return result completed - */ - public RunResult startEvent(RunResult result, ScenarioStep step) { - try { - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("StartEvent EventId[{}]", step.getTaskId()); - } - String processId = step.getScnExecution().getScnHead().getProcessId(); - // There is no multithreading: index=1 - Map<String, Object> processVariables = RunZeebeOperation.getVariablesStep(runScenario, step, 1); - - String processInstanceId = runScenario.getBpmnEngine() - .createProcessInstance(processId, step.getTaskId(), processVariables); - - result.addProcessInstanceId(processId, processInstanceId); - } catch (AutomatorException e) { - result.addError(step, "Error at creation " + e.getMessage()); + private final Logger logger = LoggerFactory.getLogger(RunScenarioUnitStartEvent.class); + + private final RunScenario runScenario; + + protected RunScenarioUnitStartEvent(RunScenario runScenario) { + this.runScenario = runScenario; + } + + /** + * Start Event + * + * @param result result to complete and return + * @param step step to execute + * @return result completed + */ + public RunResult startEvent(RunResult result, ScenarioStep step) { + try { + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("StartEvent EventId[{}]", step.getTaskId()); + } + String processId = step.getScnExecution().getScnHead().getProcessId(); + // There is no multithreading: index=1 + Map<String, Object> processVariables = RunZeebeOperation.getVariablesStep(runScenario, step, 1); + + String processInstanceId = runScenario.getBpmnEngine() + .createProcessInstance(processId, step.getTaskId(), processVariables); + + result.addProcessInstanceId(processId, processInstanceId); + } catch (AutomatorException e) { + result.addError(step, "Error at creation " + e.getMessage()); + } + return result; } - return result; - } } diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitUserTask.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitUserTask.java index cb4efaf..4904ef6 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitUserTask.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitUserTask.java @@ -13,78 +13,78 @@ public class RunScenarioUnitUserTask { - private final Logger logger = LoggerFactory.getLogger(RunScenarioUnitUserTask.class); + private final Logger logger = LoggerFactory.getLogger(RunScenarioUnitUserTask.class); - private final RunScenario runScenario; + private final RunScenario runScenario; - protected RunScenarioUnitUserTask(RunScenario runScenario) { - this.runScenario = runScenario; - } - - /** - * Execute User task - * - * @param step step to execute - * @param result result to complete and return - * @return result completed - */ - public RunResult executeUserTask(ScenarioStep step, RunResult result) { - - if (runScenario.getRunParameters().showLevelMonitoring()) { - logger.info("UserTask TaskId[{}]", step.getTaskId()); - } - - if (step.getDelay() != null) { - Duration duration = Duration.parse(step.getDelay()); - try { - Thread.sleep(duration.toMillis()); - } catch (InterruptedException e) { - // don't need to do anything here - } + protected RunScenarioUnitUserTask(RunScenario runScenario) { + this.runScenario = runScenario; } - Long waitingTimeInMs = null; - if (step.getWaitingTime() != null) { - Duration duration = Duration.parse(step.getWaitingTime()); - waitingTimeInMs = duration.toMillis(); - } - if (waitingTimeInMs == null) - waitingTimeInMs = 5L * 60 * 1000; - - for (int index = 0; index < step.getNumberOfExecutions(); index++) { - long beginTimeWait = System.currentTimeMillis(); - try { - List<String> listActivities; - do { - listActivities = runScenario.getBpmnEngine() - .searchUserTasksByProcessInstance(result.getFirstProcessInstanceId(), step.getTaskId(), 1); + /** + * Execute User task + * + * @param step step to execute + * @param result result to complete and return + * @return result completed + */ + public RunResult executeUserTask(ScenarioStep step, RunResult result) { + + if (runScenario.getRunParameters().showLevelMonitoring()) { + logger.info("UserTask TaskId[{}]", step.getTaskId()); + } - if (listActivities.isEmpty()) { + if (step.getDelay() != null) { + Duration duration = Duration.parse(step.getDelay()); try { - Thread.sleep(500); + Thread.sleep(duration.toMillis()); } catch (InterruptedException e) { - // nothing to do here + // don't need to do anything here } - } - } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); + } + Long waitingTimeInMs = null; + if (step.getWaitingTime() != null) { + Duration duration = Duration.parse(step.getWaitingTime()); + waitingTimeInMs = duration.toMillis(); + } + if (waitingTimeInMs == null) + waitingTimeInMs = 5L * 60 * 1000; - if (listActivities.isEmpty()) { - result.addError(step, "No user task show up task[" + step.getTaskId() + "] processInstance[" - + result.getFirstProcessInstanceId() + "]"); - return result; + for (int index = 0; index < step.getNumberOfExecutions(); index++) { + long beginTimeWait = System.currentTimeMillis(); + try { + List<String> listActivities; + do { + + listActivities = runScenario.getBpmnEngine() + .searchUserTasksByProcessInstance(result.getFirstProcessInstanceId(), step.getTaskId(), 1); + + if (listActivities.isEmpty()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // nothing to do here + } + } + } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); + + if (listActivities.isEmpty()) { + result.addError(step, "No user task show up task[" + step.getTaskId() + "] processInstance[" + + result.getFirstProcessInstanceId() + "]"); + return result; + } + // unit test: there is no multi thread executing this part, index=1 + runScenario.getBpmnEngine() + .executeUserTask(listActivities.get(0), step.getUserId(), + RunZeebeOperation.getVariablesStep(runScenario, step, 1)); + } catch (AutomatorException e) { + result.addError(step, e.getMessage()); + return result; + } } - // unit test: there is no multi thread executing this part, index=1 - runScenario.getBpmnEngine() - .executeUserTask(listActivities.get(0), step.getUserId(), - RunZeebeOperation.getVariablesStep(runScenario, step, 1)); - } catch (AutomatorException e) { - result.addError(step, e.getMessage()); - return result; - } - } - return result; + return result; - } + } } diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioVerification.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioVerification.java index 07f064f..93e3520 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioVerification.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioVerification.java @@ -1,103 +1,205 @@ package org.camunda.automator.engine.unit; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.definition.ScenarioExecution; -import org.camunda.automator.definition.ScenarioVerification; -import org.camunda.automator.definition.ScenarioVerificationTask; -import org.camunda.automator.definition.ScenarioVerificationVariable; +import org.camunda.automator.definition.*; import org.camunda.automator.engine.AutomatorException; import org.camunda.automator.engine.RunResult; import org.camunda.automator.engine.RunScenario; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.Optional; public class RunScenarioVerification { - private final ScenarioExecution scnExecution; - private final Logger logger = LoggerFactory.getLogger(RunScenarioVerification.class); + private final ScenarioExecution scnExecution; + private final Logger logger = LoggerFactory.getLogger(RunScenarioVerification.class); - public RunScenarioVerification(ScenarioExecution scnExecution) { - this.scnExecution = scnExecution; - } + public RunScenarioVerification(ScenarioExecution scnExecution) { + this.scnExecution = scnExecution; + } + + public RunResult runVerifications(RunScenario runScenario, String processInstanceId) { + RunResult runResult = new RunResult(runScenario); - public RunResult runVerifications(RunScenario runScenario, String processInstanceId) { - RunResult result = new RunResult(runScenario); + // we get a processInstanceId now + ScenarioVerification verifications = scnExecution.getVerifications(); + for (ScenarioVerificationTask activity : verifications.getActivities()) { + checkTask(runScenario, processInstanceId, activity, runResult); + } + for (ScenarioVerificationVariable variable : verifications.getVariables()) { + checkVariable(runScenario, processInstanceId, variable, runResult); + } + for (ScenarioVerificationPerformance performance : verifications.getPerformances()) { + checkPerformance(runScenario, processInstanceId, performance, runResult); + } + return runResult; - // we get a processInstanceId now - ScenarioVerification verifications = scnExecution.getVerifications(); - for (ScenarioVerificationTask activity : verifications.getActivities()) { - checkTask(runScenario, processInstanceId, activity, result); } - for (ScenarioVerificationVariable variable : verifications.getVariables()) { - checkVariable(runScenario, processInstanceId, variable, result); + + /** + * Check to see if verificationActivity is correct or not + * + * @param runScenario scenario to pilot the verification + * @param processInstanceId ProcessInstanceId to check + * @param verificationActivity activity to check + * @param result the result object + */ + private void checkTask(RunScenario runScenario, + String processInstanceId, + ScenarioVerificationTask verificationActivity, + RunResult result) { + try { + StringBuilder message = new StringBuilder(); + + List<BpmnEngine.TaskDescription> listTaskDescriptions = runScenario.getBpmnEngine() + .searchTasksByProcessInstanceId(processInstanceId, verificationActivity.taskId, 100); + message.append("CheckTask: PID["); + message.append(processInstanceId); + message.append("] VerifTaskName["); + message.append(verificationActivity.taskId); + + // Does a type is expected? + if (verificationActivity.getType() != null) { + message.append("] type["); + message.append(verificationActivity.getType()); + listTaskDescriptions = listTaskDescriptions.stream() + .filter(t -> (verificationActivity.getType().toString().equalsIgnoreCase(t.type.toString()))) // + .toList(); + } + + // Does a state is expected? + if (verificationActivity.state != null) { + message.append("] State["); + message.append(verificationActivity.state); + listTaskDescriptions = listTaskDescriptions.stream() + .filter(t -> ((t.isCompleted && ScenarioVerificationTask.StepState.COMPLETED.toString() + .equals(verificationActivity.state.toString())) || (!t.isCompleted + && ScenarioVerificationTask.StepState.ACTIVE.toString().equals(verificationActivity.state.toString())))) + .toList(); + } + + // Now, check the result + + boolean isSuccess = listTaskDescriptions.size() == verificationActivity.getNumberOfTasks(); + + message.append("] NumberExpectedTask: "); + message.append(verificationActivity.getNumberOfTasks()); + message.append(" Found: "); + message.append(listTaskDescriptions.size()); + message.append(" status: "); + message.append(isSuccess); + result.addVerification(verificationActivity, isSuccess, message.toString()); + + if (runScenario.getRunParameters().showLevelMonitoring()) + logger.info("ScnScenarioVerification.CheckActivity [{}] Success {} - {} ", verificationActivity.getTaskId(), + isSuccess, message); + } catch (AutomatorException e) { + result.addVerification(verificationActivity, false, "Error " + e.getMessage()); + } + } - return result; - - } - - /** - * Check to see if verificationActivity is correct or not - * - * @param runScenario scenario to pilot the verification - * @param processInstanceId ProcessInstanceId to check - * @param verificationActivity activity to check - * @param result the result object - */ - private void checkTask(RunScenario runScenario, - String processInstanceId, - ScenarioVerificationTask verificationActivity, - RunResult result) { - - try { - StringBuilder message = new StringBuilder(); - - List<BpmnEngine.TaskDescription> listTaskDescriptions = runScenario.getBpmnEngine() - .searchTasksByProcessInstanceId(processInstanceId, verificationActivity.taskId, 100); - if (listTaskDescriptions.size() != verificationActivity.getNumberOfTasks()) { - message.append("CheckTask: FAILED_NOTASK Search Task PID["); - message.append(processInstanceId); - message.append("] expected Task Name["); - message.append(verificationActivity.taskId); - message.append("] Number of tasks expected: "); - message.append(verificationActivity.getNumberOfTasks()); - message.append(", found "); - message.append(listTaskDescriptions.size()); - } - // check the type for each taskDescription - List<BpmnEngine.TaskDescription> listNotExpected = listTaskDescriptions.stream() - .filter(t -> !(verificationActivity.getType() != null && verificationActivity.getType() - .toString() - .equalsIgnoreCase(t.type.toString()))) - .filter(t -> ((t.isCompleted && ScenarioVerificationTask.StepState.COMPLETED.toString() - .equals(verificationActivity.state.toString())) || (!t.isCompleted - && ScenarioVerificationTask.StepState.ACTIVE.toString().equals(verificationActivity.state.toString())))) - .toList(); - if (!listNotExpected.isEmpty()) { - message.append("CheckTask: FAILED_BADTYPE PID["); - message.append(processInstanceId); - message.append("] Task["); - message.append(verificationActivity.taskId); - message.append("] type expected ["); - message.append(verificationActivity.type.toString()); - message.append("] FAILED, received "); - message.append(listNotExpected.stream().map(t -> t.taskId + ":" + t.type.toString()).toList()); - } - result.addVerification(verificationActivity, message.isEmpty(), message.toString()); - - if (runScenario.getRunParameters().showLevelDebug()) - logger.info("ScnScenarioVerification.CheckActivity [{}] Success {} ", verificationActivity.getTaskId(), - message.isEmpty() + " - " + message); - } catch (AutomatorException e) { - result.addVerification(verificationActivity, false, "Error " + e.getMessage()); + + private void checkVariable(RunScenario runScenario, + String processInstanceId, + ScenarioVerificationVariable verificationActivity, + RunResult result) { + try { + StringBuilder message = new StringBuilder(); + + Map<String, Object> variables = runScenario.getBpmnEngine().getVariables(processInstanceId); + message.append("CheckVariable: PID["); + message.append(processInstanceId); + message.append("] ExpectVariable["); + message.append(verificationActivity.name); + message.append("] ExpectedValue["); + message.append(verificationActivity.value); + + boolean isSuccess = false; + if (variables.containsKey(verificationActivity.name)) { + Object value = variables.get(verificationActivity.name); + message.append("] value["); + message.append(value); + if (value == null && verificationActivity.value == null) + isSuccess = true; + else if (value == null || verificationActivity.value == null) { + isSuccess = false; + } else { + // None of them is null here + isSuccess = value.toString().equals(verificationActivity.value.toString()); + } + } else + isSuccess = false; + + message.append("] status: "); + message.append(isSuccess); + + result.addVerification(verificationActivity, isSuccess, message.toString()); + + if (runScenario.getRunParameters().showLevelDebug()) + logger.info("ScnScenarioVerification.CheckVariable [{}] Success {} - {} ", verificationActivity.name, + isSuccess, message); + } catch (AutomatorException e) { + result.addVerification(verificationActivity, false, "Error " + e.getMessage()); + } } - } - private void checkVariable(RunScenario runScenario, - String processInstanceId, - ScenarioVerificationVariable activity, - RunResult result) { - } + /** + * checkPerformance + * + * @param runScenario scenario to check + * @param processInstanceId processInstanceId + * @param verificationActivity verificationActivity + * @param result complete the result + */ + private void checkPerformance(RunScenario runScenario, + String processInstanceId, + ScenarioVerificationPerformance verificationActivity, + RunResult result) { + try { + StringBuilder message = new StringBuilder(); + + List<BpmnEngine.TaskDescription> listTaskDescriptions = runScenario.getBpmnEngine() + .searchTasksByProcessInstanceId(processInstanceId, null, 100); + message.append("CheckPerformance: PID["); + message.append(processInstanceId); + message.append(" :"); + message.append(verificationActivity.getSynthesis()); + + Optional<BpmnEngine.TaskDescription> taskFrom = listTaskDescriptions.stream().filter(t -> (t.taskId.equals(verificationActivity.fromFlowNode))).findFirst(); + Optional<BpmnEngine.TaskDescription> taskTo = listTaskDescriptions.stream().filter(t -> (t.taskId.equals(verificationActivity.toFlowNode))).findFirst(); + + boolean isSuccess = true; + + if (!taskFrom.isPresent() || !taskTo.isPresent()) { + isSuccess = false; + message.append("Missing task: From [" + taskFrom.isPresent() + "] To [" + taskTo.isPresent() + "]"); + } else { + Date dateFrom = verificationActivity.getFromMarker() == ScenarioVerificationPerformance.Marker.BEGIN ? taskFrom.get().startDate : taskFrom.get().endDate; + Date endFrom = verificationActivity.getToMarker() == ScenarioVerificationPerformance.Marker.BEGIN ? taskTo.get().startDate : taskTo.get().endDate; + long durationExecution = Math.abs(endFrom.getTime() - dateFrom.getTime()); + + isSuccess = durationExecution < verificationActivity.getDurationInMs(); + message.append("ExpectExecution(ms): "); + message.append(verificationActivity.getDurationInMs()); + message.append(" Execution(ms): "); + message.append(durationExecution); + } + message.append(" status: "); + message.append(isSuccess); + + result.addVerification(verificationActivity, isSuccess, message.toString()); + + if (runScenario.getRunParameters().showLevelMonitoring()) + logger.info("ScnScenarioVerification.CheckActivity [{}] Success {} - {} ", verificationActivity.getSynthesis(), + isSuccess, message); + } catch (AutomatorException e) { + result.addVerification(verificationActivity, false, "Error " + e.getMessage()); + } + } + } diff --git a/src/main/java/org/camunda/automator/services/AutomatorStartup.java b/src/main/java/org/camunda/automator/services/AutomatorStartup.java index 2610063..bf189b7 100644 --- a/src/main/java/org/camunda/automator/services/AutomatorStartup.java +++ b/src/main/java/org/camunda/automator/services/AutomatorStartup.java @@ -20,254 +20,257 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service public class AutomatorStartup { - static Logger logger = LoggerFactory.getLogger(AutomatorStartup.class); + static Logger logger = LoggerFactory.getLogger(AutomatorStartup.class); - @Autowired - ConfigurationStartup configurationStartup; + @Autowired + ConfigurationStartup configurationStartup; - @Autowired - AutomatorAPI automatorAPI; + @Autowired + AutomatorAPI automatorAPI; - @Autowired - AutomatorCLI automatorCLI; + @Autowired + AutomatorCLI automatorCLI; - @Autowired - BpmnEngineList engineConfiguration; + @Autowired + BpmnEngineList engineConfiguration; - @Autowired - ServiceAccess serviceAccess; + @Autowired + ServiceAccess serviceAccess; - @PostConstruct - public void init() { - if (AutomatorCLI.isRunningCLI) - return; + @PostConstruct + public void init() { + if (AutomatorCLI.isRunningCLI) + return; - logger.info("AutomatorStartup-start"); - AutomatorSetupRunnable automatorSetupRunnable = new AutomatorSetupRunnable(configurationStartup, automatorAPI, - automatorCLI, engineConfiguration); + logger.info("AutomatorStartup-start"); + AutomatorSetupRunnable automatorSetupRunnable = new AutomatorSetupRunnable(configurationStartup, automatorAPI, + automatorCLI, engineConfiguration); - // start the automator startup immediately, and Spring can continue to start the application - serviceAccess.getTaskScheduler("AutomatorSetup").schedule(automatorSetupRunnable, Instant.now()); + // start the automator startup immediately, and Spring can continue to start the application + serviceAccess.getTaskScheduler("AutomatorSetup").schedule(automatorSetupRunnable, Instant.now()); - } + } - private void runFixedWarmup() { - // Fixed Warmup - if (configurationStartup.getWarmingUpServer().getSeconds() > 30) { - logger.info("WarmupFixedTime: wait.... {} s", configurationStartup.getWarmingUpServer().getSeconds()); + private void runFixedWarmup() { + // Fixed Warmup + if (configurationStartup.getWarmingUpServer().getSeconds() > 30) { + logger.info("WarmupFixedTime: wait.... {} s", configurationStartup.getWarmingUpServer().getSeconds()); - try { - Thread.sleep(configurationStartup.getWarmingUpServer().toMillis()); - } catch (Exception e) { - // do nothing - } - logger.info("WarmupFixedTime: end"); - } - } - - /** - * Load all scenario. List of File or Resource - * - * @return list of scenario - */ - private List<Object> registerScenario() { - List<Object> scenarioList = new ArrayList<>(); - // File - if (configurationStartup.getScenarioFileAtStartup().isEmpty()) { - logger.info("No scenario [File] from variable {} given", configurationStartup.getScenarioFileAtStartupName()); - } else { - logger.info("Detect {} scenario [File] from variable [{}] ScenarioPath[{}]", - configurationStartup.getScenarioFileAtStartup().size(), configurationStartup.getScenarioFileAtStartupName(), - configurationStartup.scenarioPath); - - for (String scenarioFileName : configurationStartup.getScenarioFileAtStartup()) { - logger.info("Register scenario [File] [{}]", scenarioFileName); - - File scenarioFile = new File(configurationStartup.scenarioPath + "/" + scenarioFileName); - if (!scenarioFile.exists()) { - scenarioFile = new File(scenarioFileName); - } - if (!scenarioFile.exists()) { - logger.error("ScenarioFile: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, - scenarioFileName, scenarioFileName); - continue; - } - scenarioList.add(scenarioFile); - } - } - // Resource - if (configurationStartup.getScenarioResourceAtStartup().isEmpty()) { - logger.info("No scenario [Resource] from variable {} given", - configurationStartup.getScenarioResourceAtStartupName()); - } else { - logger.info("Detect {} scenario [Resource] from variable [{}]", - configurationStartup.getScenarioResourceAtStartup().size(), - configurationStartup.getScenarioResourceAtStartupName()); - - for (Resource resource : configurationStartup.getScenarioResourceAtStartup()) { - if (resource != null) { - logger.info("Load scenario [Resource] from [{}]", resource.getDescription()); - scenarioList.add(resource); + try { + Thread.sleep(configurationStartup.getWarmingUpServer().toMillis()); + } catch (Exception e) { + // do nothing + } + logger.info("WarmupFixedTime: end"); } - } } - return scenarioList; - } + /** + * Load all scenario. List of File or Resource + * + * @return list of scenario + */ + private List<Object> registerScenario() { + List<Object> scenarioList = new ArrayList<>(); + // File + if (configurationStartup.getScenarioFileAtStartup().isEmpty()) { + logger.info("No scenario [File] from variable {} given", configurationStartup.getScenarioFileAtStartupName()); + } else { + logger.info("Detect {} scenario [File] from variable [{}] ScenarioPath[{}]", + configurationStartup.getScenarioFileAtStartup().size(), configurationStartup.getScenarioFileAtStartupName(), + configurationStartup.scenarioPath); + + for (String scenarioFileName : configurationStartup.getScenarioFileAtStartup()) { + logger.info("Register scenario [File] [{}]", scenarioFileName); + + File scenarioFile = new File(configurationStartup.scenarioPath + "/" + scenarioFileName); + if (!scenarioFile.exists()) { + scenarioFile = new File(scenarioFileName); + } + if (!scenarioFile.exists()) { + logger.error("ScenarioFile: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, + scenarioFileName, scenarioFileName); + continue; + } + scenarioList.add(scenarioFile); + } + } + // Resource + if (configurationStartup.getScenarioResourceAtStartup().isEmpty()) { + logger.info("No scenario [Resource] from variable {} given", + configurationStartup.getScenarioResourceAtStartupName()); + } else { + List<Resource> scenarioResource = configurationStartup.getScenarioResourceAtStartup().stream() + .filter(t -> t != null) + .collect(Collectors.toList()); + + logger.info("Detect {} scenario [Resource] from variable [{}]", + scenarioResource.size(), + configurationStartup.getScenarioResourceAtStartupName()); + + for (Resource resource : scenarioResource) { + logger.info("Load scenario [Resource] from [{}]", resource.getDescription()); + scenarioList.add(resource); + } + } - /** - * AutomatorSetupRunnable - run in parallel - */ - class AutomatorSetupRunnable implements Runnable { + return scenarioList; + } - ConfigurationStartup configurationStartup; + /** + * AutomatorSetupRunnable - run in parallel + */ + class AutomatorSetupRunnable implements Runnable { - AutomatorAPI automatorAPI; + ConfigurationStartup configurationStartup; - AutomatorCLI automatorCLI; + AutomatorAPI automatorAPI; - BpmnEngineList engineConfiguration; + AutomatorCLI automatorCLI; - public AutomatorSetupRunnable(ConfigurationStartup configurationStartup, - AutomatorAPI automatorAPI, - AutomatorCLI automatorCLI, - BpmnEngineList engineConfiguration) { - this.configurationStartup = configurationStartup; - this.automatorAPI = automatorAPI; - this.automatorCLI = automatorCLI; - this.engineConfiguration = engineConfiguration; - } + BpmnEngineList engineConfiguration; - @Override - public void run() { - - RunParameters runParameters = new RunParameters(); - runParameters.setExecution(true) - .setServerName(configurationStartup.getServerName()) - .setLogLevel(configurationStartup.getLogLevelEnum()) - .setCreation(configurationStartup.isPolicyExecutionCreation()) - .setServiceTask(configurationStartup.isPolicyExecutionServiceTask()) - .setUserTask(configurationStartup.isPolicyExecutionUserTask()) - .setWarmingUp(configurationStartup.isPolicyExecutionWarmingUp()) - .setDeploymentProcess(configurationStartup.isPolicyDeployProcess()) - .setDeepTracking(configurationStartup.deepTracking()) - .setStartEventNbThreads(configurationStartup.getStartEventNbThreads()); - List<String> filterService = configurationStartup.getFilterService(); - if (filterService != null) { - runParameters.setFilterExecutionServiceTask(filterService); - } - - logger.info( - "AutomatorStartup parameters serverName[{}] warmingUp[{}] creation:[{}] serviceTask:[{}] userTask:[{}] ScenarioPath[{}] logLevel[{}] waitWarmingUpServer[{} s]", - runParameters.getServerName(), runParameters.isWarmingUp(), runParameters.isCreation(), - runParameters.isServiceTask(), runParameters.isUserTask(), configurationStartup.scenarioPath, - configurationStartup.logLevel, configurationStartup.getWarmingUpServer().toMillis() / 1000); - - try { - String currentPath = new java.io.File(".").getCanonicalPath(); - logger.info("Local Path[{}]", currentPath); - } catch (Exception e) { - logger.error("Can't access Local Path : {} ", e.getMessage()); - } - - runFixedWarmup(); - - // Load scenario - List<Object> scenarioList = registerScenario(); - - // now proceed all scenario - for (Object scenarioObject : scenarioList) { - Scenario scenario = null; - if (scenarioObject instanceof File scenarioFile) - try { - scenario = automatorAPI.loadFromFile(scenarioFile); - } catch (Exception e) { - logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.getAbsolutePath(), - e.getMessage()); - } - else if (scenarioObject instanceof Resource scenarioResource) { - try { - scenario = automatorAPI.loadFromInputStream(scenarioResource.getInputStream(), - scenarioResource.getDescription()); - } catch (Exception e) { - logger.error("Error during accessing InputStream from resource [{}]: {}", scenarioResource.getDescription(), - e.getMessage()); - } + public AutomatorSetupRunnable(ConfigurationStartup configurationStartup, + AutomatorAPI automatorAPI, + AutomatorCLI automatorCLI, + BpmnEngineList engineConfiguration) { + this.configurationStartup = configurationStartup; + this.automatorAPI = automatorAPI; + this.automatorCLI = automatorCLI; + this.engineConfiguration = engineConfiguration; } - if (scenario == null) - continue; - logger.info("Start scenario [{}] on (1)ScenarioServerName[{}] (2)ConfigurationServerName[{}]", - scenario.getName(), scenario.getServerName(), runParameters.getServerName()); - - // BpmnEngine: find the correct one referenced in the scenario - int countEngineIsNotReady = 0; - BpmnEngine bpmnEngine = null; - boolean pleaseTryAgain; - String message = ""; - do { - pleaseTryAgain = false; - countEngineIsNotReady++; - try { - if (scenario.getServerName() != null && !scenario.getServerName().isEmpty()) { - message += "ScenarioServerName[" + scenario.getServerName() + "];"; - bpmnEngine = automatorAPI.getBpmnEngineFromScenario(scenario, engineConfiguration); - } else { - if (runParameters.getServerName() == null) - throw new AutomatorException("No Server define in configuration"); - message += "ConfigurationServerName[" + runParameters.getServerName() + "];"; - BpmnEngineList.BpmnServerDefinition serverDefinition = engineConfiguration.getByServerName( - runParameters.getServerName()); - if (serverDefinition == null) - throw new AutomatorException( - "Server [" + runParameters.getServerName() + "] does not exist in the list"); - - if (runParameters.showLevelMonitoring()) { - logger.info("Run scenario with Server {}", serverDefinition.getSynthesis()); - } - bpmnEngine = automatorAPI.getBpmnEngine(serverDefinition, true); - } - if (runParameters.showLevelDashboard()) { - logger.info("Scenario [{}] Connect to BpmnEngine {}", scenario.getName(), message); - } - if (!bpmnEngine.isReady()) { - bpmnEngine.connection(); + @Override + public void run() { + + RunParameters runParameters = new RunParameters(); + runParameters.setExecution(true) + .setServerName(configurationStartup.getServerName()) + .setLogLevel(configurationStartup.getLogLevelEnum()) + .setCreation(configurationStartup.isPolicyExecutionCreation()) + .setServiceTask(configurationStartup.isPolicyExecutionServiceTask()) + .setUserTask(configurationStartup.isPolicyExecutionUserTask()) + .setWarmingUp(configurationStartup.isPolicyExecutionWarmingUp()) + .setDeploymentProcess(configurationStartup.isPolicyDeployProcess()) + .setDeepTracking(configurationStartup.deepTracking()) + .setStartEventNbThreads(configurationStartup.getStartEventNbThreads()); + List<String> filterService = configurationStartup.getFilterService(); + if (filterService != null) { + runParameters.setFilterExecutionServiceTask(filterService); } - } catch (AutomatorException e) { - pleaseTryAgain = true; - message += "EXCEPT " + e.getMessage(); - } - if (pleaseTryAgain && countEngineIsNotReady < 10) { + logger.info( - "Scenario [{}] file[{}] No BPM ENGINE running [{}] tentative:{}/10. Sleep 30s. Scenario reference serverName[{}]", - message, countEngineIsNotReady, scenario.getName(), scenario.getName(), scenario.getServerName()); + "AutomatorStartup parameters serverName[{}] warmingUp[{}] creation:[{}] serviceTask:[{}] userTask:[{}] ScenarioPath[{}] logLevel[{}] waitWarmingUpServer[{} s]", + runParameters.getServerName(), runParameters.isWarmingUp(), runParameters.isCreation(), + runParameters.isServiceTask(), runParameters.isUserTask(), configurationStartup.scenarioPath, + configurationStartup.logLevel, configurationStartup.getWarmingUpServer().toMillis() / 1000); + try { - Thread.sleep(((long) 1000) * 30); - } catch (InterruptedException e) { - // nothing to do + String currentPath = new java.io.File(".").getCanonicalPath(); + logger.info("Local Path[{}]", currentPath); + } catch (Exception e) { + logger.error("Can't access Local Path : {} ", e.getMessage()); } - } - } while (pleaseTryAgain && countEngineIsNotReady < 10); - if (bpmnEngine == null) { - logger.error("Scenario [{}] file[{}] Server {} No BPM ENGINE running.", scenario.getName(), - scenario.getName(), message); - continue; - } + runFixedWarmup(); + + // Load scenario + List<Object> scenarioList = registerScenario(); + + // now proceed all scenario + for (Object scenarioObject : scenarioList) { + Scenario scenario = null; + if (scenarioObject instanceof File scenarioFile) + try { + scenario = automatorAPI.loadFromFile(scenarioFile); + } catch (Exception e) { + logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.getAbsolutePath(), + e.getMessage()); + } + else if (scenarioObject instanceof Resource scenarioResource) { + try { + scenario = automatorAPI.loadFromInputStream(scenarioResource.getInputStream(), + scenarioResource.getDescription()); + } catch (Exception e) { + logger.error("Error during accessing InputStream from resource [{}]: {}", scenarioResource.getDescription(), + e.getMessage()); + } + } + if (scenario == null) + continue; + logger.info("Start scenario [{}] on (1)ScenarioServerName[{}] (2)ConfigurationServerName[{}]", + scenario.getName(), scenario.getServerName(), runParameters.getServerName()); + + // BpmnEngine: find the correct one referenced in the scenario + int countEngineIsNotReady = 0; + BpmnEngine bpmnEngine = null; + boolean pleaseTryAgain; + String message = ""; + do { + pleaseTryAgain = false; + countEngineIsNotReady++; + try { + if (scenario.getServerName() != null && !scenario.getServerName().isEmpty()) { + message += "ScenarioServerName[" + scenario.getServerName() + "];"; + bpmnEngine = automatorAPI.getBpmnEngineFromScenario(scenario, engineConfiguration); + } else { + if (runParameters.getServerName() == null) + throw new AutomatorException("No Server define in configuration"); + message += "ConfigurationServerName[" + runParameters.getServerName() + "];"; + BpmnEngineList.BpmnServerDefinition serverDefinition = engineConfiguration.getByServerName( + runParameters.getServerName()); + if (serverDefinition == null) + throw new AutomatorException( + "Server [" + runParameters.getServerName() + "] does not exist in the list"); + + if (runParameters.showLevelMonitoring()) { + logger.info("Run scenario with Server {}", serverDefinition.getSynthesis()); + } + bpmnEngine = automatorAPI.getBpmnEngine(serverDefinition, true); + } + if (runParameters.showLevelDashboard()) { + logger.info("Scenario [{}] Connect to BpmnEngine {}", scenario.getName(), message); + } + + if (!bpmnEngine.isReady()) { + bpmnEngine.connection(); + } + } catch (AutomatorException e) { + pleaseTryAgain = true; + message += "EXCEPT " + e.getMessage(); + } + if (pleaseTryAgain && countEngineIsNotReady < 10) { + logger.info( + "Scenario [{}] file[{}] No BPM ENGINE running [{}] tentative:{}/10. Sleep 30s. Scenario reference serverName[{}]", + message, countEngineIsNotReady, scenario.getName(), scenario.getName(), scenario.getServerName()); + try { + Thread.sleep(((long) 1000) * 30); + } catch (InterruptedException e) { + // nothing to do + } + } + } while (pleaseTryAgain && countEngineIsNotReady < 10); + + if (bpmnEngine == null) { + logger.error("Scenario [{}] file[{}] Server {} No BPM ENGINE running.", scenario.getName(), + scenario.getName(), message); + continue; + } + + bpmnEngine.turnHighFlowMode(true); + logger.info("Scenario [{}] file[{}] use BpmnEngine {}", scenario.getName(), scenario.getName(), + bpmnEngine.getSignature()); + RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); + logger.info("AutomatorStartup: end scenario [{}] in {} ms", scenario.getName(), + scenarioExecutionResult.getTimeExecution()); + bpmnEngine.turnHighFlowMode(false); - bpmnEngine.turnHighFlowMode(true); - logger.info("Scenario [{}] file[{}] use BpmnEngine {}", scenario.getName(), scenario.getName(), - bpmnEngine.getSignature()); - RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); - logger.info("AutomatorStartup: end scenario [{}] in {} ms", scenario.getName(), - scenarioExecutionResult.getTimeExecution()); - bpmnEngine.turnHighFlowMode(false); - - } + } + } } - } } diff --git a/src/main/java/org/camunda/automator/services/ServiceAccess.java b/src/main/java/org/camunda/automator/services/ServiceAccess.java index f529d35..08a9dbd 100644 --- a/src/main/java/org/camunda/automator/services/ServiceAccess.java +++ b/src/main/java/org/camunda/automator/services/ServiceAccess.java @@ -16,20 +16,20 @@ @Configuration public class ServiceAccess { - private final Logger logger = LoggerFactory.getLogger(ServiceAccess.class); - @Autowired - public ServiceDataOperation serviceDataOperation; - @Value("${scheduler.poolSize}") - private int schedulerPoolSize; + private final Logger logger = LoggerFactory.getLogger(ServiceAccess.class); + @Autowired + public ServiceDataOperation serviceDataOperation; + @Value("${scheduler.poolSize}") + private int schedulerPoolSize; - /** - * Executor to run everything that is scheduled (also @Scheduled) - */ - public TaskScheduler getTaskScheduler(String schedulerName) { - ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setPoolSize(schedulerPoolSize); - scheduler.setThreadNamePrefix(schedulerName); - scheduler.initialize(); - return scheduler; - } + /** + * Executor to run everything that is scheduled (also @Scheduled) + */ + public TaskScheduler getTaskScheduler(String schedulerName) { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(schedulerPoolSize); + scheduler.setThreadNamePrefix(schedulerName); + scheduler.initialize(); + return scheduler; + } } diff --git a/src/main/java/org/camunda/automator/services/ServiceDataOperation.java b/src/main/java/org/camunda/automator/services/ServiceDataOperation.java index dcfa511..80d2944 100644 --- a/src/main/java/org/camunda/automator/services/ServiceDataOperation.java +++ b/src/main/java/org/camunda/automator/services/ServiceDataOperation.java @@ -13,39 +13,39 @@ @Component public class ServiceDataOperation { - static Logger logger = LoggerFactory.getLogger(ServiceDataOperation.class); - - @Autowired - private List<DataOperation> listDataOperation; - - /** - * please use the getInstance() - */ - private ServiceDataOperation() { - } - - /** - * Execute the DataOperation - * - * @param value value to process - * @param runScenario scenario to get information - * @param context give context in the exception in case of error - * @param index when multiple worker does the same operation, this is the index - * @return the value calculated - * @throws AutomatorException in case of error - */ - public Object execute(String value, RunScenario runScenario, String context, int index) throws AutomatorException { - for (DataOperation dataOperation : listDataOperation) { - if (dataOperation.match(value)) { - if (runScenario.getRunParameters().showLevelDebug()) - logger.info("Execute {} value[{}]", dataOperation.getName(), value); - return dataOperation.execute(value, runScenario, index); - } - } + static Logger logger = LoggerFactory.getLogger(ServiceDataOperation.class); + + @Autowired + private List<DataOperation> listDataOperation; - String helpOperations = listDataOperation.stream().map(DataOperation::getHelp).collect(Collectors.joining(", ")); + /** + * please use the getInstance() + */ + private ServiceDataOperation() { + } - throw new AutomatorException(context + "No operation for [" + value + "] - operationExpected " + helpOperations); - } + /** + * Execute the DataOperation + * + * @param value value to process + * @param runScenario scenario to get information + * @param context give context in the exception in case of error + * @param index when multiple worker does the same operation, this is the index + * @return the value calculated + * @throws AutomatorException in case of error + */ + public Object execute(String value, RunScenario runScenario, String context, int index) throws AutomatorException { + for (DataOperation dataOperation : listDataOperation) { + if (dataOperation.match(value)) { + if (runScenario.getRunParameters().showLevelDebug()) + logger.info("Execute {} value[{}]", dataOperation.getName(), value); + return dataOperation.execute(value, runScenario, index); + } + } + + String helpOperations = listDataOperation.stream().map(DataOperation::getHelp).collect(Collectors.joining(", ")); + + throw new AutomatorException(context + "No operation for [" + value + "] - operationExpected " + helpOperations); + } } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java index ed8c3cc..7d17284 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperation.java @@ -11,52 +11,52 @@ public abstract class DataOperation { - public abstract boolean match(String value); - - /** - * return the name of the operation - * - * @return name - */ - public abstract String getName(); - - /** - * @param value value from the function - * @param runScenario scenario to run - * @param index when multiple workers run the same operation, each worker has a uniq index - * @return the result of the operation - * @throws AutomatorException in case of error - */ - public abstract Object execute(String value, RunScenario runScenario, int index) throws AutomatorException; - - protected boolean matchFunction(String value, String function) { - return value.toUpperCase(Locale.ROOT).startsWith(function.toUpperCase(Locale.ROOT) + "("); - } - - protected List<String> extractArgument(String value, boolean resolveValue) throws AutomatorException { - List<String> listResult = new ArrayList<>(); - value = value.trim(); - // format is function(arg1, args2, arg3 - int pos = value.indexOf("("); - if (pos == -1 || !value.endsWith(")")) - throw new AutomatorException("Format must be function(args), received [" + value + "]"); - String args = value.substring(pos); - args = args.substring(1, args.length() - 1); - StringTokenizer st = new StringTokenizer(args, ","); - while (st.hasMoreTokens()) - listResult.add(st.nextToken()); - - // each args, if it start by a " or ', remove them - if (resolveValue) { - listResult = listResult.stream().map(t -> { - if (t.startsWith("\"") || t.startsWith("'")) - return t.substring(1, t.length() - 1); - else - return t; - }).collect(Collectors.toList()); + public abstract boolean match(String value); + + /** + * return the name of the operation + * + * @return name + */ + public abstract String getName(); + + /** + * @param value value from the function + * @param runScenario scenario to run + * @param index when multiple workers run the same operation, each worker has a uniq index + * @return the result of the operation + * @throws AutomatorException in case of error + */ + public abstract Object execute(String value, RunScenario runScenario, int index) throws AutomatorException; + + protected boolean matchFunction(String value, String function) { + return value.toUpperCase(Locale.ROOT).startsWith(function.toUpperCase(Locale.ROOT) + "("); } - return listResult; - } - public abstract String getHelp(); + protected List<String> extractArgument(String value, boolean resolveValue) throws AutomatorException { + List<String> listResult = new ArrayList<>(); + value = value.trim(); + // format is function(arg1, args2, arg3 + int pos = value.indexOf("("); + if (pos == -1 || !value.endsWith(")")) + throw new AutomatorException("Format must be function(args), received [" + value + "]"); + String args = value.substring(pos); + args = args.substring(1, args.length() - 1); + StringTokenizer st = new StringTokenizer(args, ","); + while (st.hasMoreTokens()) + listResult.add(st.nextToken()); + + // each args, if it start by a " or ', remove them + if (resolveValue) { + listResult = listResult.stream().map(t -> { + if (t.startsWith("\"") || t.startsWith("'")) + return t.substring(1, t.length() - 1); + else + return t; + }).collect(Collectors.toList()); + } + return listResult; + } + + public abstract String getHelp(); } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java index 0fe64fb..a6ffe81 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateList.java @@ -12,38 +12,38 @@ @Component public class DataOperationGenerateList extends DataOperation { - Logger logger = LoggerFactory.getLogger(DataOperationGenerateList.class); - - @Override - public String getName() { - return "GenerateList"; - } - - @Override - public boolean match(String value) { - return matchFunction(value, "generaterandomlist"); - } - - @Override - public String getHelp() { - return "generaterandomlist(<sizeOfTheList-integer>)"; - } - - @Override - public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { - - List<String> args = extractArgument(value, true); - List<String> listValues = new ArrayList<>(); - try { - Integer sizeList = Integer.valueOf(args.get(0)); - for (int i = 0; i < sizeList; i++) { - listValues.add("I" + i); - } - } catch (Exception e) { - throw new AutomatorException( - "can't generate a list: second parameters must be a Integer[" + args + "] : " + e.getMessage()); + Logger logger = LoggerFactory.getLogger(DataOperationGenerateList.class); + @Override + public String getName() { + return "GenerateList"; + } + + @Override + public boolean match(String value) { + return matchFunction(value, "generaterandomlist"); + } + + @Override + public String getHelp() { + return "generaterandomlist(<sizeOfTheList-integer>)"; + } + + @Override + public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { + + List<String> args = extractArgument(value, true); + List<String> listValues = new ArrayList<>(); + try { + Integer sizeList = Integer.valueOf(args.get(0)); + for (int i = 0; i < sizeList; i++) { + listValues.add("I" + i); + } + } catch (Exception e) { + throw new AutomatorException( + "can't generate a list: second parameters must be a Integer[" + args + "] : " + e.getMessage()); + + } + return listValues; } - return listValues; - } } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateUniqueID.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateUniqueID.java index 3833c1c..859a33f 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateUniqueID.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationGenerateUniqueID.java @@ -11,35 +11,35 @@ @Component public class DataOperationGenerateUniqueID extends DataOperation { - private final long baseTimer = System.currentTimeMillis(); - private final Map<String, Long> mapUniqueId = new HashMap<>(); - - @Override - public String getName() { - return "GenerateUniqueId"; - } - - @Override - public boolean match(String value) { - return matchFunction(value, "generateuniqueid"); - } - - @Override - public String getHelp() { - return "generateuniqueid(<prefix-String>)"; - } - - @Override - public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { - List<String> args = extractArgument(value, true); - String prefix = args.get(0); - if (prefix == null) - prefix = "default"; - - Long uniqueId = mapUniqueId.getOrDefault(prefix, 0L); - uniqueId++; - mapUniqueId.put(prefix, uniqueId); - return index + "-" + uniqueId + "-" + baseTimer; - - } + private final long baseTimer = System.currentTimeMillis(); + private final Map<String, Long> mapUniqueId = new HashMap<>(); + + @Override + public String getName() { + return "GenerateUniqueId"; + } + + @Override + public boolean match(String value) { + return matchFunction(value, "generateuniqueid"); + } + + @Override + public String getHelp() { + return "generateuniqueid(<prefix-String>)"; + } + + @Override + public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { + List<String> args = extractArgument(value, true); + String prefix = args.get(0); + if (prefix == null) + prefix = "default"; + + Long uniqueId = mapUniqueId.getOrDefault(prefix, 0L); + uniqueId++; + mapUniqueId.put(prefix, uniqueId); + return index + "-" + uniqueId + "-" + baseTimer; + + } } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java index 1847d6a..e4fd652 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationLoadFile.java @@ -12,42 +12,42 @@ @Component public class DataOperationLoadFile extends DataOperation { - @Override - public boolean match(String value) { - return matchFunction(value, "loadfile"); - } - - @Override - public String getName() { - return "LoadFile"; - } - - @Override - public String getHelp() { - return "loadfile(<CompletePathToTheFile>)"; - } - - @Override - public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { - File fileLoad = loadFile(value, runScenario); - - FileValue typedFileValue = Variables.fileValue(fileLoad.getName()).file(fileLoad) - // .mimeType("text/plain") - // .encoding("UTF-8") - .create(); - return typedFileValue; - - } - - private File loadFile(String value, RunScenario runScenario) throws AutomatorException { - List<String> args = extractArgument(value, true); - - if (args.size() != 1) { - throw new AutomatorException("Bad argument: loadfile(<fileName>)"); + @Override + public boolean match(String value) { + return matchFunction(value, "loadfile"); } - String formatArgs = args.get(0); - return ScenarioTool.loadFile(formatArgs, runScenario); + @Override + public String getName() { + return "LoadFile"; + } + + @Override + public String getHelp() { + return "loadfile(<CompletePathToTheFile>)"; + } - } + @Override + public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { + File fileLoad = loadFile(value, runScenario); + + FileValue typedFileValue = Variables.fileValue(fileLoad.getName()).file(fileLoad) + // .mimeType("text/plain") + // .encoding("UTF-8") + .create(); + return typedFileValue; + + } + + private File loadFile(String value, RunScenario runScenario) throws AutomatorException { + List<String> args = extractArgument(value, true); + + if (args.size() != 1) { + throw new AutomatorException("Bad argument: loadfile(<fileName>)"); + } + String formatArgs = args.get(0); + + return ScenarioTool.loadFile(formatArgs, runScenario); + + } } diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java index c8ffbd6..4e332c6 100644 --- a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationStringToDate.java @@ -17,64 +17,64 @@ @Component public class DataOperationStringToDate extends DataOperation { - public static final String FCT_LOCALDATETIME = "LOCALDATETIME"; - public static final String FCT_DATETIME = "DATETIME"; - public static final String FCT_DATE = "DATE"; - public static final String FCT_ZONEDATETIME = "ZONEDATETIME"; - public static final String FCT_LOCALDATE = "LOCALDATE"; - // visit https://docs.camunda.io/docs/components/modeler/bpmn/timer-events/#time-date - // 2019-10-01T12:00:00Z - public static final String ISO_8601_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - public static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd"; + public static final String FCT_LOCALDATETIME = "LOCALDATETIME"; + public static final String FCT_DATETIME = "DATETIME"; + public static final String FCT_DATE = "DATE"; + public static final String FCT_ZONEDATETIME = "ZONEDATETIME"; + public static final String FCT_LOCALDATE = "LOCALDATE"; + // visit https://docs.camunda.io/docs/components/modeler/bpmn/timer-events/#time-date + // 2019-10-01T12:00:00Z + public static final String ISO_8601_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + public static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd"; - @Override - public boolean match(String value) { - return matchFunction(value, "stringtodate"); - } + @Override + public boolean match(String value) { + return matchFunction(value, "stringtodate"); + } + + @Override + public String getName() { + return "StringToDate"; + } - @Override - public String getName() { - return "StringToDate"; - } + @Override + public String getHelp() { + return "stringtodate(" + FCT_LOCALDATETIME + "|" + FCT_DATETIME + "|" + FCT_DATE + "|" + FCT_ZONEDATETIME + "|" + + FCT_LOCALDATE + ", dateSt)"; + } - @Override - public String getHelp() { - return "stringtodate(" + FCT_LOCALDATETIME + "|" + FCT_DATETIME + "|" + FCT_DATE + "|" + FCT_ZONEDATETIME + "|" - + FCT_LOCALDATE + ", dateSt)"; - } + @Override + public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { + List<String> args = extractArgument(value, true); - @Override - public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { - List<String> args = extractArgument(value, true); + if (args.size() != 2) { + throw new AutomatorException("Bad argument: " + getHelp()); + } + String formatArgs = args.get(0).toUpperCase(Locale.ROOT); + String valueArgs = args.get(1); + try { + if (FCT_LOCALDATETIME.equals(formatArgs)) + return LocalDateTime.parse(valueArgs); - if (args.size() != 2) { - throw new AutomatorException("Bad argument: " + getHelp()); - } - String formatArgs = args.get(0).toUpperCase(Locale.ROOT); - String valueArgs = args.get(1); - try { - if (FCT_LOCALDATETIME.equals(formatArgs)) - return LocalDateTime.parse(valueArgs); + else if (FCT_DATETIME.equals(formatArgs)) { + SimpleDateFormat isoFormat = new SimpleDateFormat(ISO_8601_DATETIME_FORMAT); + return isoFormat.parse(valueArgs); // Date + } else if (FCT_DATE.equals(formatArgs)) { + SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT); + dateFormat.setLenient(false); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.parse(valueArgs, new ParsePosition(0)); + } else if (FCT_ZONEDATETIME.equals(formatArgs)) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_8601_DATETIME_FORMAT); + return ZonedDateTime.parse(valueArgs, formatter); + } else if (FCT_LOCALDATE.equals(formatArgs)) { + return LocalDate.parse(valueArgs, DateTimeFormatter.ofPattern(ISO_8601_DATE_FORMAT)); + } else + throw new AutomatorException("Unknown date formatter [" + formatArgs + "]"); + } catch (Exception e) { + throw new AutomatorException( + "parsing error function[" + formatArgs + "] value[" + valueArgs + "] : " + e.getMessage()); + } - else if (FCT_DATETIME.equals(formatArgs)) { - SimpleDateFormat isoFormat = new SimpleDateFormat(ISO_8601_DATETIME_FORMAT); - return isoFormat.parse(valueArgs); // Date - } else if (FCT_DATE.equals(formatArgs)) { - SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT); - dateFormat.setLenient(false); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - return dateFormat.parse(valueArgs, new ParsePosition(0)); - } else if (FCT_ZONEDATETIME.equals(formatArgs)) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_8601_DATETIME_FORMAT); - return ZonedDateTime.parse(valueArgs, formatter); - } else if (FCT_LOCALDATE.equals(formatArgs)) { - return LocalDate.parse(valueArgs, DateTimeFormatter.ofPattern(ISO_8601_DATE_FORMAT)); - } else - throw new AutomatorException("Unknown date formatter [" + formatArgs + "]"); - } catch (Exception e) { - throw new AutomatorException( - "parsing error function[" + formatArgs + "] value[" + valueArgs + "] : " + e.getMessage()); } - - } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c0a2aeb..61fe1d6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,9 +2,12 @@ automator: scheduler: + content: + repositoryPath: "c:/temp/processautomator" + uploadPath: "C:/dev/intellij/community/process-execution-automator/doc/unittestscenario/resources" startup: # give the server to run all tests at startup. The name must be registered in the list of server after - serverName: + serverName: Camunda8Ruby scenarioPath: # list of scenario separate by ; From a217c3bd007b56f3256b7d09746b8c1a5e0296fc Mon Sep 17 00:00:00 2001 From: Pierre-yves-monnet <pierre-yves.lonnet@laposte.net> Date: Wed, 4 Dec 2024 13:57:33 -0800 Subject: [PATCH 2/5] Add ContentManager to upload test --- README.md | 29 ++-- doc/unittestscenario/README.md | 74 +++++---- doc/unittestscenario/SendUnitTestCommand.rest | 21 ++- .../org/camunda/automator/AutomatorAPI.java | 4 +- .../org/camunda/automator/AutomatorCLI.java | 39 +++-- .../org/camunda/automator/AutomatorRest.java | 10 +- .../automator/bpmnengine/BpmnEngine.java | 7 +- .../BpmnEngineConfigurationInstance.java | 4 +- .../camunda7/BpmnEngineCamunda7.java | 8 +- .../camunda8/BpmnEngineCamunda8.java | 81 ++++++---- .../bpmnengine/dummy/BpmnEngineDummy.java | 6 +- .../configuration/BpmnEngineList.java | 15 +- .../ConfigurationServersEngine.java | 4 + .../automator/content/ContentManager.java | 144 ++++++++++++------ .../content/ContentRestController.java | 38 +++-- .../automator/content/RepositoryManager.java | 127 +++++++++++++++ .../automator/definition/Scenario.java | 41 +++-- .../definition/ScenarioVerification.java | 12 +- .../camunda/automator/engine/RunResult.java | 4 +- .../automator/engine/flow/RunObjectives.java | 9 +- .../flow/RunScenarioFlowServiceTask.java | 3 +- .../engine/flow/RunScenarioWarmingUp.java | 3 +- .../automator/services/AutomatorStartup.java | 142 ++++++++--------- src/main/resources/application.yaml | 9 +- .../java/automatorapi/TestSimpleUserTask.java | 4 +- 25 files changed, 571 insertions(+), 267 deletions(-) create mode 100644 src/main/java/org/camunda/automator/content/RepositoryManager.java diff --git a/README.md b/README.md index 62e0765..700a9d4 100644 --- a/README.md +++ b/README.md @@ -449,43 +449,36 @@ automator.servers: Rebuilt the image via ```` mvn clean install -mvn springboot:build-image ```` +# Push the docker image The docker image is build using the Dockerfile present on the root level. Push the image to -``` -ghcr.io/camunda-community-hub/process-execution-automator: -``` - -## Detail - -Run command -```` -mvn clean install -```` -Now, create a docker image ```` -docker build -t pierre-yves-monnet/processautomator:1.7.1 . +docker build -t pierre-yves-monnet/process-execution-automator:1.8.0 . +docker build pycamunda/camunda-community-hub/process-execution-automator:1.8.0 ```` Push the image to the Camunda hub (you must be login first to the docker registry) ```` -docker tag pierre-yves-monnet/processautomator:1.7.1 ghcr.io/camunda-community-hub/process-execution-automator:1.7.1 -docker push ghcr.io/camunda-community-hub/process-execution-automator:1.7.1 - +docker tag pierre-yves-monnet/process-execution-automator:1.8.0 ghcr.io/camunda-community-hub/process-execution-automator:1.8.0 +docker push ghcr.io/camunda-community-hub/process-execution-automator:1.8.0 ```` +docker tag pierre-yves-monnet/process-execution-automator:1.8.0 pycamunda/camunda-hub:process-execution-automator-1.8.0 +docker push pycamunda/camunda-hub:process-execution-automator-1.8.0 + Tag as the latest: ```` -docker tag pierre-yves-monnet/processautomator:1.7.1 ghcr.io/camunda-community-hub/process-execution-automator:latest +docker tag pierre-yves-monnet/process-execution-automator:1.8.0 ghcr.io/camunda-community-hub/process-execution-automator:latest docker push ghcr.io/camunda-community-hub/process-execution-automator:latest ```` Check on -https://github.com/camunda-community-hub/process-execution-automator/pkgs/container/process-execution-automator +https://github.com/camunda-community-hub/zeebe-cherry-runtime/pkgs/container/process-execution-automator + diff --git a/doc/unittestscenario/README.md b/doc/unittestscenario/README.md index 4f486ef..44a02c5 100644 --- a/doc/unittestscenario/README.md +++ b/doc/unittestscenario/README.md @@ -16,26 +16,18 @@ There is multiple use case: ### Verification (path and performance) -![Process](../explanationProcess.png) +![ScoreAcceptance.png](resources/ScoreAcceptance.png) in a CD/CI, you want to verify that a process follows the same behavior in the same performance time. Running every day (or hours) or asking via an API call to replay a scenario is useful to -verify there is no difference. If the customer is 4555, do we still move the process instance to -Review Level 1"? The second verification is the performance. The scenario can record an expected -duration target (for example, 4 seconds to execute the Get Context service task. Does the execution -still at this time? +verify there is no difference. If the score is 200, do we still move the process instance to +Send Acceptation? -### Coverage report - -Execute multiple scenarios to be sure that all the process is covered correctly. An "Execution -round" is a set of scenarios executed at the same time. At the end of the execution, a coverage test -can be performed. A CD/CI verification may be to check the scenario execution, the target time, and -the coverage. ### Advance process instances to a step for development -During the development, you verify the task "Notify applicant". To test it in the situation, you -must have a process instance in the process and pass four user tasks. Each test takes time: when you +During the development, you debug the task "Send rejection". To test it in the situation, you +must have a process instance in the process and pass all user tasks. Each test takes time: when you deploy a new process or want a new process instance, you need to execute again the different user task. Using Automator with the correct scenario solves the issue. Deploy a new process, but instead of starting from the beginning of a new process instance, start it via Automator. The scenario will @@ -50,27 +42,55 @@ In the unit scenario, you should place some Event (for example, the end event): This verification implies to give an Operate access. -The scenario will contains: - -The name, the process ID +The scenario contain: -A list of flow to execute under the attribut `executions` +* The name, the process ID, +* A list of flow to execute under attribut `executions` +* A list of verification under attribut `verifications` -* a STARTEVENT, to start one process instance -* the list of all SERVICETASK ## Scenario definition -## Generate from a real execution -Automator can generate a scenario from a real execution. The user creates a process instance and -executes it. It executes user tasks until the end of the process instance or at a certain point. Via -the UI (or the API), the user gives the process instance. Automator queries Camunda Engine to -collect the history of the process and, for each user task, which variable was provided. A new -scenario is created from this example. +Check the scenario: -Note: this function is yet available +[ScoreAcceptanceScn.json](resources/ScoreAcceptanceScn.json) ## execute -In progress +1. First, upload the scenario file in a config map + +``` +kubectl create configmap scoreacceptancescn --from-file=doc/unittestscenario/resources/scoreacceptancescn.json -n camunda +``` + +2. Deploy the scenario on the cluster, via the Modeler + +3. Create the pod process-execution-automator + +``` +kubectl create -f doc/unittestscenario/resources/UnittestAutomator.yaml -n camunda +``` +This configuration will upload the scenario + + +4. Port forward + +``` +kubectl port-forward svc/process-execution-automator 8381:8381 -n camunda +``` + +6. Check the scenario is uploaded + +``` +curl -X GET "http://localhost:8381/api/content/list" -H "Content-Type: application/json" +``` + + +7. upload the scenario +``` +curl -X POST -F "file=@/path/to/your/file.txt" http://localhost:8080/api/files/upload + +curl -X GET "http://localhost:8381/api/unittest/get?id=1732767184446" -H "Content-Type: application/json" +``` + diff --git a/doc/unittestscenario/SendUnitTestCommand.rest b/doc/unittestscenario/SendUnitTestCommand.rest index a41ae51..92c0bcb 100644 --- a/doc/unittestscenario/SendUnitTestCommand.rest +++ b/doc/unittestscenario/SendUnitTestCommand.rest @@ -1,5 +1,5 @@ ### POST Request -POST http://localhost:8381/api/unittest/run?name=ScoreAcceptanceScn&server=Camunda8Ruby&wait=false +POST http://localhost:8381/api/unittest/run?name=ScoreAcceptanceScn&server=Camunda8Ruby&wait=true Content-Type: application/json { @@ -19,3 +19,22 @@ Content-Type: application/json { } + +### Content Manager +GET http://localhost:8381/api/content/list +Content-Type: application/json + +{ +} + +### Upload file +POST http://localhost:8381/api/content/add +Content-Type: multipart/form-data + +--boundary +Content-Disposition: form-data; name="File"; filename="file1.txt" +Content-Type: text/plain + +< ./resources/ScoreAcceptanceScn.json + +--boundary-- diff --git a/src/main/java/org/camunda/automator/AutomatorAPI.java b/src/main/java/org/camunda/automator/AutomatorAPI.java index 7683984..baba334 100644 --- a/src/main/java/org/camunda/automator/AutomatorAPI.java +++ b/src/main/java/org/camunda/automator/AutomatorAPI.java @@ -20,8 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.io.File; import java.io.InputStream; +import java.nio.file.Path; @Component public class AutomatorAPI { @@ -48,7 +48,7 @@ public Scenario createScenario() { * @return the scenario * @throws AutomatorException if scenario can't be read */ - public Scenario loadFromFile(File scenarioFile) throws AutomatorException { + public Scenario loadFromFile(Path scenarioFile) throws AutomatorException { return Scenario.createFromFile(scenarioFile); } diff --git a/src/main/java/org/camunda/automator/AutomatorCLI.java b/src/main/java/org/camunda/automator/AutomatorCLI.java index 257785a..93665bc 100644 --- a/src/main/java/org/camunda/automator/AutomatorCLI.java +++ b/src/main/java/org/camunda/automator/AutomatorCLI.java @@ -17,8 +17,13 @@ import org.springframework.stereotype.Component; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; @SpringBootApplication @@ -98,14 +103,20 @@ private static BpmnEngineList decodeConfiguration(String propertiesFileName) thr throw new Exception("Not yet implemented"); } - private static List<File> detectRecursiveScenario(File folderRecursive) { - List<File> listFiles = new ArrayList<>(); - for (File file : folderRecursive.listFiles()) { - if (file.isDirectory()) { - listFiles.addAll(detectRecursiveScenario(file)); - } else if (file.getName().endsWith(".json")) { - listFiles.add(file); - } + private static List<Path> detectRecursiveScenario(Path folderRecursive) { + List<Path> listFiles = new ArrayList<>(); + try (Stream<Path> files = Files.list(folderRecursive)) { + // Iterate over all files in the directory + files.forEach(file -> { + if (Files.isRegularFile(file)) { + listFiles.add(file); + } + if (Files.isDirectory(file)) { + listFiles.addAll(detectRecursiveScenario(file)); + } + }); + } catch (IOException e) { + logger.error("During detection scenario file: {}", e.getMessage()); } return listFiles; } @@ -122,8 +133,8 @@ private static void logOutLn(String message) { public void run(String[] args) { if (!isRunningCLI) return; - File scenarioFile = null; - File folderRecursive = null; + Path scenarioFile = null; + Path folderRecursive = null; RunParameters runParameters = new RunParameters(); runParameters.setExecution(true) @@ -184,13 +195,13 @@ public void run(String[] args) { if (args.length < i + 1) throw new AutomatorException("Bad usage : run <scenarioFile>"); action = ACTION.RUN; - scenarioFile = new File(args[i + 1]); + scenarioFile = Paths.get(args[i + 1]); i++; } else if ("recursive".equals(args[i])) { if (args.length < i + 1) throw new AutomatorException("Bad usage : recursive <folder>"); action = ACTION.RECURSIVE; - folderRecursive = new File(args[i + 1]); + folderRecursive = Paths.get(args[i + 1]); i++; } else { printUsage(); @@ -230,8 +241,8 @@ public void run(String[] args) { logger.info(scenarioExecutionResult.getSynthesis(runParameters.isFullDetailsSynthesis())); } case RECURSIVE -> { - List<File> listScenario = detectRecursiveScenario(folderRecursive); - for (File scenarioFileIndex : listScenario) { + List<Path> listScenario = detectRecursiveScenario(folderRecursive); + for (Path scenarioFileIndex : listScenario) { Scenario scenario = automatorAPI.loadFromFile(scenarioFileIndex); BpmnEngine bpmnEngineScenario = automatorAPI.getBpmnEngine(serverDefinition, true); RunResult scenarioExecutionResult = automatorAPI.executeScenario( diff --git a/src/main/java/org/camunda/automator/AutomatorRest.java b/src/main/java/org/camunda/automator/AutomatorRest.java index a1b62a2..ce9c260 100644 --- a/src/main/java/org/camunda/automator/AutomatorRest.java +++ b/src/main/java/org/camunda/automator/AutomatorRest.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -116,11 +116,11 @@ private void startTest(String scenarioName, String serverName, String unitTestId // now proceed the scenario try { Scenario scenario = null; - File scenarioFile = contentManager.getFromName(scenarioName); + Path scenarioFile = contentManager.getFromName(scenarioName); try { scenario = automatorAPI.loadFromFile(scenarioFile); } catch (Exception e) { - logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.getAbsolutePath(), + logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.toAbsolutePath().toString(), e.getMessage()); } if (scenario == null) { @@ -140,13 +140,13 @@ private void startTest(String scenarioName, String serverName, String unitTestId return; } - bpmnEngine.turnHighFlowMode(true); + bpmnEngine.turnHighFlowMode(false); logger.info("Scenario [{}] file[{}] use BpmnEngine {}", scenario.getName(), scenario.getName(), bpmnEngine.getSignature()); RunResult scenarioExecutionResult = automatorAPI.executeScenario(bpmnEngine, runParameters, scenario); logger.info("AutomatorRest: end scenario [{}] in {} ms", scenario.getName(), scenarioExecutionResult.getTimeExecution()); - bpmnEngine.turnHighFlowMode(false); + resultMap.put(JSON_STATUS, "EXECUTED"); resultMap.putAll(resultToJson(scenarioExecutionResult)); diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java index 1ccc67c..f2be874 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java @@ -1,6 +1,7 @@ package org.camunda.automator.bpmnengine; -import io.camunda.operate.search.DateFilter; +// import io.camunda.operate.search.DateFilter; + import io.camunda.zeebe.client.api.worker.JobWorker; import org.camunda.automator.configuration.BpmnEngineList; import org.camunda.automator.definition.ScenarioDeployment; @@ -186,10 +187,10 @@ List<ProcessDescription> searchProcessInstanceByVariable(String processId, /* CountInformation */ /* */ /* ******************************************************************** */ - long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) + long countNumberOfProcessInstancesCreated(String processId, Date startDate, Date endDate) throws AutomatorException; - long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) + long countNumberOfProcessInstancesEnded(String processId, Date startDate, Date endDate) throws AutomatorException; long countNumberOfTasks(String processId, String taskId) throws AutomatorException; diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java index 89af914..6cfb3c7 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngineConfigurationInstance.java @@ -31,12 +31,14 @@ public static BpmnEngineList getCamunda7(String serverUrl) { return bpmEngineConfiguration; } - public static BpmnEngineList getCamunda8(String zeebeGatewayAddress) { + public static BpmnEngineList getCamunda8(String zeebeGatewayAddress, String zeebeGrpcAddress, String zeebeRestAddress) { BpmnEngineList bpmEngineConfiguration = new BpmnEngineList(); BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; serverDefinition.zeebeGatewayAddress = zeebeGatewayAddress; + serverDefinition.zeebeGrpcAddress = zeebeGrpcAddress; + serverDefinition.zeebeRestAddress = zeebeRestAddress; bpmEngineConfiguration.addExplicitServer(serverDefinition); diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java index 33e20d7..7f4cb16 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java @@ -1,6 +1,6 @@ package org.camunda.automator.bpmnengine.camunda7; -import io.camunda.operate.search.DateFilter; + import org.camunda.automator.bpmnengine.BpmnEngine; import org.camunda.automator.configuration.BpmnEngineList; import org.camunda.automator.definition.ScenarioDeployment; @@ -422,7 +422,7 @@ public Map<String, Object> getVariables(String processInstanceId) throws Automat /* ******************************************************************** */ @Override - public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) + public long countNumberOfProcessInstancesCreated(String processName, Date startDate, Date endDate) throws AutomatorException { try { @@ -445,7 +445,7 @@ public long countNumberOfProcessInstancesCreated(String processName, DateFilter Date datePI = stringToDate(t.getBusinessKey()); if (datePI == null) return false; - return datePI.after(startDate.getDate()); + return datePI.after(startDate); }).count(); } while (processInstanceDtos.size() >= SEARCH_MAX_SIZE && maxLoop < 1000); @@ -458,7 +458,7 @@ public long countNumberOfProcessInstancesCreated(String processName, DateFilter } @Override - public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) + public long countNumberOfProcessInstancesEnded(String processName, Date startDate, Date endDate) throws AutomatorException { throw new AutomatorException("Not yet implemented"); } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java index c79dc65..4084d77 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java @@ -10,7 +10,6 @@ import io.camunda.operate.CamundaOperateClientBuilder; import io.camunda.operate.exception.OperateException; import io.camunda.operate.model.*; -import io.camunda.operate.search.DateFilter; import io.camunda.operate.search.*; import io.camunda.tasklist.CamundaTaskListClient; import io.camunda.tasklist.CamundaTaskListClientBuilder; @@ -51,7 +50,7 @@ public class BpmnEngineCamunda8 implements BpmnEngine { private final BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy; boolean hightFlowMode = false; /** - * It is not possible to search user task for a specfic processInstance. So, to realize this, a marker is created in each process instance. Retrieving the user task, + * It is not possible to search user task for a specific processInstance. So, to realize this, a marker is created in each process instance. Retrieving the user task, * the process instance can be found and correction can be done */ Map<String, Long> cacheProcessInstanceMarker = new HashMap<>(); @@ -93,6 +92,8 @@ public static BpmnEngineCamunda8 getFromServerDefinition(BpmnEngineList.BpmnServ * @param tasklistUrl Url to access TaskList */ public static BpmnEngineCamunda8 getFromCamunda8(String zeebeSelfGatewayAddress, + String zeebeGrpcAddress, + String zeebeRestAddress, Boolean zeebePlainText, String operateUrl, String operateUserName, @@ -104,6 +105,8 @@ public static BpmnEngineCamunda8 getFromCamunda8(String zeebeSelfGatewayAddress, bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); bpmnEngineCamunda8.serverDefinition.zeebeGatewayAddress = zeebeSelfGatewayAddress; + bpmnEngineCamunda8.serverDefinition.zeebeGrpcAddress = zeebeGrpcAddress; + bpmnEngineCamunda8.serverDefinition.zeebeRestAddress = zeebeRestAddress; bpmnEngineCamunda8.serverDefinition.zeebePlainText = zeebePlainText; @@ -303,24 +306,31 @@ public List<String> searchUserTasksByProcessInstance(String processInstanceId, S taskSearch.setPagination(new Pagination().setPageSize(maxResult)); TaskList tasksList = taskClient.getTasks(taskSearch); + boolean getAllTasks = tasksList.size() < maxResult; List<String> listTasksResult = new ArrayList<>(); do { - listTasksResult.addAll(tasksList.getItems().stream().filter(t -> { - List<Variable> listVariables = t.getVariables(); - Optional<Variable> markerTask = listVariables.stream() - .filter(v -> v.getName().equals(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME)) - .findFirst(); - - if (markerTask.isEmpty()) - return false; - Long processInstanceIdTask = cacheProcessInstanceMarker.get(markerTask.get().getValue()); - return (processInstanceIdLong.equals(processInstanceIdTask)); - }).map(Task::getId) // Task to ID - .toList()); + if (!hightFlowMode) { + // We check that the task is the one expected + listTasksResult.addAll(tasksList.getItems().stream().filter(t -> { + List<Variable> listVariables = t.getVariables(); + Optional<Variable> markerTask = listVariables.stream() + .filter(v -> v.getName().equals(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME)) + .findFirst(); + if (markerTask.isEmpty()) + return false; + Long processInstanceIdTask = cacheProcessInstanceMarker.get(markerTask.get().getValue()); + return (processInstanceIdLong.equals(processInstanceIdTask)); + }).map(Task::getId) // Task to ID + .toList()); + } else { + listTasksResult.addAll(tasksList.getItems().stream() + .map(Task::getId) // Task to ID + .toList()); + } - if (tasksList.size() > 0) + if (tasksList.size() > 0 && !getAllTasks) tasksList = taskClient.after(tasksList); - } while (tasksList.size() > 0); + } while (tasksList.size() > 0 && !getAllTasks); return listTasksResult; @@ -409,6 +419,8 @@ public void executeUserTask(String userTaskId, String userId, Map<String, Object taskClient.completeTask(userTaskId, variables); } catch (TaskListException e) { throw new AutomatorException("Can't execute task [" + userTaskId + "]"); + } catch (Exception e) { + throw new AutomatorException("Can't execute task [" + userTaskId + "]"); } } @@ -622,7 +634,7 @@ public Map<String, Object> getVariables(String processInstanceId) throws Automat /* CountInformation */ /* */ /* ******************************************************************** */ - public long countNumberOfProcessInstancesCreated(String processId, DateFilter startDate, DateFilter endDate) + public long countNumberOfProcessInstancesCreated(String processId, Date startDate, Date endDate) throws AutomatorException { if (operateClient == null) { throw new AutomatorException("No Operate connection was provided"); @@ -644,7 +656,7 @@ public long countNumberOfProcessInstancesCreated(String processId, DateFilter st searchQuery.setSize(SEARCH_MAX_SIZE); searchResult = operateClient.searchProcessInstanceResults(searchQuery); - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate)).count(); } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); return cumul; @@ -653,7 +665,7 @@ public long countNumberOfProcessInstancesCreated(String processId, DateFilter st } } - public long countNumberOfProcessInstancesEnded(String processId, DateFilter startDate, DateFilter endDate) + public long countNumberOfProcessInstancesEnded(String processId, Date startDate, Date endDate) throws AutomatorException { if (operateClient == null) { throw new AutomatorException("No Operate connection was provided"); @@ -679,7 +691,7 @@ public long countNumberOfProcessInstancesEnded(String processId, DateFilter star SearchQuery searchQuery = queryBuilder.build(); searchQuery.setSize(SEARCH_MAX_SIZE); searchResult = operateClient.searchProcessInstanceResults(searchQuery); - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate.getDate())).count(); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate)).count(); } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); return cumul; @@ -855,6 +867,8 @@ else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) .build(); clientBuilder = ZeebeClient.newClientBuilder() .gatewayAddress(serverDefinition.zeebeGatewayAddress) + .grpcAddress(new URI(serverDefinition.zeebeGrpcAddress)) + .restAddress(new URI(serverDefinition.zeebeRestAddress)) .defaultTenantId(serverDefinition.zeebeTenantId == null ? "<default>" : serverDefinition.zeebeTenantId) .credentialsProvider(credentialsProvider); if (Boolean.TRUE.equals(serverDefinition.zeebePlainText)) @@ -867,11 +881,24 @@ else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) "BadCredential[" + serverDefinition.name + "] Analysis:" + analysis + " : " + e.getMessage()); } } else { - analysis.append("NoAuthentication;"); - // connect to local deployment; assumes that authentication is disabled - clientBuilder = ZeebeClient.newClientBuilder() - .gatewayAddress(serverDefinition.zeebeGatewayAddress) - .usePlaintext(); + try { + analysis.append("NoAuthentication;"); + // connect to local deployment; assumes that authentication is disabled + clientBuilder = ZeebeClient.newClientBuilder() + .gatewayAddress(serverDefinition.zeebeGatewayAddress); + if (serverDefinition.zeebeGrpcAddress != null) { + clientBuilder = clientBuilder.grpcAddress(new URI(serverDefinition.zeebeGrpcAddress)); + } + if (serverDefinition.zeebeRestAddress != null) { + clientBuilder = clientBuilder.restAddress(new URI(serverDefinition.zeebeRestAddress)); + } + clientBuilder = clientBuilder.usePlaintext(); + } catch (Exception e) { + zeebeClient = null; + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "badURL[" + serverDefinition.name + "] Analysis:" + analysis + " : " + e.getMessage()); + } } } else throw new AutomatorException("Invalid configuration"); @@ -1136,8 +1163,10 @@ private void connectTaskList(StringBuilder analysis) throws AutomatorException { // ---------------- connection try { - + taskListBuilder.zeebeClient(zeebeClient); + taskListBuilder.useZeebeUserTasks(); taskClient = taskListBuilder.build(); + analysis.append("successfully, "); } catch (Exception e) { diff --git a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java index 8be203f..5ed76ea 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java +++ b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java @@ -1,6 +1,5 @@ package org.camunda.automator.bpmnengine.dummy; -import io.camunda.operate.search.DateFilter; import org.camunda.automator.bpmnengine.BpmnEngine; import org.camunda.automator.configuration.BpmnEngineList; import org.camunda.automator.definition.ScenarioDeployment; @@ -12,6 +11,7 @@ import java.io.File; import java.time.Duration; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; @@ -123,13 +123,13 @@ public Map<String, Object> getVariables(String processInstanceId) throws Automat /* ******************************************************************** */ @Override - public long countNumberOfProcessInstancesCreated(String processName, DateFilter startDate, DateFilter endDate) + public long countNumberOfProcessInstancesCreated(String processName, Date startDate, Date endDate) throws AutomatorException { throw new AutomatorException("Not yet implemented"); } @Override - public long countNumberOfProcessInstancesEnded(String processName, DateFilter startDate, DateFilter endDate) + public long countNumberOfProcessInstancesEnded(String processName, Date startDate, Date endDate) throws AutomatorException { throw new AutomatorException("Not yet implemented"); } diff --git a/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java b/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java index 5222766..efa90bf 100644 --- a/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java +++ b/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java @@ -40,6 +40,8 @@ public class BpmnEngineList { public static final String CONF_OPERATE_AUDIENCE = "operateAudientce"; public static final String CONF_ZEEBE_GATEWAY_ADDRESS = "zeebeGatewayAddress"; + public static final String CONF_ZEEBE_GRPC_ADDRESS = "zeebeGrpcAddress"; + public static final String CONF_ZEEBE_REST_ADDRESS = "zeebeRestAddress"; public static final String CONF_URL = "url"; public static final String CONF_TYPE = "type"; public static final String CONF_TYPE_V_CAMUNDA_8 = "camunda8"; @@ -204,6 +206,12 @@ private List<BpmnServerDefinition> getFromServersList() throws AutomatorExceptio bpmnServerDefinition.serverType = CamundaEngine.CAMUNDA_8; bpmnServerDefinition.zeebeGatewayAddress = getString(CONF_ZEEBE_GATEWAY_ADDRESS, serverMap, null, contextLog, true); + bpmnServerDefinition.zeebeGrpcAddress = getString(CONF_ZEEBE_GRPC_ADDRESS, serverMap, null, contextLog, + false); + + bpmnServerDefinition.zeebeRestAddress = getString(CONF_ZEEBE_REST_ADDRESS, serverMap, null, contextLog, + false); + bpmnServerDefinition.zeebeClientId = getString(CONF_ZEEBE_CLIENT_ID, serverMap, null, contextLog, false); bpmnServerDefinition.zeebeClientSecret = getString(CONF_ZEEBE_SECRET, serverMap, null, contextLog, false); bpmnServerDefinition.zeebeAudience = getString(CONF_ZEEBE_AUDIENCE, serverMap, ZEEBE_DEFAULT_AUDIENCE, @@ -358,6 +366,9 @@ private List<BpmnServerDefinition> getFromServerConfiguration() { camunda8.serverType = CamundaEngine.CAMUNDA_8; camunda8.name = configurationServersEngine.zeebeName; camunda8.zeebeGatewayAddress = configurationServersEngine.zeebeGatewayAddress; + camunda8.zeebeGrpcAddress = configurationServersEngine.zeebeGrpcAddress; + camunda8.zeebeRestAddress = configurationServersEngine.zeebeRestAddress; + camunda8.workerExecutionThreads = parseInt("Camunda8." + CONF_WORKER_EXECUTION_THREADS, configurationServersEngine.zeebeWorkerExecutionThreads, DEFAULT_VALUE_EXECUTION_THREADS, ""); camunda8.workerMaxJobsActive = parseInt("Camunda8." + CONF_WORKER_MAX_JOBS_ACTIVE, @@ -511,6 +522,8 @@ public static class BpmnServerDefinition { * My Zeebe Address */ public String zeebeGatewayAddress; + public String zeebeGrpcAddress; + public String zeebeRestAddress; public Boolean zeebePlainText; /** @@ -580,7 +593,7 @@ public String getSynthesis() { synthesis += " url[" + camunda7ServerUrl + "] userName[" + camunda7UserName + "]"; } if (serverType.equals(CamundaEngine.CAMUNDA_8)) { - synthesis += " address[" + zeebeGatewayAddress + "] workerThread[" + workerExecutionThreads + "] MaxJobActive[" + synthesis += " GrpcAddress[" + zeebeGatewayAddress + "] RestAddress[" + zeebeRestAddress + "] workerThread[" + workerExecutionThreads + "] MaxJobActive[" + workerMaxJobsActive + "]"; } if (serverType.equals(CamundaEngine.CAMUNDA_8_SAAS)) { diff --git a/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java b/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java index d4877ae..8fb092d 100644 --- a/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java +++ b/src/main/java/org/camunda/automator/configuration/ConfigurationServersEngine.java @@ -34,6 +34,10 @@ public class ConfigurationServersEngine { public String zeebeName; @Value("${automator.servers.camunda8.zeebeGatewayAddress:''}") public String zeebeGatewayAddress; + @Value("${automator.servers.camunda8.zeebeGrpcAddress:''}") + public String zeebeGrpcAddress; + @Value("${automator.servers.camunda8.zeebeRestAddress:''}") + public String zeebeRestAddress; @Value("${automator.servers.camunda8.operateUrl:''}") public String zeebeOperateUrl; @Value("${automator.servers.camunda8.operateUserName:''}") diff --git a/src/main/java/org/camunda/automator/content/ContentManager.java b/src/main/java/org/camunda/automator/content/ContentManager.java index dafa0fd..4b2f866 100644 --- a/src/main/java/org/camunda/automator/content/ContentManager.java +++ b/src/main/java/org/camunda/automator/content/ContentManager.java @@ -1,10 +1,15 @@ package org.camunda.automator.content; +import org.camunda.automator.configuration.ConfigurationStartup; +import org.camunda.automator.definition.Scenario; +import org.camunda.automator.engine.AutomatorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; @@ -13,71 +18,114 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; @Component @PropertySource("classpath:application.yaml") @Configuration - public class ContentManager { + private static final Logger logger = LoggerFactory.getLogger(ContentManager.class.getName()); @Value("${automator.content.repositoryPath}") public String repositoryPath; @Value("${automator.content.uploadPath}") public String uploadPath; + @Autowired + ConfigurationStartup configurationStartup; + RepositoryManager repositoryManager = new RepositoryManager(); + @Value("${automator.content.scenario:}") + private Resource scenarioResource; - public File getFromName(String scenarioName) { - return new File(repositoryPath + File.separator + scenarioName + ".json"); + public Path getFromName(String scenarioName) { + return repositoryManager.getFromName(scenarioName); } - public void saveFromMultiPartFile(MultipartFile file, String fileName) { - // save the file on a temporary disk - OutputStream outputStream = null; - File fileScenario = null; + + @PostConstruct + public void init() { try { - fileScenario = new File(repositoryPath + File.separator + fileName); - // Open an OutputStream to the temporary file - outputStream = new FileOutputStream(fileScenario); - // Transfer data from InputStream to OutputStream - byte[] buffer = new byte[1024 * 100]; // 100Ko - int bytesRead; - int count = 0; - InputStream inputStream = file.getInputStream(); - while ((bytesRead = inputStream.read(buffer)) != -1) { - count += bytesRead; - outputStream.write(buffer, 0, bytesRead); - } - outputStream.flush(); - outputStream.close(); - outputStream = null; + repositoryManager.initializeRepository(repositoryPath); + loadUploadPath(); + LoadContentResource(); } catch (Exception e) { - logger.error("Can't load File [" + fileName + "] : " + e.getMessage()); - } finally { - if (outputStream != null) - try { - outputStream.close(); - } catch (Exception e) { - // do nothing - } + logger.error("ContentManager: error during initialization {}", e.getMessage()); } } - @PostConstruct - public void init() { - Path sourceDirectory = Paths.get(uploadPath); - Path targetDirectory = Paths.get(repositoryPath); - logger.info("ContentManager initiaalisation Copied: [{}] to [{}]", sourceDirectory, targetDirectory); - int nbFilesCopied = 0; - try { - // Create target directory if it doesn't exist - if (!Files.exists(targetDirectory)) { - Files.createDirectories(targetDirectory); + /* ******************************************************************** */ + /* */ + /* Repository management */ + /* */ + /* ******************************************************************** */ + + public List<Path> getContent() { + return repositoryManager.getContentRepository(); + } + + public List<Scenario> getContentScenario() { + List<Scenario> listScenario = new ArrayList<>(); + List<Path> listContent = repositoryManager.getContentRepository(); + for (Path path : listContent) { + // The content can have multiple files, not only scenario + if (! path.getFileName().toString().endsWith(".json")) + continue; + try { + Scenario scenario = Scenario.createFromFile(path); + listScenario.add(scenario); + } catch (AutomatorException e) { + logger.error("ContentManager/getContentScenario: path [{}] failed: {}", path.getFileName(), e.getMessage()); } + } + return listScenario; + } + + + public Path addFile(Path file) throws IOException { + return repositoryManager.addFile(file); + } + + public Path addResource(Resource resource) throws IOException { + return repositoryManager.addResource(resource); + } + + public Path addFromMultipart(MultipartFile file, String fileName) throws IOException { + // save the file on a temporary disk + return repositoryManager.addFromInputStream(file.getInputStream(), fileName); + } + /* ******************************************************************** */ + /* */ + /* Load management */ + /* */ + /* ******************************************************************** */ + + /** + * Load from the content resource. This is typicaly provided on a Pod + */ + private void LoadContentResource() { + if (scenarioResource == null) { + logger.info("ContentManager/LoadContentResource: No scenario resource"); + return; + } + try { + logger.info("ContentManager/LoadContentResource: Detect [Resource] name[{}]", scenarioResource.getFilename()); + Path scenario = repositoryManager.addResource(scenarioResource); + } catch (IOException e) { + logger.error("ContentManager/LoadContentResource: Error occurred: {} ", e.getMessage()); + } + } + + /** + * Upload from a path. This is typicaly provided on a local machine + */ + private void loadUploadPath() { + try { + Path sourceDirectory = Paths.get(uploadPath); + logger.info("ContentManager/Upload: from [{}]", sourceDirectory); + int nbFilesCopied = 0; // Copy all files from source to target List<Path> listFiles = Files.walk(sourceDirectory) .filter(Files::isRegularFile).toList(); @@ -85,20 +133,16 @@ public void init() { for (Path sourcePath : listFiles)// Filter only regular files { try { - Path targetPath = targetDirectory.resolve(sourceDirectory.relativize(sourcePath)); - Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); - logger.info("Copied: [{}] tp [{}]", sourcePath, targetPath); + repositoryManager.addFile(sourcePath); nbFilesCopied++; } catch (IOException e) { - logger.error("Error copying file: [{}] -> [{}] : {}", sourcePath, targetDirectory, e.getMessage()); + logger.error("ContentManager/Upload: Error copying[{}] -> [{}] : {}", sourcePath, repositoryManager.getRepositoryPath(), e.getMessage()); } } - + logger.info("ContentManager/Upload: upload {} files", nbFilesCopied); } catch (IOException e) { - logger.error("Error occurred: {} ", e.getMessage()); + logger.error("ContentManager/Upload: Error occurred: {} ", e.getMessage()); } - logger.info("End of ContentManager {} files copied", nbFilesCopied); } - } diff --git a/src/main/java/org/camunda/automator/content/ContentRestController.java b/src/main/java/org/camunda/automator/content/ContentRestController.java index 41db040..3f75eba 100644 --- a/src/main/java/org/camunda/automator/content/ContentRestController.java +++ b/src/main/java/org/camunda/automator/content/ContentRestController.java @@ -1,15 +1,20 @@ package org.camunda.automator.content; +import org.camunda.automator.definition.Scenario; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; -import java.util.HashMap; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -21,20 +26,35 @@ public class ContentRestController { @Autowired ContentManager contentManager; + /** + * curl -X POST "http://localhost:8381/api/content/add" -H "Content-Type: multipart/form-data" -F "File=@C:/dev/intellij/community/process-execution-automator/doc/unittestscenario/resources/ScoreAcceptanceScn.json" + **/ @PostMapping(value = "/api/content/add", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE}, produces = MediaType.APPLICATION_JSON_VALUE) - public Map<String, Object> upload(@RequestPart("File") List<MultipartFile> uploadedfiles) { - Map<String, Object> status = new HashMap<>(); + public List<Map<String, Object>> upload(@RequestPart("File") List<MultipartFile> uploadedfiles) { + List<Map<String, Object>> result = new ArrayList<>(); for (MultipartFile file : uploadedfiles) { - String resultFile = "Load [" + file.getName() + "]"; - - // is this worker is running? - String jarFileName = file.getOriginalFilename(); - contentManager.saveFromMultiPartFile(file, jarFileName); + try { + Path fileSaved = contentManager.addFromMultipart(file, file.getOriginalFilename()); + result.add(Map.of("filename", fileSaved.getFileName(), "status", "UPLOADED")); + } catch (Exception e) { + result.add(Map.of("filename", file.getOriginalFilename(), "status", "ERROR", "error", e.getMessage())); + } } - return new HashMap<>(); + return result; } + @GetMapping("/api/content/list") + List<Map<String, Object>> getContentScenario() { + try { + return contentManager.getContentScenario().stream() + .map(Scenario::getDescription) + .toList(); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Error during Content : " + e.getMessage()); + } + } + } \ No newline at end of file diff --git a/src/main/java/org/camunda/automator/content/RepositoryManager.java b/src/main/java/org/camunda/automator/content/RepositoryManager.java new file mode 100644 index 0000000..3b424e9 --- /dev/null +++ b/src/main/java/org/camunda/automator/content/RepositoryManager.java @@ -0,0 +1,127 @@ +package org.camunda.automator.content; + +import org.camunda.automator.engine.AutomatorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +public class RepositoryManager { + private final Logger logger = LoggerFactory.getLogger(RepositoryManager.class); + + private Path repositoryPath; + + public void initializeRepository(String repositoryProposition) throws AutomatorException { + if (repositoryProposition != null && !repositoryProposition.isEmpty()) { + Path path = Paths.get(repositoryProposition); + if (Files.exists(path) && Files.isDirectory(path)) { + repositoryPath = path; + } + } else { + // Not exist: create a subfolder + Path tempDir = Paths.get(System.getProperty("java.io.tmpdir")); + + // Create a new folder in the temporary directory + try { + repositoryPath = Files.createDirectory(tempDir.resolve("repository")); + } catch (Exception e) { + logger.error("Can't create folder [{}]", tempDir.toAbsolutePath() + "/repository"); + throw new AutomatorException("Can't create folder[" + tempDir.toAbsolutePath() + "/repository]"); + } + } + logger.info("RepositoryManager: directory under [{}] ", repositoryPath.toAbsolutePath()); + + } + + public Path addResource(Resource resource) throws IOException { + Path targetPath = repositoryPath.resolve(resource.getFilename()); + Files.copy(resource.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING); + logger.info("CopiedReource: [{}] tp [{}]", resource.getFilename(), targetPath); + return targetPath; + } + + public Path addFile(Path sourcePath) throws IOException { + Path sourceFileName = sourcePath.getFileName(); + // Get the directory from targetPath + Path targetDir = repositoryPath.getParent(); + // Combine the directory of targetPath with the filename of sourcePath + Path targetPath = targetDir.resolve(sourceFileName); + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + logger.info("CopiedFile: [{}] tp [{}]", sourcePath, targetPath); + return targetPath; + } + + public Path addFromInputStream(InputStream inputStream, String fileName) throws IOException { + OutputStream outputStream = null; + File fileContent = null; + try { + fileContent = new File(repositoryPath + File.separator + fileName); + // Open an OutputStream to the temporary file + outputStream = new FileOutputStream(fileContent); + // Transfer data from InputStream to OutputStream + byte[] buffer = new byte[1024 * 100]; // 100Ko + int bytesRead; + int count = 0; + + while ((bytesRead = inputStream.read(buffer)) != -1) { + count += bytesRead; + outputStream.write(buffer, 0, bytesRead); + } + outputStream.flush(); + outputStream.close(); + outputStream = null; + return fileContent.toPath(); + } catch (Exception e) { + logger.error("RepositoryManager/addFromInputStream: Can't upload File [" + fileName + "] : " + e.getMessage()); + throw e; + } finally { + if (outputStream != null) + try { + outputStream.close(); + } catch (Exception e) { + // do nothing + } + } + } + + public Path getRepositoryPath() { + return repositoryPath; + } + + public Path getFromName(String scenarioName) { + Path scenarioPath = Paths.get(repositoryPath + File.separator + scenarioName + ".json"); + if (Files.exists(scenarioPath)) { + return scenarioPath; + } + return null; + } + + + /** + * Return the content of the repository path + * + * @return list of files + */ + public List<Path> getContentRepository() { + try (Stream<Path> files = Files.walk(repositoryPath)) { + return files.filter(Files::isRegularFile) // You can filter by file type if needed + .toList(); + } catch (IOException e) { + logger.error("Error reading content [{}]", repositoryPath.toString()); + return Collections.emptyList(); + } + } + + public File prepareFileToUpload(String fileName) { + Path scenarioPath = Paths.get(repositoryPath + File.separator + fileName); + return scenarioPath.toFile(); + } +} diff --git a/src/main/java/org/camunda/automator/definition/Scenario.java b/src/main/java/org/camunda/automator/definition/Scenario.java index ff8bce3..461abee 100644 --- a/src/main/java/org/camunda/automator/definition/Scenario.java +++ b/src/main/java/org/camunda/automator/definition/Scenario.java @@ -13,8 +13,10 @@ import org.slf4j.LoggerFactory; import java.io.*; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * the Scenario Head group a scenario definition @@ -50,30 +52,34 @@ public class Scenario { */ private String scenarioFile = null; - public static Scenario createFromJson(String jsonContent) { + public static Scenario createFromJson(String jsonContent) throws AutomatorException { GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); - - Gson gson = builder.create(); - Scenario scenario = gson.fromJson(jsonContent, Scenario.class); - if (scenario == null) { - logger.error("Scenario: Can't build scenario from content [{}]", jsonContent); - return null; + try { + Gson gson = builder.create(); + Scenario scenario = gson.fromJson(jsonContent, Scenario.class); + if (scenario == null) { + logger.error("Scenario: Can't build scenario from content [{}]", jsonContent); + return null; + } + scenario.afterUnSerialize(); + return scenario; + } catch (Exception e) { + logger.error("Scenario: can't unparse Json content [{}]", jsonContent); + throw new AutomatorException("Scenario: can't unparse GSon file:" + e.getMessage()); } - scenario.afterUnSerialize(); - return scenario; } - public static Scenario createFromFile(File scenarioFile) throws AutomatorException { + public static Scenario createFromFile(Path scenarioFile) throws AutomatorException { try { - Scenario scenario = createFromInputStream(new FileInputStream(scenarioFile), scenarioFile.getAbsolutePath()); - scenario.scenarioFile = scenarioFile.getAbsolutePath(); + Scenario scenario = createFromInputStream(new FileInputStream(scenarioFile.toFile()), scenarioFile.toAbsolutePath().toString()); + scenario.scenarioFile = scenarioFile.toAbsolutePath().toString(); scenario.initialize(); return scenario; } catch (FileNotFoundException e) { - throw new AutomatorException("Can't access file [" + scenarioFile.getAbsolutePath() + "] " + e.getMessage()); + throw new AutomatorException("Can't access file [" + scenarioFile.getFileName() + "] " + e.getMessage()); } catch (AutomatorException e) { throw e; } @@ -191,6 +197,15 @@ private void afterUnSerialize() { } } + + public Map<String, Object> getDescription() { + return Map.of("name", name==null?"":name,// + "server", serverName==null? "": serverName, // + "serverType", serverType==null?"": serverType, // + "processId", processId==null?"":processId, // + "typeScenario", typeScenario==null? "": typeScenario.toString()); + } + public enum TYPESCENARIO {FLOW, UNIT} } diff --git a/src/main/java/org/camunda/automator/definition/ScenarioVerification.java b/src/main/java/org/camunda/automator/definition/ScenarioVerification.java index 9b8a074..d6dbda5 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioVerification.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioVerification.java @@ -7,6 +7,11 @@ public class ScenarioVerification { private final ScenarioExecution scenarioExecution; + /** + * List of duration to check + * Maybe null due the Gson deserializer if there is no definition + */ + private final List<ScenarioVerificationPerformance> performances = new ArrayList<>(); /** * List of activities to check * Maybe null due the Gson deserializer if there is no definition @@ -17,13 +22,6 @@ public class ScenarioVerification { * Maybe null due the Gson deserializer if there is no definition */ private List<ScenarioVerificationVariable> variables = new ArrayList<>(); - - /** - * List of duration to check - * Maybe null due the Gson deserializer if there is no definition - */ - private final List<ScenarioVerificationPerformance> performances = new ArrayList<>(); - /** * Variable to search the process instance, if only the verification is running * Maybe null due the Gson deserializer if there is no definition diff --git a/src/main/java/org/camunda/automator/engine/RunResult.java b/src/main/java/org/camunda/automator/engine/RunResult.java index 1e44c8d..a112d40 100644 --- a/src/main/java/org/camunda/automator/engine/RunResult.java +++ b/src/main/java/org/camunda/automator/engine/RunResult.java @@ -38,18 +38,16 @@ public class RunResult { * Keep a photo of process instance created/failed per processid */ private final Map<String, RecordCreationPI> recordCreationPIMap = new HashMap<>(); + private final List<RunResult> listRunResults = new ArrayList<>(); Logger logger = LoggerFactory.getLogger(RunResult.class); private int numberOfSteps = 0; private int numberOfErrorSteps = 0; - /** * Time to execute it */ private long timeExecution; - private Date startDate; private Date endDate; - private final List<RunResult> listRunResults = new ArrayList<>(); public RunResult(RunScenario runScenario) { this.runScenario = runScenario; diff --git a/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java b/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java index 6dbf3a3..7017575 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunObjectives.java @@ -6,7 +6,6 @@ /* ******************************************************************** */ package org.camunda.automator.engine.flow; -import io.camunda.operate.search.DateFilter; import org.camunda.automator.bpmnengine.BpmnEngine; import org.camunda.automator.definition.ScenarioFlowControl; import org.camunda.automator.engine.AutomatorException; @@ -22,8 +21,8 @@ public class RunObjectives { private final Map<Integer, List<SavePhoto>> flowRateMnObjective = new HashMap<>(); private final List<ScenarioFlowControl.Objective> listObjectives; Logger logger = LoggerFactory.getLogger(RunObjectives.class); - private DateFilter startDateFilter; - private DateFilter endDateFilter; + private Date startDateFilter; + private Date endDateFilter; private long lastHeartBeat; public RunObjectives(List<ScenarioFlowControl.Objective> listObjectives, @@ -39,12 +38,12 @@ public RunObjectives(List<ScenarioFlowControl.Objective> listObjectives, } public void setStartDate(Date startTestDate) { - this.startDateFilter = new DateFilter(startTestDate); + this.startDateFilter = startTestDate; this.lastHeartBeat = System.currentTimeMillis(); } public void setEndDate(Date endTestDate) { - this.endDateFilter = new DateFilter(endTestDate); + this.endDateFilter = endTestDate; } /** diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java index 85e5396..bb799a8 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java @@ -23,7 +23,6 @@ import org.camunda.bpm.client.task.ExternalTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.TaskScheduler; import java.time.Duration; @@ -41,7 +40,7 @@ public class RunScenarioFlowServiceTask extends RunScenarioFlowBasic { private BpmnEngine.RegisteredTask registeredTask; private boolean stopping; - private BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy=null; + private final BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy = null; public RunScenarioFlowServiceTask(TaskScheduler scheduler, ScenarioStep scenarioStep, diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java index 5d23b79..de4c0c5 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioWarmingUp.java @@ -6,7 +6,6 @@ /* ******************************************************************** */ package org.camunda.automator.engine.flow; -import io.camunda.operate.search.DateFilter; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.definition.ScenarioWarmingUp; import org.camunda.automator.engine.AutomatorException; @@ -311,7 +310,7 @@ private CheckFunctionResult endCheckFunction(String function, RunResult runResul long value = runScenario.getBpmnEngine() .countNumberOfProcessInstancesEnded(runScenario.getScenario().getProcessId(), - new DateFilter(runResult.getStartDate()), new DateFilter(new Date())); + runResult.getStartDate(), new Date()); return new CheckFunctionResult(value >= threshold, "End[" + endId + "] value [" + value + "] / threshold[" + threshold + "]"); diff --git a/src/main/java/org/camunda/automator/services/AutomatorStartup.java b/src/main/java/org/camunda/automator/services/AutomatorStartup.java index bf189b7..c5eb633 100644 --- a/src/main/java/org/camunda/automator/services/AutomatorStartup.java +++ b/src/main/java/org/camunda/automator/services/AutomatorStartup.java @@ -5,6 +5,7 @@ import org.camunda.automator.bpmnengine.BpmnEngine; import org.camunda.automator.configuration.BpmnEngineList; import org.camunda.automator.configuration.ConfigurationStartup; +import org.camunda.automator.content.ContentManager; import org.camunda.automator.definition.Scenario; import org.camunda.automator.engine.AutomatorException; import org.camunda.automator.engine.RunParameters; @@ -16,7 +17,10 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -40,6 +44,8 @@ public class AutomatorStartup { @Autowired ServiceAccess serviceAccess; + @Autowired + private ContentManager contentManager; @PostConstruct public void init() { @@ -69,57 +75,6 @@ private void runFixedWarmup() { } } - /** - * Load all scenario. List of File or Resource - * - * @return list of scenario - */ - private List<Object> registerScenario() { - List<Object> scenarioList = new ArrayList<>(); - // File - if (configurationStartup.getScenarioFileAtStartup().isEmpty()) { - logger.info("No scenario [File] from variable {} given", configurationStartup.getScenarioFileAtStartupName()); - } else { - logger.info("Detect {} scenario [File] from variable [{}] ScenarioPath[{}]", - configurationStartup.getScenarioFileAtStartup().size(), configurationStartup.getScenarioFileAtStartupName(), - configurationStartup.scenarioPath); - - for (String scenarioFileName : configurationStartup.getScenarioFileAtStartup()) { - logger.info("Register scenario [File] [{}]", scenarioFileName); - - File scenarioFile = new File(configurationStartup.scenarioPath + "/" + scenarioFileName); - if (!scenarioFile.exists()) { - scenarioFile = new File(scenarioFileName); - } - if (!scenarioFile.exists()) { - logger.error("ScenarioFile: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, - scenarioFileName, scenarioFileName); - continue; - } - scenarioList.add(scenarioFile); - } - } - // Resource - if (configurationStartup.getScenarioResourceAtStartup().isEmpty()) { - logger.info("No scenario [Resource] from variable {} given", - configurationStartup.getScenarioResourceAtStartupName()); - } else { - List<Resource> scenarioResource = configurationStartup.getScenarioResourceAtStartup().stream() - .filter(t -> t != null) - .collect(Collectors.toList()); - - logger.info("Detect {} scenario [Resource] from variable [{}]", - scenarioResource.size(), - configurationStartup.getScenarioResourceAtStartupName()); - - for (Resource resource : scenarioResource) { - logger.info("Load scenario [Resource] from [{}]", resource.getDescription()); - scenarioList.add(resource); - } - } - - return scenarioList; - } /** * AutomatorSetupRunnable - run in parallel @@ -179,27 +134,19 @@ public void run() { runFixedWarmup(); // Load scenario - List<Object> scenarioList = registerScenario(); + List<Path> scenarioList = loadStartupScenario(); // now proceed all scenario - for (Object scenarioObject : scenarioList) { + for (Path scenarioPath : scenarioList) { Scenario scenario = null; - if (scenarioObject instanceof File scenarioFile) - try { - scenario = automatorAPI.loadFromFile(scenarioFile); - } catch (Exception e) { - logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.getAbsolutePath(), - e.getMessage()); - } - else if (scenarioObject instanceof Resource scenarioResource) { - try { - scenario = automatorAPI.loadFromInputStream(scenarioResource.getInputStream(), - scenarioResource.getDescription()); - } catch (Exception e) { - logger.error("Error during accessing InputStream from resource [{}]: {}", scenarioResource.getDescription(), - e.getMessage()); - } + try { + scenario = automatorAPI.loadFromFile(scenarioPath); + } catch (Exception e) { + logger.error("Error during accessing InputStream from File [{}]: {}", scenarioPath.getFileName(), + e.getMessage()); } + + if (scenario == null) continue; logger.info("Start scenario [{}] on (1)ScenarioServerName[{}] (2)ConfigurationServerName[{}]", @@ -272,5 +219,62 @@ else if (scenarioObject instanceof Resource scenarioResource) { } } } + + private List<Path> loadStartupScenario() { + List<Path> scenarioList = new ArrayList<>(); + // File + if (configurationStartup.getScenarioFileAtStartup().isEmpty()) { + logger.info("AutomatorStartup/StartupScenario: no scenario [File] from {} given", configurationStartup.getScenarioFileAtStartupName()); + } else { + logger.info("Detect {} scenario [File] from variable [{}] ScenarioPath[{}]", + configurationStartup.getScenarioFileAtStartup().size(), configurationStartup.getScenarioFileAtStartupName(), + configurationStartup.scenarioPath); + + for (String scenarioFileName : configurationStartup.getScenarioFileAtStartup()) { + logger.info("AutomatorStartup/StartupScenario: Register scenario [File] [{}]", scenarioFileName); + + Path scenarioFile = Paths.get(configurationStartup.scenarioPath + "/" + scenarioFileName); + if (!Files.exists(scenarioFile)) { + scenarioFile = Paths.get(scenarioFileName); + } + if (Files.exists(scenarioFile)) { + try { + contentManager.addFile(scenarioFile); + }catch (IOException e) { + logger.error("AutomatorStartup/StartupScenario: File [{}] Can't add in the repository: {}", scenarioFile.toAbsolutePath().toString(), e.getMessage()); + } + } else { + logger.error("AutomatorStartup/StartupScenario:: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, + scenarioFileName, scenarioFileName); + continue; + } + } + + } + + // Resource + if (configurationStartup.getScenarioResourceAtStartup().isEmpty()) { + logger.info("No scenario [Resource] from variable {} given", + configurationStartup.getScenarioResourceAtStartupName()); + } else { + List<Resource> scenarioResource = configurationStartup.getScenarioResourceAtStartup().stream() + .filter(t -> t != null) + .collect(Collectors.toList()); + + logger.info("Detect {} scenario [Resource] from variable [{}]", + scenarioResource.size(), + configurationStartup.getScenarioResourceAtStartupName()); + for (Resource resource : scenarioResource) { + try { + scenarioList.add(contentManager.addResource(resource)); + } catch (IOException e) { + logger.error("Error loading resource [{}]", resource.getFilename()); + } + } + } + + return scenarioList; + } + } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 61fe1d6..729dc17 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -4,7 +4,9 @@ automator: content: repositoryPath: "c:/temp/processautomator" - uploadPath: "C:/dev/intellij/community/process-execution-automator/doc/unittestscenario/resources" + uploadPath: "C:/temp/upload" + scenario: + startup: # give the server to run all tests at startup. The name must be registered in the list of server after serverName: Camunda8Ruby @@ -16,6 +18,8 @@ automator: # one scenario resource - to be accessible in a Docker container via a configMap scenarioResourceAtStartup: + + # DEBUG, INFO, MONITORING, MAIN, NOTHING logLevel: MONITORING # string composed with DEPLOYPROCESS, WARMINGUP, CREATION, SERVICETASK, USERTASK @@ -53,6 +57,7 @@ automator: description: "Simple authentication" name: "Camunda8Ruby" zeebeGatewayAddress: "127.0.0.1:26500" + zeebeRestAddress: "http://localhost:9600" operateUserName: "demo" operateUserPassword: "demo" operateUrl: "http://localhost:8081" @@ -67,6 +72,7 @@ automator: name: "Camunda8Lazuli" description: "A Zeebe+Identity server" zeebeGatewayAddress: "127.0.0.1:26500" + zeebeRestAddress: "http://localhost:9600" zeebeClientId: "zeebe" zeebeClientSecret: "LHwdAq56bZ" zeebeAudience: "zeebe" @@ -93,6 +99,7 @@ automator: - type: "camunda8" name: "Camunda8ZeebeOnly" zeebeGatewayAddress: "127.0.0.1:26500" + zeebeRestAddress: "http://localhost:9600" zeebePlainText: true workerExecutionThreads: 200 # -1 means : align the jobsActive to the workerExecutionThreads diff --git a/src/test/java/automatorapi/TestSimpleUserTask.java b/src/test/java/automatorapi/TestSimpleUserTask.java index 1ae5a72..5787ff6 100644 --- a/src/test/java/automatorapi/TestSimpleUserTask.java +++ b/src/test/java/automatorapi/TestSimpleUserTask.java @@ -14,6 +14,8 @@ import org.springframework.context.annotation.Bean; import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; public class TestSimpleUserTask { @@ -58,7 +60,7 @@ public void SimpleUserTaskScenario() { assert(true); return; } - File userTaskFile = new File("./test/resources/simpleusertask/AutomatorSimpleUserTask.json"); + Path userTaskFile = Paths.get("./test/resources/simpleusertask/AutomatorSimpleUserTask.json"); Scenario scenario = automatorApi.loadFromFile(userTaskFile); assert(scenario!=null); } catch (Exception e) { From 83869cc1ddc43616c013117a52734b23cd7b4072 Mon Sep 17 00:00:00 2001 From: Pierre-yves-monnet <pierre-yves.lonnet@laposte.net> Date: Fri, 6 Dec 2024 11:01:52 -0800 Subject: [PATCH 3/5] Migrate to last version of API --- README.md | 11 +- doc/scenarioreference/C8CrawlUrl.bpmn | 82 +- doc/unittestscenario/README.md | 33 +- doc/unittestscenario/SendUnitTestCommand.rest | 5 +- .../resources/ScoreAcceptance.bpmn | 127 +++ .../resources/ScoreAcceptance.png | Bin 0 -> 107789 bytes .../resources/ScoreAcceptanceScn.json | 8 + .../resources/UnittestAutomator.yaml | 68 ++ pom.xml | 70 +- .../org/camunda/automator/AutomatorCLI.java | 1 - .../org/camunda/automator/AutomatorRest.java | 11 +- .../automator/bpmnengine/BpmnEngine.java | 2 +- .../camunda7/BpmnEngineCamunda7.java | 2 +- .../camunda8/BpmnEngineCamunda8.java | 763 +++--------------- .../bpmnengine/camunda8/OperateClient.java | 429 ++++++++++ .../bpmnengine/camunda8/TaskListClient.java | 245 ++++++ .../refactoring/RefactoredCommandWrapper.java | 89 -- .../bpmnengine/dummy/BpmnEngineDummy.java | 2 +- .../configuration/BpmnEngineList.java | 3 +- .../automator/content/ContentManager.java | 9 +- .../content/ContentRestController.java | 11 +- .../automator/content/RepositoryManager.java | 25 +- .../automator/definition/Scenario.java | 10 +- .../definition/ScenarioExecution.java | 1 + .../camunda/automator/engine/RunResult.java | 18 + .../flow/RunScenarioFlowServiceTask.java | 43 +- .../engine/unit/RunScenarioUnit.java | 5 +- .../unit/RunScenarioUnitServiceTask.java | 2 +- .../automator/services/AutomatorStartup.java | 111 ++- src/main/resources/application.yaml | 25 +- src/main/resources/banner.txt | 2 +- 31 files changed, 1281 insertions(+), 932 deletions(-) create mode 100644 doc/unittestscenario/resources/ScoreAcceptance.bpmn create mode 100644 doc/unittestscenario/resources/ScoreAcceptance.png create mode 100644 doc/unittestscenario/resources/UnittestAutomator.yaml create mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java create mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java delete mode 100644 src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java diff --git a/README.md b/README.md index 700a9d4..9a4a9e9 100644 --- a/README.md +++ b/README.md @@ -457,8 +457,7 @@ The docker image is build using the Dockerfile present on the root level. Push the image to ```` -docker build -t pierre-yves-monnet/process-execution-automator:1.8.0 . -docker build pycamunda/camunda-community-hub/process-execution-automator:1.8.0 +docker build -t pierre-yves-monnet/process-execution-automator:1.8.1 . ```` @@ -468,8 +467,12 @@ Push the image to the Camunda hub (you must be login first to the docker registr docker tag pierre-yves-monnet/process-execution-automator:1.8.0 ghcr.io/camunda-community-hub/process-execution-automator:1.8.0 docker push ghcr.io/camunda-community-hub/process-execution-automator:1.8.0 ```` -docker tag pierre-yves-monnet/process-execution-automator:1.8.0 pycamunda/camunda-hub:process-execution-automator-1.8.0 -docker push pycamunda/camunda-hub:process-execution-automator-1.8.0 + + +Temporary: +docker build -t pierre-yves-monnet/process-execution-automator:1.8.3 . +docker tag pierre-yves-monnet/process-execution-automator:1.8.3 pycamunda/camunda-hub:process-execution-automator-1.8.3 +docker push pycamunda/camunda-hub:process-execution-automator-1.8.3 diff --git a/doc/scenarioreference/C8CrawlUrl.bpmn b/doc/scenarioreference/C8CrawlUrl.bpmn index 5d946a7..e19d4e0 100644 --- a/doc/scenarioreference/C8CrawlUrl.bpmn +++ b/doc/scenarioreference/C8CrawlUrl.bpmn @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1de8grd" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.29.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.2.0"> +<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:color="http://www.omg.org/spec/BPMN/non-normative/color/1.0" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1de8grd" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.29.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.2.0"> <bpmn:collaboration id="CrawlUrlProcessAutomator-4553"> <bpmn:extensionElements> <zeebe:properties> @@ -429,7 +429,7 @@ <di:waypoint x="208" y="263" /> <di:waypoint x="258" y="200" /> </bpmndi:BPMNEdge> - <bpmndi:BPMNShape id="Participant_1tdai6p_di" bpmnElement="Participant_1vqtszi" isHorizontal="true"> + <bpmndi:BPMNShape id="Participant_1tdai6p_di" bpmnElement="Participant_1vqtszi" isHorizontal="true" bioc:stroke="#0d4372" bioc:fill="#bbdefb" color:background-color="#bbdefb" color:border-color="#0d4372"> <dc:Bounds x="129" y="790" width="1720" height="250" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_0c0eu5q_di" bpmnElement="Activity_0c0eu5q"> @@ -470,6 +470,45 @@ <dc:Bounds x="868" y="1002" width="44" height="14" /> </bpmndi:BPMNLabel> </bpmndi:BPMNShape> + <bpmndi:BPMNEdge id="DataOutputAssociation_16252ue_di" bpmnElement="DataOutputAssociation_16252ue"> + <di:waypoint x="240" y="900" /> + <di:waypoint x="260" y="975" /> + <di:waypoint x="872" y="975" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_1x6wk7g_di" bpmnElement="DataOutputAssociation_1x6wk7g"> + <di:waypoint x="355" y="900" /> + <di:waypoint x="370" y="940" /> + <di:waypoint x="872" y="974" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_0domor3_di" bpmnElement="DataOutputAssociation_0domor3"> + <di:waypoint x="645" y="900" /> + <di:waypoint x="660" y="940" /> + <di:waypoint x="872" y="967" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_17clz1o_di" bpmnElement="DataOutputAssociation_17clz1o"> + <di:waypoint x="791" y="900" /> + <di:waypoint x="800" y="930" /> + <di:waypoint x="872" y="957" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_1jb2g71_di" bpmnElement="DataOutputAssociation_1jb2g71"> + <di:waypoint x="903" y="900" /> + <di:waypoint x="895" y="945" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_02rmp5j_di" bpmnElement="DataOutputAssociation_02rmp5j"> + <di:waypoint x="1039" y="900" /> + <di:waypoint x="1030" y="930" /> + <di:waypoint x="908" y="961" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_1i1hr24_di" bpmnElement="DataOutputAssociation_1i1hr24"> + <di:waypoint x="1285" y="900" /> + <di:waypoint x="1270" y="940" /> + <di:waypoint x="908" y="969" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="DataOutputAssociation_1r8w49n_di" bpmnElement="DataOutputAssociation_1r8w49n"> + <di:waypoint x="1464" y="900" /> + <di:waypoint x="1410" y="960" /> + <di:waypoint x="908" y="974" /> + </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_0dr7tpp_di" bpmnElement="Flow_0dr7tpp"> <di:waypoint x="197" y="820" /> <di:waypoint x="197" y="295" /> @@ -526,45 +565,6 @@ <dc:Bounds x="891" y="747" width="77" height="14" /> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_16252ue_di" bpmnElement="DataOutputAssociation_16252ue"> - <di:waypoint x="240" y="900" /> - <di:waypoint x="260" y="975" /> - <di:waypoint x="872" y="975" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_1x6wk7g_di" bpmnElement="DataOutputAssociation_1x6wk7g"> - <di:waypoint x="355" y="900" /> - <di:waypoint x="370" y="940" /> - <di:waypoint x="872" y="974" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_0domor3_di" bpmnElement="DataOutputAssociation_0domor3"> - <di:waypoint x="645" y="900" /> - <di:waypoint x="660" y="940" /> - <di:waypoint x="872" y="967" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_17clz1o_di" bpmnElement="DataOutputAssociation_17clz1o"> - <di:waypoint x="791" y="900" /> - <di:waypoint x="800" y="930" /> - <di:waypoint x="872" y="957" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_1jb2g71_di" bpmnElement="DataOutputAssociation_1jb2g71"> - <di:waypoint x="903" y="900" /> - <di:waypoint x="895" y="945" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_02rmp5j_di" bpmnElement="DataOutputAssociation_02rmp5j"> - <di:waypoint x="1039" y="900" /> - <di:waypoint x="1030" y="930" /> - <di:waypoint x="908" y="961" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_1i1hr24_di" bpmnElement="DataOutputAssociation_1i1hr24"> - <di:waypoint x="1285" y="900" /> - <di:waypoint x="1270" y="940" /> - <di:waypoint x="908" y="969" /> - </bpmndi:BPMNEdge> - <bpmndi:BPMNEdge id="DataOutputAssociation_1r8w49n_di" bpmnElement="DataOutputAssociation_1r8w49n"> - <di:waypoint x="1464" y="900" /> - <di:waypoint x="1410" y="960" /> - <di:waypoint x="908" y="974" /> - </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </bpmn:definitions> diff --git a/doc/unittestscenario/README.md b/doc/unittestscenario/README.md index 44a02c5..9691171 100644 --- a/doc/unittestscenario/README.md +++ b/doc/unittestscenario/README.md @@ -58,28 +58,35 @@ Check the scenario: ## execute -1. First, upload the scenario file in a config map + +1. Deploy the scenario on the cluster, via the Modeler + +2. Create the pod process-execution-automator ``` -kubectl create configmap scoreacceptancescn --from-file=doc/unittestscenario/resources/scoreacceptancescn.json -n camunda +kubectl create -f doc/unittestscenario/resources/UnittestAutomator.yaml -n camunda ``` -2. Deploy the scenario on the cluster, via the Modeler - -3. Create the pod process-execution-automator +3. Port forward ``` -kubectl create -f doc/unittestscenario/resources/UnittestAutomator.yaml -n camunda +kubectl port-forward svc/process-execution-automator 8381:8381 -n camunda ``` -This configuration will upload the scenario +4. Upload the scenario -4. Port forward ``` -kubectl port-forward svc/process-execution-automator 8381:8381 -n camunda +curl -X POST http://localhost:8381/api/files/upload \ + -H "Content-Type: multipart/form-data" \ + -F "file=@doc/unittestscenario/resources/ScoreAcceptanceScn.json" + +curl -X GET "http://localhost:8381/api/content/list" -H "Content-Type: application/json" + ``` + + 6. Check the scenario is uploaded ``` @@ -94,3 +101,11 @@ curl -X POST -F "file=@/path/to/your/file.txt" http://localhost:8080/api/files/u curl -X GET "http://localhost:8381/api/unittest/get?id=1732767184446" -H "Content-Type: application/json" ``` +Option: give the scenario via the configMap + +a. create the configMap +```` +kubectl create configmap scoreacceptancescn --from-file=doc/unittestscenario/resources/scoreacceptancescn.json -n camunda +```` + +b. Chheck the configuration diff --git a/doc/unittestscenario/SendUnitTestCommand.rest b/doc/unittestscenario/SendUnitTestCommand.rest index 92c0bcb..0dca8f6 100644 --- a/doc/unittestscenario/SendUnitTestCommand.rest +++ b/doc/unittestscenario/SendUnitTestCommand.rest @@ -29,11 +29,10 @@ Content-Type: application/json ### Upload file POST http://localhost:8381/api/content/add -Content-Type: multipart/form-data +Content-Type: multipart/form-data; boundary=boundary --boundary -Content-Disposition: form-data; name="File"; filename="file1.txt" -Content-Type: text/plain +Content-Disposition: form-data; name="FileToUpload"; filename="ScoreAcceptanceScn.json" < ./resources/ScoreAcceptanceScn.json diff --git a/doc/unittestscenario/resources/ScoreAcceptance.bpmn b/doc/unittestscenario/resources/ScoreAcceptance.bpmn new file mode 100644 index 0000000..f7f46ec --- /dev/null +++ b/doc/unittestscenario/resources/ScoreAcceptance.bpmn @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8"?> +<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1v4ppib" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.29.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.6.0"> + <bpmn:process id="ScoreAcceptance" isExecutable="true"> + <bpmn:startEvent id="StartScoreApplication" name="score application"> + <bpmn:outgoing>Flow_1wuzgpt</bpmn:outgoing> + </bpmn:startEvent> + <bpmn:sequenceFlow id="Flow_1wuzgpt" sourceRef="StartScoreApplication" targetRef="ActGetScore" /> + <bpmn:scriptTask id="ActGetScore" name="getScore"> + <bpmn:extensionElements> + <zeebe:script expression="=score&#62;100" resultVariable="accepted" /> + </bpmn:extensionElements> + <bpmn:incoming>Flow_1wuzgpt</bpmn:incoming> + <bpmn:outgoing>Flow_0gi4xk6</bpmn:outgoing> + </bpmn:scriptTask> + <bpmn:exclusiveGateway id="Gateway_01ox9rs" name="Accepted?" default="Flow_0a4sjzy"> + <bpmn:incoming>Flow_0gi4xk6</bpmn:incoming> + <bpmn:outgoing>Flow_16hd1bd</bpmn:outgoing> + <bpmn:outgoing>Flow_0a4sjzy</bpmn:outgoing> + </bpmn:exclusiveGateway> + <bpmn:sequenceFlow id="Flow_0gi4xk6" sourceRef="ActGetScore" targetRef="Gateway_01ox9rs" /> + <bpmn:task id="ActSendAcceptation" name="Send acception"> + <bpmn:incoming>Flow_16hd1bd</bpmn:incoming> + <bpmn:outgoing>Flow_09z898p</bpmn:outgoing> + </bpmn:task> + <bpmn:sequenceFlow id="Flow_16hd1bd" name="yes" sourceRef="Gateway_01ox9rs" targetRef="ActSendAcceptation"> + <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=accepted</bpmn:conditionExpression> + </bpmn:sequenceFlow> + <bpmn:endEvent id="EndAccepted" name="accepted"> + <bpmn:incoming>Flow_09z898p</bpmn:incoming> + </bpmn:endEvent> + <bpmn:sequenceFlow id="Flow_09z898p" sourceRef="ActSendAcceptation" targetRef="EndAccepted" /> + <bpmn:task id="ActSendRejection" name="Send rejection"> + <bpmn:incoming>Flow_0wya675</bpmn:incoming> + <bpmn:outgoing>Flow_1pxztl2</bpmn:outgoing> + </bpmn:task> + <bpmn:sequenceFlow id="Flow_0a4sjzy" name="Rejected" sourceRef="Gateway_01ox9rs" targetRef="CallApplicant" /> + <bpmn:endEvent id="EndRejected" name="rejected"> + <bpmn:incoming>Flow_1pxztl2</bpmn:incoming> + </bpmn:endEvent> + <bpmn:sequenceFlow id="Flow_1pxztl2" sourceRef="ActSendRejection" targetRef="EndRejected" /> + <bpmn:sequenceFlow id="Flow_0wya675" sourceRef="CallApplicant" targetRef="ActSendRejection" /> + <bpmn:userTask id="CallApplicant" name="Call Applicant"> + <bpmn:extensionElements /> + <bpmn:incoming>Flow_0a4sjzy</bpmn:incoming> + <bpmn:outgoing>Flow_0wya675</bpmn:outgoing> + </bpmn:userTask> + </bpmn:process> + <bpmndi:BPMNDiagram id="BPMNDiagram_1"> + <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ScoreAcceptance"> + <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartScoreApplication"> + <dc:Bounds x="192" y="162" width="36" height="36" /> + <bpmndi:BPMNLabel> + <dc:Bounds x="169" y="205" width="83" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Activity_1fm9qgv_di" bpmnElement="ActGetScore"> + <dc:Bounds x="280" y="140" width="100" height="80" /> + <bpmndi:BPMNLabel /> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Gateway_01ox9rs_di" bpmnElement="Gateway_01ox9rs" isMarkerVisible="true"> + <dc:Bounds x="435" y="155" width="50" height="50" /> + <bpmndi:BPMNLabel> + <dc:Bounds x="434" y="125" width="52" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Activity_1eppdvp_di" bpmnElement="ActSendAcceptation"> + <dc:Bounds x="540" y="140" width="100" height="80" /> + <bpmndi:BPMNLabel /> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Event_1961r03_di" bpmnElement="EndAccepted"> + <dc:Bounds x="702" y="162" width="36" height="36" /> + <bpmndi:BPMNLabel> + <dc:Bounds x="699" y="205" width="45" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Event_1lfkf11_di" bpmnElement="EndRejected"> + <dc:Bounds x="872" y="272" width="36" height="36" /> + <bpmndi:BPMNLabel> + <dc:Bounds x="870" y="315" width="40" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Activity_1dmaofg_di" bpmnElement="ActSendRejection"> + <dc:Bounds x="710" y="250" width="100" height="80" /> + <bpmndi:BPMNLabel /> + </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Activity_1kkk9hs_di" bpmnElement="CallApplicant"> + <dc:Bounds x="530" y="250" width="100" height="80" /> + <bpmndi:BPMNLabel /> + </bpmndi:BPMNShape> + <bpmndi:BPMNEdge id="Flow_1wuzgpt_di" bpmnElement="Flow_1wuzgpt"> + <di:waypoint x="228" y="180" /> + <di:waypoint x="280" y="180" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="Flow_0gi4xk6_di" bpmnElement="Flow_0gi4xk6"> + <di:waypoint x="380" y="180" /> + <di:waypoint x="435" y="180" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="Flow_16hd1bd_di" bpmnElement="Flow_16hd1bd"> + <di:waypoint x="485" y="180" /> + <di:waypoint x="540" y="180" /> + <bpmndi:BPMNLabel> + <dc:Bounds x="504" y="162" width="18" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="Flow_09z898p_di" bpmnElement="Flow_09z898p"> + <di:waypoint x="640" y="180" /> + <di:waypoint x="702" y="180" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="Flow_0a4sjzy_di" bpmnElement="Flow_0a4sjzy"> + <di:waypoint x="460" y="205" /> + <di:waypoint x="460" y="290" /> + <di:waypoint x="530" y="290" /> + <bpmndi:BPMNLabel> + <dc:Bounds x="468" y="273" width="44" height="14" /> + </bpmndi:BPMNLabel> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="Flow_1pxztl2_di" bpmnElement="Flow_1pxztl2"> + <di:waypoint x="810" y="290" /> + <di:waypoint x="872" y="290" /> + </bpmndi:BPMNEdge> + <bpmndi:BPMNEdge id="Flow_0wya675_di" bpmnElement="Flow_0wya675"> + <di:waypoint x="630" y="290" /> + <di:waypoint x="710" y="290" /> + </bpmndi:BPMNEdge> + </bpmndi:BPMNPlane> + </bpmndi:BPMNDiagram> +</bpmn:definitions> diff --git a/doc/unittestscenario/resources/ScoreAcceptance.png b/doc/unittestscenario/resources/ScoreAcceptance.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf28e2e8c494046f4ac0f79a85d3fcb6b4065b4 GIT binary patch literal 107789 zcmeFZby(F~^EWK}fD#4>h#-xCfOJVCA<_zLx}>GMQ!!}i2I<&rknU1KkPr}%M!G|~ z-}P0Ga-QFL?&rSV_qyJ{o<HR5vp4MTTC-+8GxM35<@ZQV;tD1a=7kFvu1G!<Q@C&e zz4O8a*bDTF;GO>EKveJ_6gve8kqh}<#ETa$kYA7#yRYP|y)t$w0#AIfW%JTs?^EAR zBC=8MOFp}eiy0G|aNo9z5d7n*nW3J)jV^T&=W!8?5|c9f&V6wuVd~dD>q<wfb;X}v z#tvbOv7ZUHrWN(W_fB&cQ{wsHbe5wPHloDn<lYxx|IH8IP+?4eqxU9dlK3d7*uoe7 z&p#TjzcqP@h4O#?#UI~AzJhuu4x^fm_Q`+u?H5q|jVdnwR|^5(|6a~JHTGS+?ez!$ z#loRq0ZTXgFMn0||1SN1BdPx@^?%pT|Nj-H;Q*E;m#7?WgtY%Y!>!Dg$mj4Vet<E8 zQXF%hmSE=c>A^hN>eoVPl>#lrMTG)Xo;a^pWSp)EncXoKCwpPdTS_xIw9`S$<rAKL z1IUpQqg7WA+kHi?s%?a2^(xnFTet>u{r0n5Wn_9sEZYzz?X!PJ2%kx;M;y4vJmj$R zR=)8_w`VG2Nl5o$hgvtkqF6X>kcvT-J?XBPor+Jfaed!p3e|QO-@0kjMx$$~D_r9O zHJrn4y5)+6+q%cjTA$8*_gRE)N2YhC=+S`baC)dhhLmBzC_3!lvF91BaF5K!3L)Y7 zk87|i+z*O)ofe-U?_4J&%$e{y8(T7*+op3}tDMr_{z%sYV^Gdl2ubolo;fJ=bgYfJ z_2-Myni&$>;nAxK<I_jLyApWpH{XjRJ<X7gE2A0pr-z>oU5*@)NH5FU9c3D_^N;(Z z81Gd9G_fSPD@gpYq6R<MrE8=LX*7%m{>D{K%Xy_{+zSiyt$Far7q6@!BIp!(Ss68& zR+>@?i{UB(%B%KMP0<6&G9Ipbk2;C^^|US-QO8{q8ZO8mb8XAZ)E^Ea<CZe~6jAho z@BY67zOXd}zrlQIl;`|`Hvuej|H(=PM%)cf#aK20T_jWSN>HTwJKaLIakqlGj--5k z7h9Je_N<#Iv^P2{Ec@Zr%2#%lN7x(R=j@f$7bqk7R2jM==+#8@A6!!r6GIIT`FC&g zgdQA8j2!<{sy~mP6!WnYI?awk4lnk}hcw{{tW0=4>eMJJ#iZbMT6)Wc)VL$=k0&R? zyuiwbG?`oKH(TnjP9Dh9D&Tn9Ycifd=(<uUg2Ag@V=uWn?9`hia1_C5cB3;EKVxO6 z7;P-^!k;rZ|BCz`46hTlHSsadITdce@(GGBQx(59ck|xckfnYk<Df{~@xj(p*DnTU z{e(^-?8e=u{ki2kYh|mvjw@J*eH!Vx4%gYp=sd<`+qsTV6P&J{<-t<7J?+}UDOr-^ z{Wo37XOd2%Hm{OgzuUlbRkbsuqLsv!zWBY=w^gmQjV!AvW>mi1>@TE08^fm}H)i{Z zadQ_(Ye7$WoRwkKpnLc2_?d2&QFkn@;v<9atcdj(<m+6O0-}P>?4D^3kCqiwea#~0 zB!B*$8CfBDxzV1)*#75rC%X|hnAI|!#)mTdjJp<++)mS?bvz#<)j1TS?#9j%C7azf z3dny#^<a79Tld|q#Xg$FwQBa;F|W)*WlL0oUy<4FDWs76d%)lub3vS)f-5Ec`JXcu z&c=+Hits$Sq@jUcY1wSx&y*<_pJFpjtDe&x#{*}s+i(xNJeaT5H$(S0j>jw?u6ljK zX2haA$ECQ%bg(QE7_x&Ru3}yfQo<_n-lpNR>Dbix$5bzKg?%KfOt<zXyfm7*p1%tG zOy+eQs!MidX|QKGk#7lLMNLkSSx-m0-*k!j5GJB~KiGb;*YvY@9lc7vOr8k2e1MTV zPPt!q$70O^7N_HAlk<E7UU%Rvb_G7C)nK|M$!u@hHjHQke{%o{DM*d-HoQ+Aah#p> zZ>G+v#UG2k;Io1E)`W(CJ~8i{-dHCz6zaG3r$3bjI8hAic<bX4$z~|U5c2%ewVcP{ zH<TZXjV3ooXRFy5Y`poVe%d0n+-ejVW;-jj92u>nkozU4)M;IiQKL>In(4&!q{gFq zU_hvR+|^9R=}7SSA&Jhi9`de!@Mxu7j%z7>dm>+!=jj0fqgs(B*{YE4NryQKhebQ8 zS%2KZ2fQSiIR4JVR^m+IEX|=_daWgVhL@tR1PlFj>dsCtEziSqm3rTzc;MmS;4ow? z0AP9V@7I{;s{cI)z6BRJGJmX9>E2AwF<fZp)NzmM<&~nt0Ks4~p_DD5lXMb3M>TP= zIhJOTZb6NX!*z-8Gs-V7pXE<ZAu6VK_4T+Iwj%Bx6rI+bW{tZz#Saurq+cSf&<PC< zJ=~ub9oTQ9i#yP5Wird4bSl7YJ)xn{J?|d=2z=wAcPe2>g-PHa4Z8qqyslc}oO5{X zrrs5yC&wm{%sN~&c!T(2nkp%hcLuK9;zlUWTMjJIpkd7ne#8;g!!L>1>R4h$jyQg4 z3!nIy_nFO2IRcY_-o(LWvzNMdSDgTX-HcaVGhX?eh%=8&u}Ocpi4ikL3-R)BXO*z8 zh4Z4K61&m1H9oy6x$?*Qv(xHQ%+7Xh6^;7wQfb;!^B#@?de$Cs_8!`}IvROmD~mQf zkK?UwA@yp@=NGRMnc~A@rxFPthevXnMX63#?sDqw>mn}Dt$jmO2nlZc#c`P#Hitht zeI>|~$E4+^ZinsRtidrotTK9Vd4RLC;@^XHY)udkKU?lzIREkFOK&;_Rt8^yck(V| zZTc3C5v=+xs>W3}`t8@g+_z9Ke+}2XKK8Y=fhK1PV-OZPfQ-}7xSjDZRAk-4OQ$og zys#=z<MK37gqe+Orpz^yu9d}sGU5D#bjk1sz)26<oz>?`YhE1`l(A_`P<veHuce8l zB_o)rn`zRMWu?G5qAHurs*%<jSnkPXElk#r(BM<9PWLx*$1kV5L&Rg62k^@ZpF~_X zK4vmXy`>7ie3f;C+eMv2|ND5UTL|1;Um1;U`U(k~aDT3H@ZO>Iv~4=Z{|0?tL+Ha6 zn(p&QH-ztCRan-4DUlR)Sl@NcLx#z${fdI};F90xyR#M_hZ`y<`nkp6OAv9G&kfuV znD0td9NA5bq<o>2;?C2bzmwZP=ItX|VtybM`z+eU&S;^q6_fg-kkzS(86uOIhg3P5 zsbDc>WyrA1k&QJb(sFc2&UCEm%F@>y1m}ZMd|gN&IDu-NEe^Nn1{*Ykq#~Rpr&|Kw z*}fJGqmSE!kGAyVk&v7m$M_R4<VxbU(pd~wPjT8a+Ol2Yr;B4nV_zP$QmRLJj>NZ+ zY3!K1f0AcWVp%yG_2`sQ)bjjcpL3iqfha7=dr;2@9M5PctLP>5#OVhMbX4k3pBrKd zb>KK*Wi4D*t}ks-cW5FOeRWw08P_i$)s<J)n`)>*a&My$p1|&A6tPyv_gumwMuez% zHLRMISm3adi1TSmve&8j6{0WjWbHA1OntD=4u4KVsc0FNC<c{+T#4>FWeUHcxVAiX zedPkbUz<JJBU2hX3__-+%yklJh(9tyOZE-U=`%6`LB>Fc0A1Wrty{sc!-4^RM-1Hg z;*|x2?QHwI_kj{B3_<5`^$#MTYXQ9%ak6Z?{AaU1Z?LVkB4=Wa{Z%yx#WSS|tSs|K zXyAE|<UYK-s<8AWN3WEG+cPXvCaB1>GaMGUAlD(qWh8#9*i>3X|Mo>IWg%7#+|C5f zLDPY16{p#*ME0J_!?o&lq(!7mkIX2(UMe`#Z^3FiG*;sh*H_KHEZ7Wi$r|-XHe<~% z`O%UFJWW>Z31psjWYqMpwv}^lv>GjG9!~lc&U18nMD`^W&KLWPN|9x2VpD(Hd@~iV z!?#Jx=l1y+<hNhh2m(i0XOm8F9uGIdy_W~Xk}f|fGm2w(tNK3EhQD00kczVvFpk&h ze$YQ?+FO#*m2}dDNR36P6y&I^d#V>Z*3vFEl`ty3^ET>C$lYG-)2P3pwGc+v_m^@V z52@#!9#7m)LWQY>IxUG`{JCs!Sw=+qb4ETM8amCJ{|wGwJII~Y)hg9{l3*IuR88H( z_#^Q@uY5k^12<?tAJuVgKh`zEwpg6Mg0?35sJA9_6Ol$8IOP|pmHdrPkGJHn+`K3D zDXJi%x>+pvtW)IQO`LqP5J-bG+y-|3b5yCxuZ7bCxlHq($B%jJ<{tS)>y(t?ORZE` ze@pGy6^droQ9eCB;1Dn^>d0m9k$4wibb5MZb%l^i0qe%ysA>&*>ZpQphE536u;#kM z)GBEXALf~~MBBJk3G{(5A71}NkwL~uInIW$%)gpV{%yWi%l+Rc^hXuJt0R!z=%4XW zikv&H_sY6UC4@wLmd_&38c;lx3M^M|Gs+ao%rgm;I4pA^R(&_q;C1dpF<Y8gqAh`> z`Sg(tG+(pm@CdowOLkYrvLI(#ZkZVQeRrfk&*c`$;mkWyubOv8GRdhQ?H8}FOfU3w z@2F?x`^%8!440Y$mojHqrtZ4=Vm`csA?0HJ!QBKQ4e<cML|q+~d~G<$U`jnt9+=(A zQY+4+15xVcaNtcr)dJ-LA_TKebFJ6e{eI+xs@KU*bR4%?-rAV9y#4a+Vm+C4!4sq^ zKVux7N`9aW6Hbnzn?;A;zatQK9EhQP=W4?6&z)2-L51^T*l_83x`5lR8bfnOtbmrw z_ZeG;0`Y+8+%s<q<q-vH>5q7=Vq#*dXIB1N0FsT6Vu}bOhbs#c5+L<-t2sIS{)ySN zC%0DcbpOrYi0QDT#Hw-DRIV;P8mt+hP9slLz?LDs^!%D*uFIz~<(`LqG*^(Rp&C}T zoQ<PbN%_;e3Y=74US5Q!h^X}!sL&cUlMJ3tj|8`u_9<q`AtZ~Hd0oEW`h^!o-~9xy zYdmc!0Z<K`LosGZu&1XCl%=*%r@T*F9xOoUoHeo)#(qdlB`jyUPDJqsh|j$e{t6|& zyU~F4`;&luRct5~rmBO5G^L(?M-(6}5`opOWiq~*x-s36z$$+5eKx4P`tL-lnK2)Y zZh64sQLCI5(hh_s1=<4DEgIY3N37~|%=X4z=M$YG1>Crx3@1;mSan3U)VyCV+0#{Q zu{WcCMeQ-YX0~n(YYg%wh?VlVRcZ}S_}BdT@?S6+VIe>~f@GZ{HR?~049`+Ki=kS> z<ewY->7i(%LepAIcM}ej?|yi7rAWC{GL*7e*2c?y0hiNaK<Y<m8kkDxcIrt@e@^lL z06@YW;hsQT<Di&x5RPu+Qz4GOX@MVc^ZsbR<Wzn`(UWN-ho#X@`j@O4cY{dSDF?X3 zGuQQ7gArP<<OXu@{zyRW^~*TeUeVRKtd=r?P_(>sN_s+2AP*U>sS?1-DwD*frI`6Z zZN9{!Pl|-E#P4ab>(0nh4<Z@=Ih`XRr(QNDo->ECdS-~r7J=zXg`aYDR+762E^T1H zz}_)!W};b2z(MnMYVBSSsU!PcQbW8>4<r~SG?8^D7u8N;`n6~R$@KB@DoLxI*5M~F zWsy2WyasPeugi~qP_e1L!4><SBK+(VbNx+(^<0x5bAPP_8Q@9!Vpu?Oy*Y85{_0O~ z^5>QC1vKz6IhW{p)$w+N<&U6O=6{|VA37@=u6P<yS{u&uD`?Tj1Oltl^XJM{&dJkj zPAVH-|5~U402IoDp5QlM{uwdV<-ARE7ztK>9K@64K4k;XQFt87Zrlt0?X=voQrEa2 zoAyyreyRS91nIV3NlWvBK_%a$<10p7y>94^nNfmq0D)p;J3d~lpn{IO&3k(LgQ<Y& zfo|EraN1y%p479Jn;sQ6I%8H-JIWL@von=DRd&AWzi`-`68}2|N!0V?`c|)>7c)hg zub~nvk|IYkvN#^Nw-m}>8A<V>9*wKc|EMMC*1!@^?a}3Tm@YD>%ys)Uxyt$3BZ}js zW>-_U*rux$Iqbf@M>9L34VNE%Mk}9BF?^n-I44LG$jFAbq#e&0**%2@ha;+Dix4#) zYQy#j(%Gr1$TvK!YmQ=2-nqiF#bMLSfiR(}`+%3by*{Xc-yYr1SUzg27~U<&mkLtX zR&@+&%d~xEPd`h2y%=^U9~ybTB24@1yrk|dhz?V}OcvU;8xQiu*=br!m$^-Wg>E`V ze)i|;3DPPLw_PP)lM<?XJ+f_uY0PE*HN8<(V&V88c`E2&Xi6NOAw})rSzEnh=WoP+ zXd$fsujYgOPz<C!OxMnT_%700tu0-8Flo5P5P?59BRLJvw3iIg53Fuh%n76B0Qrfy zm{`-cp+8qgF3YD0)f`5RvL!@;yt>e*aN208NOy5KLRn!>CiXhX2;Q5O(MpYg=)1-K z#!Om={p$Fc4@0w*w)#y6+F2y=j}v&@lo86cNT+g(!9sl<RdN2zkY{M$5qd9d>ETuB zlnPnF+*b!v2^AkHR6M%>$@27B^Wk=1ci!sTALK@Xa`SK%9mp^J=Jb^_V|guJ0+NPI z@ndh1xT3@I@ImHQ7;d?lv611;;@mC`tAf(9W;R6EO!rxKsesZ@Q3jHK>5GLyqDW6* zYd!wG|4l*RIz-t^|Me#l0k5pz36G}6?w4uVTD$rN|Iw3wyxNxq!maP=Wj*Jr(8(vg zKwNoj)q#Fall}lV`M)|ibYDLZpFCmfN%#B56Mp}lAi-Ocmicu1*Z=q!6{<}%sIg!F z#{z!)4&Ny7r74`r2Ipv%3$Q0N5Md^JZ}HzP!MhvKd7FgPB<CUJ0?Y?2fM#PZ@Sh9# zy}lF*@TDdlRBFH90lOED!30K?i7P++@1l5P4kQ&O`|#4gfBbtNzrRxs7f9hL`-7VQ z_7B=_fK?Cq+W*h5`uA^`<Nzy5;L>jj_=lW-FW`Ut)&Hk02ow9HNeB({X_PNcH3uBd zzDLulv2HuuL))4lU|s^OWDJG3w<wxImR!cij}J5CleGzZvs4TY?axm6*c4{lqm+Y* zukmrPzyDR5@)5?^sKLJTwB<he28^E>bvyy*oZA#8--d|VS{`aDRBZIWd=jf2m&Ytd zc21A@wpYuu-jnMqh+Xj6M+KZ?N|B*dj%ttb_WtyX`yzh_tank82r96#g&WW}Y8o8= zDbm}F;QV_Zlt3}9IfeDgR|56ADOpTIbJkm^KHev&9auJs{9C3w^zGra<tMP)l)n&Z z1971n%X2ov4waa>Ve8p+)|>NCCEOqnqQQNPC`)J`Iv5FjPI6mw%)QTE1URe?lhrLn zd14Ebi=ioGREYut51#gN2cjvkJHtwTg%ITG-t%brXsFm5V&H{*iOD|>_8U1^^}P-@ z9!hU-Dztp9Y9svim}jwe^PctwS5boiL4^A;W>Xts-GcWyT`H2^*@uPgm#OWGgYT<< z*GmU`Le;P(@>Pb@@4P(;Uk3}f_Jb6M3s1G&@_D9gat;VcMp$@7fIw+6X4=A~8RNL& z8UC}0cwB|Q{#KY0oSU@^xwm%#s<ZY+<n5F5s^_f;yuHm*2M#w3-8ZixwOP8~s{uRY zd-dM9TLh1tkzv=icacF`-pWuBokm$+dy+srB^8yxQa}qjxh|Pc00Hyjmz+Lchoy+R z($kZ5p$T_4DWTJaAxtF@@W5Z~TC9W<hyHq?w+dK^b90L%x^Fr-A_^Yl?HA{50tK?H zhRMrNSaO?&#c;_!=$23qWBSp0y?lmLT`Hho7jkHmt(S>GSS!4M?!MpSX)#^{k1wxD zTdzNBL^gGpvQM`J+anl*ew|f=CwSynE4kO`DAkx7HM)bb6inx};axm{Gsk$Gsrwgv z-r<`}q!e?Rk0uzLQfqtm&_~f}9pVGOb$cl3B04$tFia(1vnNw_UA38AW5R>E%>WDk z?ptmZCfOpAD}O&v8+x9Z;OmPhSwN*HBVsA#&OZ+wg3rb<V@>d8gZSI8#mtMZ$hxRa z^~&IMfCB~DJr*DMh+Nx;x$a)RZ2Uw|lcXXpzQ0Vg+@;<12ROeR3Na;6Y*8A27MypL zK$2X^<nKlQHYu<YpSS1$B=H<Cii0;#X*c$9#T_<#GsGlm<ZdEt4W&Q41QM>GerG>) zYpKskH`QXjnmNsJ_3NCn7soHt^qGY0p5N`k19BxaXKnQUSZZce?922j%zyU+{7E%8 z9sr+Z_2u3ru)2*<;nJm2hlTJD7`su_(ng)H<MK2sZ4XVvs(^^CalTfXmGjn*YYCkl zOFyHFW>l04rwPU)xc*)2$NC#=fc)-Rwxi!fX@%q$lSBFFy!<wbf&4r#X1YN_qCnY- z9<nM>izfeU*Ns2865D70iLu-pawpEz8+#LH*$T5uhsR$DFp?%J6)%-q^z|Z6dHyB} zj%k4m&&(G=-R%^A(#412pRWAPow#rG&>o+Wvpku?lTF~@Yz)1{k&QU@a6_YDdkJvu z5#GXZZL~7dm9O#3#j6Wxt`26e^p<9Pf9NzYerIj#nGzfhSTOkd#zIAj$@5S@`4?~t z?%odxz?<H*Fe3-TEo6^xa4wwT>GsDbN%oQ&&=w_qe4f90+W4#)qb#1qv!p$mIo?2s z<8RTQ0hONA(Q%ak7z>^@+5YzjkjQ+jA?`!dIH?9lnEVb5gqMn?DrU=oo!AUMdzdt; zUhW8gq^A$Wi|*mmBC7#qs4ej(N&J=DbN(3sncxeeZzz636Bo3DOk7b_D5&Vh`X50> zA@Bm<eiT_@cUbi2%!7zInZ|L}0a3TR;;I8m1ky!R!`15_4EDZRUUDIG5qsJ<K5J<= z49plC4XXCmDy0U>B@5zO;f+;0C<n~RKEFvJ`m_3mJr476)ke26ECSo~1rRnl{LgJd zl@4O##DpK)gc#FE(^C7j(U8qxA+K>6d}d`1wL+*o97K4evpQbOt#;yUYYRe64%-=Q z0j(K2dAT%37&@xt<o+fuy<)y?O`36sbAfVwr3@jv(Pahu%Rlj5cpnpW%7wz+p^X>? zbrcea79H5YkHh})LpU3<I(l|VQUHx^K2Vn0(!sOY!ee0V)t?WiQscR+S)v5!=fT-e z(c%3e+_cQv9MQmXChuMHA;-v@cqd81TSvOS05JUVp5^A*-G|@U?*2T0Clr(G^Do{B z-<AX>1|5OmlXTqkhG7j6&R1jaya#q<j77}1`y#hiU1Ko+#72?w7ZTUVeRijE*#3&4 zUVFG2BiP|68Nwgdx-5vxXERY3qIXiE(EOc<+c90IB~=kTCAq=ZPZ4AzU*a!5j9j`} zTd9`=xV_c-M#y4*z?~k({+0`hDfpj;TfmRkJf0^0$C6UPlH?LN^*|`migB5gO$7Ma zK<>3G%gPK+Yns}#C5E4r&G<44>I4bxP8kYn@+h7kE(@Jijvxead4KSPr!DcULcsw1 zP7Zg%=;FSCgP+H6-)y#tWYnP^Z}?@7Jj`^IKf{%V6xgY&w)LG$PpG`TuK-<n*LWNI zpZpw1UShpj*wKNzs}^1?iiB#_+^iAK2ja3JaVz-UFN5Oy>qO^O=XV#z#XI9UcXR8T z<k+TTHd90V@!NCk=C8Jg@f3Hrriuc?Nf7>|yjrp|wr5GS(RiuYbYvjN2g^6~r!zpe zL-FEg_T`~l1dF)_`Cb%Ct$+C5Rp5IIPw;TaVd(d?CL4T0;AzUW&V4rpY-2$0!i<f- z4VDpLoO1xPvNSZ+%)iEvgR6;U1nT!UcGmhuuiW|~yTsuMQi-eBs-;U74y&YR{%l+G zol2W};v{d-D1Jr&ljhJm?rHcg;0QM`LH@~*=Jh{PlVdu-hzL0>-U0{vCgQW;B&cL2 zmzk?0UcW{UNtn9Pfybou2&$QvnO}4L62cdxg=N&6Su33;aV1)f<Q4cX5z)!^NCtYf zs%M~l_bUFnUO?vO82zFjhWf7FyC9ihC7BfmghYx!4%Y25IJD=}i`x?5P-Yfvn82Hp zFNH;<k=&-LIvz*c)@iTKI9W%sV`f9sLiEIwd-iS8?Dxh9`IlM~Tk_G^_r{_h>$e7e z?h{Y4omUzLNctT0;x)n)z^9Dkv1)Mem|VyB(ID2)1FLh_w&p->1ZTkru=3q(2{zvO zHS9kEPK->i7vc!9RVD#zqL9OeKm9_j%AgM9jTj-HJGkVH4jk<wn0EZ(Ea9MV>ajb7 zQ(J9OE#Xng<#~`nu)UCWpy&CtxgEaLQ(Ulfc1`H`4X0dPO^EZD5G~ld2b$oqNxkXd z)0<Y_W)s>uHOJ%S>oYin&@#NI(cDjJCY`ab1DTBg->X|qM?rqxoHii3S@2>86dyFe z$x|{0)@%2)SBG|zBvjdrx&}Tzp3bvlGj30B4w$32o09=}E8347{F+ubWoCZJ&d{b= zVSS&|bYO6hR$jeRrctIDb2e;Dhy*;_11*Ve_nGJM=CLe_qshbr02rlKlVVVm06zVI z=6+B!X^r_Fw1nKk0xO$tqu@BdvWI10Wo<O+e}Om2p8${b(zNkCH8&><Lq|-c(Wrn7 zN&uY!D;ne6O+b0&!V%^t<plvoGEai(KC?<d^ngP<s?&#bP{_ZhG}#}wW4|$3k5H{7 z1>yJOGtXL*IDBBv&{}l8P;>;CzX~Z*0+gc#OPQP*qH_GlQlx+;rTgylfZu*|*(W%$ z%4Do6h<oQztvY@XF>BLml0*n$LH1&C-Y%%&4P=Lk>L)HER%1O67me{<<|<?ARPO4e zw~{ifI3{pFP>N~4G%9O0kY7)?|9$q8$@_iQE*byqpDB<v2&t0@h~LlyL-T$Px%-V7 z99~FfKLiJU+y;gv6DIb<Vb>6te9f+OTpRXKyaY8ojc_rJtySn}<*S2GzTflfXP;cP zw5pML*Iw(^E-lk96)zm5d|)A_JMo_ccoNHiWjyi$tqP{?UlzH=b!t=}cP4Z9rKsU> z{e0jq!AAeqvlDh|!1h}g0S_UUB%pV$iRXe(I&jjGQm>OCS+-$kKgy{JCOtXMBn2cQ z66~#Ntk_J5A&PlGKLp?QNK>9D&LXyBS7t6-rMwJ8H`8s4B_N!9g_);a`#`H|r-tsZ z#)YhBxLk#ogW>%z_&tTyxoW@j$`k`JnA{EEBHM~3R?f+T{7nQ<6-kUuJ4jUsVa>^I zYM{00gRDRdzGjCM(;8{W4QEZ20`1~BufsM8|JSdr_mo10A?6rF+}*s!!x0$tYDK;h z^L;l_P!s4jHd|jQ)jk-De+CNQM<rX&of2d59{*I9Ns!$MFyAAGi-2>y1S!j88<qV( zltlq3%WXds=(wDjC)HP`hClg)I5M!YC617&WUI<b2niXLPSn^0+0HnBU9}T<de30t zVP|4_18;`@tFoofbwRXQeK$~0iRrBY%P4SaP#B*opU4|2$mibh|Ndm0?-|!Qcb_gb z;e@!7@EiEfb?TTTvYR$N0u<R$+SZsw&65u_rR)1u{l{&zq=Q8nXE#1u17>6aYk$cT z|4_l;C!gZIj=F!mlSnN95`HhV3Y~+&=<$D~?#@tWQFl@(h?_2>y6-4F1sy~ErEbj9 zdFuV`M4YxJHLkcca+^{+v(eh0(?aI@xF_5Z(K>Xu`da;G1GWLTTDue;4!}qaR#&CN z!(f&Qr5oT}D`4)Y7YCDjfV=TPSyNfl=(+wYV))@06R3f=DV_^30iQns3X`+lCohDM zDvg7xnv6dC%JFnsWnpp&`@Lo<0LzvZDdltYvzUOHr1i`Hf{TKq88M*e!QKUHDqU?c z4}We9z3xJCePi-plmJ8bP%CsQ=fxP#AGmcA(7vvCc1obGmoWCfXY_Wc*>UL2s4^UZ zKEXD;N`tNhx(6Rr#Mvc6JoAZ4mlgqGqC7F(8l(Wgk~m#18(3kGH}ks6cwjfrPrdd{ z1JC?+Z+Rxrn;l4*$8P*J(fQqmK}qdXDs>1Hw6VZ5!|3#K)N7+av&s`dj_SDt-Qr_Y zHg6kCqN`%>>jvo_@~jhZqH`Vo3N3+zwT_cuqrioNhwgSqq5s_IyI0_$yD^tiL71No zo=R;YhV_q)W<ncH;7$b_eTBY%n9&=S@u|Oj;%&?dUx`&(Fv;+t<_Elmn0_Nrgq+ij zr%A@`qkxs+ZtR@2J7QeN{s}2uc#~U$FT+J10*_d5VUL07^7&_=!$ATg;$YN<1<^B; zA<<HqCM|f~Vq`|8gvCpN1tB53WRLockPZjm4H3&XI^(n5$_G6JE}sB>ltn$ZsV|Z= z_5Il%;cbDoe_Xoy1^Xrpy$NU1vzhu_+r$M}3e=95-F4Rm{3Rpm@utEe$o7?sZ&++M zUgAR%Q}t!^tqeV;2gT%WJMEJJB8SCc_RNVVqL*@wf|dlyUR?zmz7IRC&_NSofK&tQ zqG259=&+mT>#I<L+;HNL_(%390td+OC{gV@$c8o0_m?wza}={cxzo&JXGfBu8?eo0 zyLnHgk_BtKx03j&rKZpL+LpKhryM6>7nkIDNS7QU`U*%*6UA$+;8{F=+AsfQg(B#+ zDj*~V7>+Ysu+RSAo&r*j|7D*C3QxO}JWPjcN>BHvuQ3b)j5d7_D37*k(9NJ;XdBt@ z1bRI4-p9(sl5Tubwf6_y63)i_3H?1nR{Fo40yx1_J278$@(t(&^HKl939d&=S>?M* zk5%mjx0T8+-*Bz9YmGTOTPdb;bsBLjVAS?pL=!kP5!9;p)aukI>F?62M%Hx{bjJub zFMIL;YGQ?Ua{VasgpKAG6Zed{Vcu$E-WwgrTL1zAo*vBerjY<TUjw%Q{|kWNKFr5C zhr`e`q9y3e>FBoIWiqaZoZ86D+9N@wG1s7QWT|tHCX}ru3VO7t56vM8>dFqyzr3qE z++7S^Ib8&&x<Y~d`Y2L>jT-akCReB$<_kRLo;?JRWCI?rNpap2Vdp|c;iZl+0vP&N z5%NdOk%!wP*%_b4-`NHS27dW&h_wS?>7c}<W&7lt#1RtT*z@ef$t-Vk`auq;G?cxg zK}W@Ym?ZpmO{Fa$tQe>mg%1gz)6=z@7Xu-;xN{3Yx#}-q8pPya6@StAVS`Ox2S@(m zKO8yiKw<G`{L`vD4t2oAJpM2Pq7zNl3DwO^*WD%K6NR<W$`bcQ*qFnhR@nx$h7E1< zH+u~0X)`jY*stYv-#5|4-Fs@9N9OP|kV6BlDCSIH4V|>FH-ID_cpu>t{B=G^@y!Kb zk-}R%1IV~CZ1frF4A9n^D?1X*>LQ=Oq6|3U-GJvqnNN>)ujSQgl$vMeYw;c!67z~* z@M*w6{qBihk#`fr=QF|U0$mzjGfZO<uhX{A2f)5~y{@)FoX?~uJ`=la(mCn-CWA+Q z<DNGFt#c@N=-bj6a*5SXJBN3aU%tR1=xyh|aYQ`=>hd|-&uLgatR~9T)QSvtf9kjo z4*K>9J&PJECM<>VjcTKe{TyDG?=GjXV;2F?_5-_z7twtKF6<$!W1s$Fm43RVV~DD* z=b;5<E%dj-Z*}OQ-i2Iv0ZI6`S8h%VptE$JdKwht2S>~X)I6?{@C%ok)-FZcEa0J_ z!l<wpj^MgQdU&WJDh-LxVi#8E5-`S=y*m<sk{4{vzqyQ!P5_kb8(ZJZxq6H6J_g_f zJniHFU5YL2qg$iICeEJe+(KMzB-bCn%)cHT$Zh2!VKH21v~yy<sTarX^iroK1?1o8 zfx;|nDn4z2kFn7OP~<U<Qj4>!X}0tKV#B-wP()Opv<>ECjSXabwOA(jylk@|+48I# z0InJ3I(mzAM#_f=rd^|G$}a_~YV+qh&&q?snEOso_9o!(tZ@*ZUVq8iS^RcVw~|x` z^rHdR2!QgQ_)Agp+o&~kF@ACh?_=LopgyE*0`bI^LJVpWR1sD0BRIM|Q_ea11A7j# zrvo(ML*OHIpq=I_7KHAu9(|NDp9{=!U2OJJi97fkOmKhaOI>H6)soY)_x<4+?0~F) z3I3^eEgts=VnYiNGNXh73`x*|m3q@o24dlw?j+a@B;Z;sU~L@SI<%8{iSFwUj{8xp z<oBQ8{^PhW_*6i2xhw>%o_LTLXan#Dnd>#*6%GH4=v3YIJhH2Hh-mrxeuWuoCmjhq z5>yX@N>8gzBc+H0OjV1HvOrpco&+66<EN-l9P1=+J~VbHL%P0|j@-Ka`liq4>yumD zC3E5C*yx5p1ycB{XMUzmKhgkUXQ&K3z9qj41Y_(YT(1<zYFG9kn8?^Ll2!jf?cUmh zF`HHrLFPCf(C5)__E}8m*t(18(=oNycKOJOjMN_ZJ6a#%iGt&Zi({17VX$$$MrS*@ zhf|?=FfK^t=WxZGfqGE^Hx}?8+!!i|cR=o!`=Il%%KF;`+^EWNF<qH4zWLt1ty|_U z39s3QmQLjfG)(^7S!n`#P@OJgp@jfO?gzk7&fG<PuSm|*%-qJq&0pF^<kJ8hfV15L z5MQr@Xv<~3MeUp(LfM+X(Tb3S7>Gx&-T=5lJtz{Dz+-39-<|;?`;AKN(R%NAUc+Zn zk$1ms*(_iKGh@L12+WLKp93|4VKN|<Kxg}#=6y<uhEvF0dj-D+zXQlx&*sSggrYkN zZKb*`w*{Ra3H9e__03<jIh$GqngzAC{&LLDKEh)Kuw+6i%1Id%og3BrMa;&g*EwOk z_&}a@cA^GAo*DotRZ#x}QZ`9vQ~*^NE`Qv5)xsmmYqZ{)K_fGQMwWQ~=XRb^0gnW? zX46+fO`x3&p(OCyUgTG!d;qEvU^_pm5~x!I(02Y)yaSxVXWCbwD;yPKFKJY~@H#Ys z3l@D9^d+_VlU_@TZp)2mg8Sr?iYQ*=1@1(55-G5!V5fI2H;pGK1R2v`c~3x@z%51+ zz<q>3nZV76wVxE#uV9D^Sjtx;dUVjDDe|~MhdC0Y9NCSc51J^qBpN$HsU$$?NdBX9 zr0`A*Ml(ZX0`{9@hx+#*^%*5V7keyC-eN7JuMX&@<a*7&fV$!VGmwj=n*y5gC*Y6= zY}ckzATR@O?@O>c&m5>k$YH&~QhRn99CL?~lJZNj1VMl9K`!XQ>yGj2z+@dvJ3Bi} zuJRn4HTH@-t3R#Z9q85?K0E8~)XA$PMXGZt4l*V3W51;X)PZ=z;2uNPjBEn>S8?(; z&3Ao>KQYxc`FLwVvE_0HHt_RJkm_*$2U)wHDCC@ZHx`yjVk(7Q6O4h6RXHj{6G)~H zL6e=&1+4u;PyERZ!mB6}Sg2{Qsc-7@x`v^Xq6lGZ94ynSR)GX48Oyr<I}aVn`N|!L zcLCAYCWd_xu#TDD1v>QoIfD<6c0KSO<$<U!TO+!wJz~+vJNT0>&^uKWorwb2ErB$O zNlhQ!8wIF#=JQ=QL#ZY;0PcG%OIiT|3jqP27H1fvhEZ*RhO5QxMRs0~xx6mbvxJ_- zK4YlqMXS~k4hmNNrHDcM*(LbN>CyVYBd2kfEYKjK40`$Ta6;8{6f=|$w?o?#c*5C? zyYlYNO{lRkm_>CcYh3;N+{nK`?La$(G(rFdbG?PM>6^#c+LAGhx*{a|bK$%5*xSI- zaN%sUp0v~C;9P{2;lC5@@h8!Hw~C5$6NZ8fu<^pj%RuOYAP|_veZYI4d<X`nk>VX! z$J4Z`cU8{>9hU5ij%4!wDn7I?OzqBAEi_izj?IeXTGjF-3F-PfY4y>C`1Q*UEui%B z4rek$;YWdV(iw;%I9cSnb&PkSKYRr*;6rApH4fmUlIwqRQs{4hcerq${W5G59qLDQ zJz6bwJ$s(WYdd4|D2UX9BRR>f3=pn4>Chw&=Z%ZMSrd0CQ!Thn^0j-YctTB@JBin0 zSiNd9-b#M4)lO&$9NmCzkV=7O>8VAOGe@hI)0iRSNDMD!>CQSB`dI*-Mf4-j)sPvt z$U5TSAc7hA>)PnL@D-5<4MMOQrSBmOwSYKUhb(eDf)wZB!-imSddEON26V`=Z*dbF z#S-J7Ux8c-Yly)&VjnC5rUhjAAT-+Ju)U>Uc9~zN4s<7#hS9@w--7N-b3&^7zg_Yg zkk)jt0(wcM?X3S*V*d2ME?qS}^Lc(a^Z5yayoF3cOw1P(pZ+Z^sh5mA|7HxBIk>ZB zMvM*;TIR_2HUn*hfc>(rx1FoODcwR{(M-^r|C+$vCl&~L!Q4-)ex%kQ;od_`cBA0< zzG{F5@jzdCZTX3Z1oiQEY36KYBoT1?^CbA(jlZn{lnP_9>#oXh9{NWjN<fdD$Khh= zTcbE3FTqGq3<Q0jP&e=;=oHA`03l(fbLoRBkuC5q)Dj_fIM(tF7~*ttuX_Y6)F~z} z1B*yxl@|jM*L9HHz)+8AH|CSQ)Di6+aRTx>o(GX<7cqsqfdrJcGK1iSdOTx}BNiw( zLn;2oKc5-kox|NlHFh?-h0YdEt~o9Q;Uh{cMjTIe26f8(yN~ZzHhp4-GD&*vnrzVP zenX>Kf(3nF-8)*U?nVZ%ij8QHS$VbfGQ-X=@8#TVmMpaOpqR9WmL~YTDMWZ5-MSt# zxss-@0poag#7e!yId+a&HGON|3v;vq<tE*9?OyKVG}m0k>ddv8t*#4!AF=;-quzS} znVqeS5$s+d`B=X873&65Po>?wIlhKS<EWF)1JE!kv9mNVb*(EnhV|L@1F)#^Y@4f~ zoFxiEG?p)f?3$<vs=iZAF&}1KDZBqAQn_K`bBo*3q^)<Bh<&&IaA$A=Cg3jIDjm&) zta9+s;xtoI^v9!v@p3)?qgkK@XF5MZb|mp=Q=&6TpnG_J8`uI@5ZmTEnGBazbxvr= zVa%@Ei#YbL`&4h<0EMdH0!bhpY_uSOO|fZ_1BI3#`5upBmKJT(tvaAiFV{!Id0=uN zC!Q@Kq|&a&B<6F}9CO_s0M2KXC{<?)Ot;iO2qMk}GY-4bXHc&DQ%1i^2WXp`#pO@G zY^$lC48$3T+pG)h-ErGBZ$Ev+W$4C&u$}vuVjsE~K|?9>v^V{690!hnU|>m0h4W@A zlTMu;{=8<Lt#~w(cJC%GE##HJSZev<;Y^zWGnE?B)UedFU*?)&cT!%>JwlXf^bLhJ zCieSaSRJsLdx#%wa}t)XNRQ;5naP6=n~N|XTwuM{1A(9t+y=H3$uHFPU=mPCMBJXw zz|rRcvXbxaeP|4m)1vobfrB}MZ>@sr?v;msGe;{>K}KMnx-na(vHjh*w36c>Zo?>> z?HLv{HZ;sR6`$h}FmrZHuf$TnR<+C|U-hsEIrM7hGi~zH#<vf=?)&V^$z<Kh^56Lf zFB-+R9PXGR%m%ysA0`f`**y*P9z%7`F(@abAO#uhLlET*UWC=*qi5)aKWc9-FP5=F zzpdllJu!2u7vzbkAYp{-CJ<6{paHsA6J#pTte&sYv(irl5C853AT^3)9C;qcdr%Li zzM@|l_kt4XPYc@!0oll@&5m>dpW_;69Ixf47x4oHJ$!~J#T`?41hbBIeN&`TmQhX{ zK7$IB0#?~C4yMPo9v{~ov4Ig=3fD5{$K~BA6fj5J$&9cuBVT8brBDD+)VzhhFGr3S zAS1#`JJEShfMT*1VE*`s4GmbeGPLSiLPmR3Xu^;}RAT9HXE`N2;u@P|Bz@$??RBII zLS^T^mHqE%;I=$~rkMk@l@&pcOQwrO+@qug&V{MDYQ<IUQFODrJU%8Bj$;?0@i(_U z&xKv<U*(B0OL<Y$mD>%{)O?*63jq(S2eiCG5PKs?_zpp|udQWWtcUCU20MTU0^HMp zLPHORK(m59*A6XTvo@Dh;K=mkFbGAO(zuJbeRD&!J#_ks!10Et>*VQCB#kUlMkTw5 zQ>J7ZG$m086>;iy>heC->x8Neovt`dEP_(ifQpVs26O$PI)FTQgI>NuW$MezYs`vb zAAWBSRT^whx2>9I4XXbDUk2tE!;E7D?thA)%h<KhQ1fCb+uCd<Xyr5<zHBjCp>_ra zA;MDPgf_ZC3kT@nzC9Fm5&f$qKp4lkD|#RrY66&S<3aw$-N*V4J;p2Ia4+i5f5Fy3 zQm|Zy&j>QO3Lpq*2(0V+J+D*ReyguvkWPo=Mp4vuM)*vck3goc@J$3QlG}<jww`RG zd2>+5YjL4RpVxi?7aB<gP4&2mJZ=L7TCF1z;}YLZPHSS@-%vcCXn6}#_@ALGIV;F9 z%-ucm-0ey)UM5Zj118i&AakLELuPB)uL-qjPX}6F?Od5A;`E4unl;*}=|N+CMktuu z1fz$+?V-9KG{COD37i>(c!z^c@jNYi-5(75rX+J+u5x%&ohtKRaC1%?WsCaj;gz^0 z(7<z0q=8`GYVO4Z!JK<#k?Tfdp5VGr;njLD;o3p}-E1(wTsduX=gFzI{`e@N>m4^o z9Vc0@zfoQ|450jNNxc1wnGZAFLdMm>h)39Sp*?ffE4O$bf?BLjn8oVo<jT<cmuh*h z)vuL&Z7c{CMC$#&j+TP?)*coyUcp8$10efM;IBY1(!2(_Z54k<^wGQ6!Q!{S#jp<w z!cIX%F-VrX4O}I7={b81et4PF=j7;G?$fP-LqG<2M5%*_U}+#Hqdk)Tj(n1);4(K4 ziHGM3R_)2II-`0GM;r68D_d8h(|As0an>@JJae%nviKdH89<KITx7x#s)ax^8?C`4 z@+H%Od*cYt6W1L392NIl!f)J0K=1g)D@2(PJaL3ckacFg*fM4SV`k5YSwB#{sepWi z$T?r}l-tLei4<dgbk&jMdWane<{j2Xad=?gXc|uFM~xEP1@OOCIm8b;xeK)Qe;Hfl zX4B&3G*!|GA{yw^avItq*)OO+&8*zIVRHIR9AJxzlcID<q8i+**SC=V@f3CPwgm0p z2jR^J4x%Cr?=2T-PYN*olAC2!cfi$yqvJAb7(~kRrOZoJj%m=U{v;F7Rm-)j>$Jmg z&@O4#mg-V<WVy>Lo6j?UBoB1yM`(_{fdqlUgln&cGYG{fpJ0;?@~R!mdw1)c4fV3a z(DwmGMOq0gGM1o5G3q=;rmh80$;NS*_7}y%S#U#nOPQ;--N?Ye&_F^Z79LPu&`dZN zzWMShVYWIa;wYV&_xIdS7`WVohA!p8$#M01ol0U=ko{ptEUE;@2+q#Z5F7*uS05=g zk{EqU%^>1(S4!<aP-8P50QV?tH~h7CiCgu71UU+dHoeVLkeMk$24I-^x{nEl7jSGv z^X}azxQiX}-xmO!Ql<g+b6}tj5aLoSlb}UE`&Zld?+Hd82LWiLX?1Kb<p#5M=5sQ# z-h(Y6HI4ZjAJ4MEP<SestGRuE76OJ<%=Xr5UG<yeSv_J{!PHlElGmvk9I38WX*c@? zbiM28Xp)Jy%ol`^FJ44JJ!Wb+X<u);4C<huG465oZle8kOOS!_qkK)moz*eXz0s{? zgFXDft6eAiX{G)(tdeV?;6@2qz+r;R2Ba)i5`K^S|EkNLT5SMluT(838Y`qkW6vKU zP@O4vPyo7BznyuTbG+Z2ru|Z7(JH1g(H6s+7R4A_0C4c@9b1w6lRA*RsL#vmDh$tW zlpibR>$mnAo{Pl>o<~;`OYH_N4coW9p}Pr)uL(uyzB7uSq><H7@6JSOa1Mgu@b=>u z;@eN^W_pTMgjR=&nHvQ=?t!+7K1F#!oaNC4+&V!Qg<990L5ZVlxBZntgZ|E9zbu$k z-fh}>tmJ-rh#qDMjAb1l?Xg_Pg{yd9!4wLZ*`-!gN=W!nGf2v#_j+eO>(RZAWPG>n z6$H2#fQBIv%pSqF=DWVIcjHMa3nl!XU%n0U9T^1?&(lg5LbVL3PBAPaFtML*)a@^| zeapx5Dtq==yp=eY<*>q~tL_Q9#B(Dd2o>h~MU6N2uOu|${JN!v82Cc#OR_goF2cR1 z-uNCAVk~9EPELa6pD&XXRC&oEKj*vRbVq(oTCt75I^x+yLj;uXWUl(*>Zt3O+yhkD z@*p(QP>N}~Hp=7h&zmfyp&6nXziD{MLSJD^6&U2wc0&eq=&(THJqQ?BBbiS8*Xqv% z&0h3?6w|bSsT>U47Yw+_#&am`Sr&Xcej%An3c}7G{C^540MT#HMFfV?TJLWd=fbmm zK*c4*z(MKf{Sk_EikULXLXLbx=6xL2pIOWd8)t}(0&-_R-Jl0EmtlQZ=6~lmeo%XQ z6IPS^a)IfNIx92DG**U~Uo?kduk-Q?y?bPHXiH{^RqCI7Y3*bBoyRpRl|U;TewWgW zB|FflA#)LQ&_#J25AYPtKEk~)ycfC=CEh3M75Sv$UvJ3HeZowv-u?_ytvstOxg-wl z(8XRgjLI9;F=tled4Af<UzKwdPOK2L#ng@YV{Un?i5*e0Mv-5@Je58Yy~?KVhS?F! z?lR%14v6dPpJLGrP9=b(_i0yN_8V<@M7Pe-NfHI9-n`a<xUY+#NW~)FG*?^aQop|X z#Q*+-Afik#%jP=rf~rdc%y5<}q|l{q?y@jk%Xiif?KtxK%^Eb20VLfQ?GF+x(5}to z-63jMN(Vc(x^R_@>&`eea_lDMOk7O#JCDPwpNnQ0a66E?KF8{y)n=_?I{3|}WZwij z5KtL+-Fi%g9SoEk%14KAQ80~02j91QAcRXex|3>M_redb0-DN9cwM*cI8A#+Ak;2p zR0LPa=)KnfT@}2JOQt7Yp#I3|z90+@pxioz+PiY>xXqelyApX-PsFS?IZU_jfw^$L zHe_nY(mm;@yDCw6YJiCv6f2Lw^i2_J1%5NR3L-h`z_H9q%O2VmHAFI~C{>wLCkh-T z4nB=pWy_L~Rfd|Q#$JWX9fRSy{xS=RST8p>1Hh8p8Yn1J7MuCaO}m4>_P_%1v4!px z8_3nKj_f^thU9ZHEhw{4uk6v-=ZxiWi96+cV*AWbXiw9oGme!rJ$mD6y_%C{OO2B4 z|Kk2|-l8|*BE3fR<|<=_x~&;q3BjEprAUQQ{a=HgrL(IELw%97@=ORnvmROZ5w7Od z);uYF<?x|m<77+%rgWel5fq{q5knQ5;ZO~(G|co%i61nxc@kSjbvU;*h|?$b2A#ZN zw-3;JP3R>iL(1!xBWtA}L5VCQmOZmQj{{e(H4>rHpKW6YO==H5jfWp($;Z#^93=c~ zM*s=i?b`reu=9gZ{(5BsToFbv@t%gqee|APQMp)Nt?3qd8J4b=Afjyf^|i9@=NCkE zyrU<9{0&}%&PtF%N5<a1ri1n9_*?BA$MKPa!;W9$kplw_8B$T7V|qU5Bk}ahZa`h! zP@7F(cQ?3#!l-iux~nDv5DcA3gc;TAr{(B0bsyX%4L-gLh3<~&w*;57w8!t^AQmr? zttf3pSs>vi9ZPRPcb(#_)NCYG>xU;t8J7U{=26;52HdtZaNG3;i-VFdBhDJYk~)(V z9oT(s!q7*;rmXq%b?WoWR<--e_Qv;kPfkx<5%*7cwUfJl&!!5y1Jgj=uVR5OWD*Ia zq{*$u5%Fj1c&FLUSxe%R#pc>6dUW{+v!FTW;K_KjfC|1maLF#I;v?2LZx^Tz*n^&Y zzv9vJ0Svs;Z5<nwRRvXt1umWein#yn6LzgScQvY)*P@2#?;YN=nUdxNU=2EKo_BDk z#dBJugS&cYhz4#kD7WQV>IbW_XSL+r(Jy0P;MRSuT;Rz!Z~%Z69ts*Rq%tsFb3cZI z#`7+Mr+lf`1ieUDWl=%!n8)#f)7x9{Xyr9%Fm0tj-I>dV7gxQ!Op0A>k-_cZ`<@3R z4k1;0D`{|q-kHniu~C`E9u36AXDS!lYB$W)-^9ypz$;3IeRR44T?5mul1H<Z%2Jmm za!^DhWU2J^tZ_(d6A%;GqOF6aPp0Rb<yoa)#e2wgdS(%$gIp$h@@E(Qh7vf9EWMIz z4k-Qt?%W?#54+fS1RxU1tof#0g=LRH5~5>3#)!K6C(hk5?2Mx}%Jv}hOy_mD<sx7| zN|^wL7}Q5rnY79%8|&>|zx%cJ*SO#cXw;a5q48>8G785%I4=E3-rlAIo<fq6$oHMM zT4aI!kQ5ukhKrpNLR~HG!PT9)FK-00j0-%Wh(>$XczmxwYbz7<Nq$0X{q8!)e$4=k zz&jtKcw6^{fB*KZicTwEste&gH)W--Op>ds95EU7x410xLG`nF<VDUs?#TIXRWljo z`3duUz(X8#g(U%pUk^F_W$E><>u*1P*xP!Mvo0&Jx;Yvmlxv7^1k-L*g=M;sTLkmX z1_Fw)_(tHOrdx<d&4b_~5SwJ7v+%OoaxHL!)Ie6U=M5_qSqLNFd0B}+c;FhBuRpwX zu>7O*{=GIxqrd9Fo?Q71W=D}A$x^w<T)SVBDIq%m;<Ks#gA~^=HJeO__^ZQ*Yi`;Z zs?+_ZM1tmTROW4e50GK2fXwrP7Oo#$PH10O8L3kZc;_tOCsQ~B)kJE{E9rUdcFk-2 z3TNRao#lN+-!j7bi~^l;&~-qP|BY|YfK;Plu$cs^)_EKW+6D}{>wllU&V13qEDof* z;#dTX4h-6TAMtXc@2b5=?$(t#m^~tDMvuWaYzdBk`}8rpzC{mcDWm`%;{9;k)9>k< z^omsmcT2&XRJrHrR(E~&{pVFneTeGr`W2g<B~Fji16GUC5;e0yi#K%Heu7Y$EQ&Gi z<%)lv`|0<1Pz_F?1d%8myLdv~4Hh&2djlJ}Bb4GvUydu;9gbGZ-5fl#lMTwSk%Mpj zcGra#;JJti&3qUd>CTd--0#sRO^8=-*m(yoVFIZ!$9O1keOHk#W-maq*}V&)zH8W^ z{VX%L-?Z)cK3|>wV%UM}QX8!Zfp>>F1MrO_a#wr+F-i;(A=l9#)-f&ZgW_tgQgboE z3YgUH*FZ#FnvKt%8%YA|`vPb*fhyv0FRK7HV@E4#C7oMtcz1$k)yehCOma1{l!1wW zD_PP2Akgc#1Qw>tCG(^ib*gJ1GNbNnhP4C{QWWFo*IzA;%U1Kq1Qn8jCH4>@ZB_P6 zc??t5*m_&elkW%1@>+)gt#m;4`8|WigGampk0cuL2o8pIN2lM&+8p?EM{S4?-JX&8 z=D0)T;BdT;5fZdD)0W|2ru@R(rnHqj;qR)RHpnHXITPJz<D%WErK0*kNn=Xj$*&ek zx5OU$$3DMe$IUWUao}N*y9eBqvDZ<<u7kROg2*x8pH`qlf_S>d>l+fRtxM$tP^Lnf zWl;8!kk$QN>pb<IFT}loc3o1ZV$eG1aCWj+>5(LqH%7|eajVL*S*cKjXJ?^De5`6` z05rSw|Gc^7P_|nJ)Kv=er$L)Ui+TzTg;X);Cb*Ra8hU{^oBm>B6@lYNj%GVCF9M=W zy2m}BgdbcQK-*X^1}d-5;yA7JcOGZ!q99^uJr1`ic-%lFI#Uvc2cv-S<#twNdKfkf z1IyK$H_wjdn&f2TcbP1PO5{L-Ll!uOz5nf%9hg%H^E}z%=2maDWCL~1*vL7a-^e-x z(#>#U0q(9uLH)7^L8Js$pTN+M*_3ag<+cc}6PkMYYBrd^Z5gRi>9!mQ(7lQZ1JPE7 zuqpcu;G`vh$=hz<8(f5)(oa>`?&K12KmFEkJAHDvt@PpL4(tJ+*D2){BDT@#P3Y#a zVm-gCSM?nNK~%&e@`+p@hBN6}KU@QIX^sOrsL+jkMsmri^GOV(%zn!Rc&3B7DV`o~ zP;q&)Y;5nFgy~r3^M2!BH)6oe_3Ok(E2Xblj8(}3=H@L=ah3*alT!@xZ5)>sbukHy zq0@I7F4YBEfhEAi!OA^`rWtt@#ipa<fQ&q$56-R~Nj$R5H5&<8SucPkjtD*Kn?nk4 zv7kw<*Y5>))c=+diWO9hRPGAe^5pl@X0Fv#H}cx8cy$mpdbDn_yIeG2>3h>w7E~#= znCq@v!K>6n+>gp+0sAESaaL9YREaafJmz111WaTZ)K>M{8h^uS^Q9Wy2fQxlEo7Up z1?wSb8iH2`&o|E+9rd}`8GkhAS?>I1<z}Ua`N|GrQiI}3-q6XgqS1j|D)0RZtfvpY zq@yNB%8Wow%b@YAYPD?SOO2X-so98BjzWgNxFeWUSQ<|Y5#%|}*;zJfzFlmqu>DQl zT@kqaD=QNkAS*09WEo+X{du$2uS*q09EUsvy*_+%BH^LLCIW6l1aywP_wf1E6tb4Q zamE<WVfl8vw#v<&l=k8Ci`NE^(E|9vok=x0;IhA!O_$$hi7p1&Wvf|=RsDVSB72t_ zsz6d68PJyceR|>B$j&W>aiwnUlPNTl&u{Y&_g3-i?ir9;1E+<SkbnZ(pQHg+X}`CL zOGAUfOP-YRvuThu6LW(Lp5oS0UKN>jYB1oAzNc9v4YPcLc?_IWenGupti6781=QhJ zQ^}5u6brLMHN6^!tDl{?Ts5X(Z@Gq->0WZ?t-anWYTWH<oM==wle=m9|FHFyQB`%( z+JdN{s7OfnA*8#z4<X$x(%oGON;iUZcPQN;E!|zx-JRb$Z{2&x_#EfI?7dga`P5{^ zpi+{XEYTU~KCl|SK1)39BJ@_{FIC8n0vSG;0RaJ6LX*phn|xHtG^G?NE`1uco#^ux zGpAvFYU|yg5(K8T-QjcH_lfj?!+|+>f%gX#po_JyyZU5)-7t6P41owlZ)`TkaGLMT z=Dq8cNG08vVc$|#Evl|)fC&OvnO1(APGCk~?7D^1Q@Uq~{fk(_Oj(_>lb_WM*K0R) zhd~kNI*Pd7ajaRma5&oSVBVNktfKh!wYL*m=U(+<$vk)U&l%)6XbV8>(HYF13O(}u z2>ujFa-v-B_e4V-IQw3TXJ4!+{iQ@ho2wo6hK?MI7k~U-xx!_&15*29@b!+8Q8sH2 zp+m0@+AAY~s-*4HRk>20o-d^Q8eAASOb#EV<)u6*;$uzvs<e}J%yU6n-AN94?qJf- zrd4oVLw8+3NyKU~CBPfQ>mFsxSqqoi{Fk_#r~wqF{*xYG-K)?jWMZ$F3v#=nL{F5m zx}#H4#N07`8{PR~`AWQOu8sEM=8i)D(;ngi*tS95ekDo2P23b@d9YVDoK0v}p6|uN z2n5{5p>|DQSk{I|M?sx!n+HDI`S4%%{2sf|g&DgPV;Ftf7d!#javrd#ew7=qw!A<n zl*rW2T%Q3B!!D$oohOH5rpzorH4(mII#ChncD}iN<QkZaQp9;=iu!7M7uZ(KMv7wJ zr`(E>Nj7BJ8i>{0^=qBHT$-=H=qWmMJzNpy0<bmp$ydUj5<n_W%!IuI2z{VcF8X{| z-OI#^hs?)J{w(V?8z~Q)KU@8aFf4-JdUDY;W%qBCe)d{Hp@P>3h!s^Iy$^%rViBEz z%J(A&cja=og_@tFZ4y1+`A&aZ3ocB2kg=K6bic9cCanF}CGx-)iXtQX3`LcP0f@a| z1sMwLZs}0FCw(J-tQ#B8bFBK<-#IrlD}$0<W*;Rf9Kgs8N|I3lz^kf<2HpzWcf{7i zdI5%D=BCT#8gPo|g8<7-v6P<|HVNGBbt&#8HMkqKldG^~8r#ut`>al;h94MyUalan z&s~xda$js|urPz1G;>3&uq8ww^}%x%0|JLZz*oCkbz%>o$FqB<1Gmi|`9pwVWM3@I zX8}uj_&}!ZEj%Wj){qogkBe4Kd#ZVD#y?k6ize)|$4ad>03(BG&sbifMixkS)2uFn zIg76KmFhyM^_p0(B=e7tO4v>`x}3}wOIS@1FSP-t>P2Dn2#~Fj;JhUi90@IcvMU$! z6A&_u6TeTCHJFV0@?P$!QZs4SeZ<uTA)AK1$}pysXL|?6Bi%Qs!>AR4<PE3+cV8sM z5m%+<>A5uaL8Gd@k*sP8Pmv!^QSIspyV`LGu=}OJY8W6@d5_`pCtHEb^LkPFaE`aH zVBN^k{?#IqbZ|mGqrt<D{8yvOHW}Q7^G!UQzZJfqfPtA|33Z<V!nQhvMnMbY(YPrN z!Wz`EcPaGV`zt7I1@$VHT$=z#Z+y4$aJyl(H-G1qA@$`e2Kw)78Q7C%W`pAb_<g{W zejZ8QL)cXcVYIpxk@>#8Z$|r|X}j)^nKBCOu2Gh=6|Ux%i}$`=chdhzH7|K&Wq*un zx#HBL%{&0Qd3&a@`Yh>Vl>!dS4{J4x&mSji|C~&gFbive&7%q<|3o+t@i^_2FsPfi z<qM2rziV?>y(1xM%{(zls`(4(*qx5!(XlkTn;A#<v$y?+2KMO!ZLEv*w@ak;wQ^S^ z{KcQ^#UQMNzBF7stG*q9@9s2!O11cnb@bJVQ32r59+51m!uprkI3Va~`3f!~j{vA@ z71|GjH)6dLdzB!3D!lkUys`XIYR2{b1Fj&Mk`Nq*9NZI;Y^gfUy86HVac5~lO(WPq z?zkU(ke{FA?St{<Ue}#Pf8V}EU*8R1`0Lgb_kGS%`-_3TdFG8OvuWgH?#jve!C{aa z8yL^#`ECCm<O~V`y_Vy}NwqMfvrku-B!3YAN1s8Ntk*qcEdC1A5aUAz%_P57%coj* zO4Uh3xuHG?H9EGhrhHot-()fx@55`CIBie8E!M0Y8voilgP6yQ<}xx;WNP4WFae!? z6hi`sO7!>7_kpw}3;T2Krx=a6sGSm8`<#$Vhcl+n;+b5u2vn;1fq+b{3sfmmBQOwq zIhyf_39`fvXGL`t8io)4Qz2=e9pwOD`<jS1a<e9ulT{cq)#mt2?s$*28?UL&CM^SW zy=t@ccOMG+0}ew>qveX!-n_ia2$nE>li!|KjX|%2KH4V7$gE(H8>d^d=6(4`{B)oD zQTV;5Ut#lo5H7oysx*~m70Z{`_}$eRZFodF-feh~Udh5k(!M{Mr0&n7G*N(;P303E z2tQc~=eIID7bxh(<827PP)rEiLTj+!C{eH!D;O}bv~D=!IzO2G{rp73S3V3u!soA_ z05N#@#}uFD8Vq|QF=1OGapKqK>vWa=?WbX4${I*CI%Rl|pT4q13&*4<2*baE2LYuM z1wnR@w8Tg3u@0mL&rpec8Qv_pVUQo<vE?C<@NRvW+;23Q(DH5_0K{j=V3oB<6a>n0 zQtx%g6%<GFj59%8R_gmPK8+@m?xdRAY2j}%H{|i%aKAx%?WPcN8I9$xR63FGq_(jl zc_8C}9aXxb@@2#Q4A-qg3WBt#&rWQ9tEj;pk<IC*?O4I6fa)MX<uZhC*y^t3)Chft z4LS{gtGAAeR<{a;v&?uDe1C`l{NKNGa@gapG+WI-b=aEdl`$w%`)PB#3D<p<W06i_ zb90Wr+8z5koPgI3*8?c|FS9JRhHyLHSPJ_jN>(AU;*-IRqAj@@UZae_VZs|htKzvV z$vZ<+vlzV|l#?Q5^rn#~X3QoE!VS8b$EXO%*qzTE&}rgzo%u<*<H^2>U0Y<)L1RF0 zHDcAlCEibi<^Tn<;7sM-b%}4gGNfPA>3~e_dWV9;i4^F9IpIoFiNy!tgr)VS4&~#y zWpC~rL$DZaR)_c+&SwgVtHyNNp#B3MPlC4X>P(}*Yz{XDm++yJ|GTn(u)~q}g9c^7 zl8X}ecE<MjUoAjni3}TAt~7!5l7v!roKH-8oZV@OjAWE79=}E@S5IPmOJru3&B=Ny zrFMacM8i^!f&_y-3fM1?5p5co9%XbW`(5MUAiggrQ+MHBRt^;e2^>Qp`H+<n8J`Um zum}pV_86Np*`yjQ)*1D>3|#TFIluG-lGBM%wkyLX7l+zoGb0`$fgGD1<t5~NL4IF3 zow#1*ydb~>ij_yYMNhF(=cM8K_9&YzpMEb-`2Df_ka(h4JzqWL(|=zQ0{f5~0_#P! zL>m@$RiH1<kofcIX_$zz8`AEj1%*bX)xiX<?eb&{JN(qH;1aMSh+PGZqZ{+z9zGr` z(!l@}<jaDSIi=@RI#d*Js0<NcJ3ZFdkW=O;Nx%QVVqn)XU+<mn)?arCOizyWytcPk zF|?XJ=FR<y)#;w%ch>6P=vCi>FpDuuSqAQS#;RYdg7ckdh+P_8JEcdqSz=&cETMpX zfmbrNoh(3E+Iw-ZC=T<Rul`2sC&nIXehsKx*fZUuMPNQ!6@65^SG$qYwiPXoLa)P9 ztW7EY&!UCs*~3dGFq*)7JlwseRL=R7ScR$U@n&NHPrNUl@g+{6*)1^$Q}ABN44FQ< zJTj=>PxY?C&R57e6{o$45An$*)cBOl(<Va}Efd8M@4nJ`z+tmQ#J5<ByXaw05J#u0 zQWUY%b+a`>qFRDSsZqhe<Z((|N~RwWp8RFi%`n$w=Ykb)QtnO#-spV+huba(Fvtev z%B~@j@5!VvJ9#!?yi<TU?*RVB5dEFX%nI+1(l@Ku<z{QB?V-4;^~A(cEcPM7egSO= z0z`P`+7NB;HP@@%+R`8P8&=?6LtadIcy&xeODlT3Dv`>EAA~_0(%^a`pd;{86PMKu z&uAc_u!a{XgO!REep?}0tkJLR5$sE7lv=Q2Tkcwb7h8X^D-3*0ETikg(h2g|B$V#P zwRV_8+2;3i-@8-!OaITVpoi@W^c#Rq=r5(ETg?FFQJA3=CKk){aRi|>jZiKC%ElxH z5r4kPGzv8y7oRGUX_*YC`6Ze1Cnu_MBc{;#SZw6}?pZ7$)>v41jpNQ+a_OW`Yt$+$ z!<pg*X$xLAw5JENF95>h+rZc+1}_FXFj(Zxw_bFyD$xs4Lli;C00OLu+o@9!v~H_Y zwz|t|O=(EKDS|S+M^>Ky`JuDDEc-<ytGSW#v<;FP@x-4GUACUEm4%5ksSBN$^<F{& zQi@NoKvl<t_8^QGfGZLOoXXLRd1*n9rl=1TT_tdpnIHIJUel=6U;!zQ__oqMy#nLH z{!y6US&v3}#6sR1#5G6P%Yfl{X4M;ldk{S5b7nq&&^+$3ym604c>)zp;xBDvGCna1 z9WFMfczE_v_TMWLPV<f4NQmH{Z<*e!h(wbUIgT$Jc84a?<nzc;;0?ND+AFro=ac}F z{XCjH1WAPTCc)2V<~7aom*L;cb4pCVR~cHQ^EueePF;4V>tCC_UdN=<5s9PYwO!xk z)F$nHjc%uHI$nldIIu|~u1^atcGL8ay5DWIgwKXl%1gCpRZJF3DhXosqxXi(=~cYz zR4y(n&Bt3n245!75b<M4#T5?~chMNG7o*$H3x4>T_C_vIRmDRmVw=fs^;i5)G!`;D z?l$j(MrTID5sro%tL?N7NEAAAAW2llpJrMZJZ|S1Qy>U%3i$Q@<UnV|Tsy^XmwM)P zkVkS7#(PJK(z9cC`qAFe9r%dCf%!lGg+eO>{BdhpLrqbo;Xurr)RGChQf2zJf38^{ zgd(U9l=*1~tNnyl4Y-sOt3dcG2?$wu8Y;x<ixR=U0n#hCb4UblFFB6Z(gl>tOQDD+ zu2W7mAC!)f*<+=dIFT6?y_RAKxw5Y$6C&-)+|T#J!BIq27KNw?q%_^($zT@-b6Kl< zwbXdsJ#Cuu7MLD#Y&fGn2I1QZ3jrTV;Yg9d`5l#W=HS1AQxKbxU}+>7|0XqiSva<! z(&oBv`aw9YSSSQB9ETG<mRh+bp>k)q*xfo5@nKXq(+vZJ1j-pS9|o(_p8uhcd@;5j z!%SSrL+|nPfoP^;FTnj`|J^>+vF$zn3`{2_5ksl3lu_kzKkv-1*U>XR^~2U)=~I6f z;YNG=k-_11VIU2^Z@h?pD9XdtMykn3MXX)247df!`!^A(Q!yP{GV+KKgj}`XN7H>S zA(b(fPW1cG5rnl&5o<#F!*Iu`!hBIZ@$i9PlF?~*>T-KGE+VsH>c6)wArSf~+Gq^X z^zwFlTsN{*K%N~41PqqvzOT3d2^FhJy2KBpZuMP4!@m6^xXr`#Mu{j6+aEG%_Y7!5 zCqMQWScm#;tf>4sH;M6d%t!h#b@2oMP!&svv)twV1g$e%u&JTeldt{Zs7vN_dz6sd zd0&Mmim8R!SXyud0u|aM{#wQb?r)WF8@+qj+wS<lTHp7LVdd~D(Rwh`?ToADVafL` z^6*C|i`f9w?4GUtI8J+Vk2<sbeXl?JdgGiZRwYjOx|V#d%OBP^o<P_4M=Fh##^;`| z@&5v;u!kd3^sdS9QB&21y=<qr3|Q6Hi}I^kvdJ^7wEULwEC!({<}wLCzf^a+{n~h9 z;Hr?w<twKh2P?2X`Ag>Eu0n|E^?HKiEz(Jex{{3hQ^Y5Io?RSt?vzrD|DSs4uWNSM z>vRf~TJtS3(J@pDO^%=8Nag(H5hP=75%}*Ne#y)iU`R!P<0|t@dI}=h(qLOv8-_P~ zTWvmidNeVjktsAGa+^{3OK5wB=%81O#eBgNP$Xw><&kmT=Y#ar8oRZE5$EO0VM@&^ z2Na%k$$C_9WKMRQpKya~hqZUig2}u!r0sy!d?Hixb@BLz`%JPyB5;pq;xw70b_HX; zE>xuxoT)H%I15JCFC6rj_+#;mdu%<5{;83N&gFWd^>CUzv&5&W(jeRtuU_80OQdK< z&rXXq(tI{%!(*4;2bb<FZ1W;;$sX~CdFULb;=5mq6DduuJ%08&-Rtqi_Ub~B*GWJ7 z5AVhI4@n<NeGpfBv>&ls-Il0#m65%OU^aH@KSqcAS9tw(#h~2CP8JDY5O~h9V9+W^ z<Ri4a<wO9&5C3`h^IB|J;G$Ad+C%7E_fg0<6XLWT{n=h9q8se3WsxyWL<~o<oGkqk zKHZ*wjmvH+pOnJvj`JL(St0qDx5_lSRSxH>I?EujCp=Ay*aO?T><;}9i3q$d-KY7} z2Iqd*Uk`-8gtY^l@Dm?rR~R;2K7;<cQ5>iKI@b4JPL;0c{m)*2$dTqpN);yhV2lCD zcAi|N3n#@~_ptPf^?sr2(}O}s=Cch@cQ{^Vgcy&6JlL*+({Fcblpj95j*ect3jXf; z^@PR8lsQ6!;a1LswRa|Xz6q7vD^o}GtkigbVzPs9;Ua-T@9$q8?9RX;ewo^ftD=}Q zU=wXIm&fOJwwojPSX4+<Z)oX{T8&BItc}W*KA93;KUY}ES4#|vO7cM)#ow3HAlL>i ziNp2z@zeVSPhCpu_-;lQP}~FLzcMy6jDX(@H8v?@tyq1UBUa>_<t$&@w7srim_1rD zHs3W$wj_r5mI7HM$C0_!Kn?jXm*c8A3PT2qO``TdG~5z!K)x0mr;23}j%rW(Ln^jX zT&+OlURAU?Qu}$3^pSu6gyn4GHCYvzbiAD7t*-ao%h@XLFN1uu`N84BKb0;)5=5;6 zSuPks0V{f65%LA)12&|WUY10sp{6^Lf_;0U*uO}fX$6#b!A8-|NAiv#@WY^%UiLc_ zg?%&Y^StQ~l{a-Wj=0O+kp^58d2zZYye6hIS;TG*2N{aqAA@EstwfB_`z&K;@J3dW zu~RUUqK#GqK-o)9DxV>eFy{avm}_)b<s8V@ZPw|IVf;MVG;ncPmh)R4YeWGe8wL`- z(mr(9b$??G9`v&j9|=0SYn(eMS^V)PvD%{R>SXbqcv{fFhmPme7t;!!)M6o61$><Q z?Ch2cU+g!U6i&0G;>4hoML@P{H;5OKh5gfe5%VV2eJ|Ck0i}ZsbjK3OTo}iz1O6bE zipVnIVs1Z@my;~;TsMUy5C8{Mldn_<fF$G4rFpNQmy%bkQ`<tT$^i~md;uZ5L1z)? zRHeD>*hv~sJ&2uslPwh|GRI*%cj!`xx<|7)ME{UOu>kJn*J0kS@QG}eXWhJWCz@w3 z(&T*xU*HJJ_c6d0WiMYZ5aO}SY6DdeGR~B0InR?&(xF0U?;@?Ud?SBYYSVez|L>A$ zP~6xD7gTv-miEBegb(Eh5s1DRs<Bq0()UO+W{U}Oz16m=&oMJbfb0!PR-7!+?TR2K z{oy$Ksx&B@#{<NiGgw~JkiF?t9SN~`TB_0b6wDZrLp5w*g{Ezd{lo_}j6bH13$diM z^+?}nt}G)MAd;tWUiODuztsc+O`4LEdripK-f$Hh<M&?I3rZ3JasEcD*t0@@d-v=$ zgJF+DUvjnF)gJ+R!mkbXBhuwLp^;pUlBfI3Ei$=~M5luZ&h4%!ffWNvC0xoCH^a|F z&z3%bJ|?A4xxCE`iMe$tPAX2mR#IlJ!Ijwu(QV(Xqx$0>hd6R77Mn^QoA<3C+V8;^ z0~aPyS&R<dkCaN31QnwK3EoE4z*KK!rZcM1S%&}?co#^QqEV6cos;=+-g(jBaOjOr zxG75N>U|?f+X%pAFA-0+Yp(TbBM%{9UMM7}H6{RQ$5Fey=I2_X;R~zIlfNn#O1ZC< z0hr?H<|zCL0jYuH7NpD)WAs2=+f=Ew3lOePw|ujpQ7-7rlHtR)m<D9|YumAfoAb_T zlN(7O(_1PqypLkTFJBMR-+eN_#cUjVRqVv_fn&Wo<skp9Iv>4Bx(GHYlHYb6fHX8= zIkn|mjqZ-(phsotRUG1gFW=~E3y3serKV9XtFiNg{Qr)q$-+g1XRq;{qLcWo-yU!D zX8v_X34|F%ktTpe!J$^OH0`=;F+qkB^40Pgt##rbP}W)j-@WZ)d@cun3nwo7xN7=r zd>#jcvR~A+z|jzZL7k&8-L5$j((sbQa9dhB`CK=fVH$LLMU<#yk^zbA5AC4H2yo+8 zJY};SK?I&fvE8Ykv9`bktV_lyzTgmpdRFgrpnT6O^-Vg0kR%&S-M~{AUIWFG>b=+T zsws=f81lvbyy87wV(u}i_4%%P=12H+r-yq_P#++Gl;Hq{mF-F6)Ps;~`Igz&5bs-C zw8rbx@G9HYqK)I&LeLZHRS;rkty+SB>^ZL(n|ndAWtigo&qCu(_cz+$<DA0wt+6fU zcph6CzDuIU57lpuko+?7Z|oY&48|&N@WRaV3LVYZ)EM<Ue+f`|R$9#KxVNxKGy$+e zt=foe!Q<rR%aL`|pC?+3s#zq18kMzZ0UeQ-4MZ23c6k0u%rWGF%e0y-qLpT`u~}BM z7#d^oN&FAIGZi}l!I%%Z0X>bmD+F@)Ugx`<c-&qjRB$AQJ<c(h8a&FywvhgYqs|>l zt-Q43oLYzz_hM#`=T%DM4S;l#DMwdd+UGlMZihp7iqz}*G?=Q{wU1VsGnB?6Z&XZu zA^k3Ox^deaS0@|FDZ*J?!PJ=8<$`y0o8#q$U1mIAU*XGhaNjzu(Zq6*ZFNN=9?-fj zQu6)^sAq`Seq|?8*MLRD7@29c=DIU`vZ()X|5^S-f-jNDl*jI2u{`6n(dLMR&1w!F zsusn+<{#p6wERxokwW^C20LG?mPsO(Df{@rEY|4by~T9d%e$w@=iS<By>m`Vbg0Qp z&4Ad)?x*V#VRiE=^kl!gN*a7|FK3V{fCv`2R<;?fp{$VksQp-#N^;DonBGPSugOA2 zD4YHxa5uJrgap=J#fX2QzLq8gP#6mAuJy~hmbX`0&eS~JT9h?zK~&O5hfPfr0#*9_ z81&t?tST+58P;~79Q-d2(17gLVGqcV8IXT&fa9{bGyx5znmlFVR;KtX(Hv>)Otxmj zQH254{h4CbF{7$(H_3!I>Q}Y`3y3k~iE+H{4$4#|?IGCAJtosXzO`ddBRX0{^tyH{ zY|lH6YM-8MUKR0_TJ$*Y%>2$gS?*nZHyZnplxVTL-uGP?`XiDM2|$Ot<vfiPYs-@$ z3MQ7_Fgx)5Q6Z4<4^yp5Joyfd&iMe?sh}ik(knR80y|pWpHz~vU<G5$CS3(2UGh3i z<>kHHeziB7rrc{Zyb7*OJf~0`MszR=ENGb$cQHy0BF_ATl&`Z-#N7Oko8jK&(`{{V zbPli;hFY)rVLyAu`qa&U$9!E0{hgMaHT!6H;Za#z5~Iuyl5V+5R|CKN$nyrGaYo3y z!N{@YL-{arD*1-^sP3bM`ux~c_(qv1)S0RsKQxN>@3ga91C=;qvM@K!Zw|Z#rsy@B z6s|Uk)R_&Avuv$m<&vVYJmz;6>KtF(J$u!$-mi(_Sw>IFZzK7k(LS0`0+-!19l|3K z6{y5(F`8pok?E;rX@B|}Vk%Z(<g_~`1J#SXIXf~CC)RD^v5F-e-=D^>x;^eCV=*5l zFBl1gM1iz&DJZ!uOEyW*7BdLObXF?TtTyvK-_sDCug1-$n6kx8T{ZdTw5K5+92-In z)eAFwqwv#YBII4qaMeJ+ZAJX<%2k16!8eu_9&Zuv;S7JqG+pcJtGnihyAZy+rvNUF zGM)xvLn6ll<KLWNMyIzEH=g4pgBwq1V<-(Euny$m^3M78dX}|6$6BkIYulf&Kbg`5 z5zgBEW+A56bMlMh;Ca>mx}jSzVQ38lzvMT%gP+Jd`^mFJhWgVaUt7ZY@BKjAREr%$ z-tRtDW$3wzB`NhuhzR}l?5j5X#6OKUuMRhRW%+pu16p%Jee50IPERE<o8zpy@8o*| zSj1$cNLeW=I5$*o8pUcX%fC#2cZ*g;wQ;~&%Wc+UJb{I@j%->4)dvWaR?ntLT}Ro) z&x<IP3;d4BKa{aAp1Sx&l@||q<;PCNv1@@=obb#*Xyg4}>_<$~Wos~CFUE|+%tLE& zD3<BqWPQNzc4*n81j^jB5EaY!D<Hh;yOH%ft537s_ht)US8y~k#gsc}Du3?9+@@7K zG*s90$ddsQMD9iX7XQW^qbH*9zRye&<4Br>n@&Xg_kG^$rM_u<9qkxN%8nom71YOk z*N2ZwyYM;DyLmJ4;<&7CA#16tF6n}guU+;wG(0;L9kDjD;49tdVZ|#LOs5oP6IJE% zXU=4tSF3PW!XIB9HyN!RzsaQldBu7pWOI9@lETTON>QG^t7oEEv)5cXRkPYU6C{qv zBVm?!)!3<U-=78FJ;U&{OMS4~R*Gjf=7LoH8ZmH~^H&G4X|WGH9X*v?`FKC62RR1l zd9Wf`R@|gZ6H-f@Y6+yMy|6i(L$wN%9FJUy)jsA=;=%ynGm$?hgKXl;*(0OjWxi;K zRn-SF<q%4*Dg*na$BK{tZ6tiwkQhumeNZqd)$rm<wQNTn03M$02B*1-5)oPsDkzaK z!HV`OaMWe;o|gG?JI#qmrylJhvdZG74$Nq=d+?I=0|5Zu-7{qQx_mO5fDomx*i06X zx3q35kv*g2wJBNH!^BR&;7%`T7!Rg_i7T5OiIH2*ut+{hMC}L`!+*U-wPW$Q8)|w} zW4q$!Ppj;3{TlDn#b~y2L6Za-cK#`o-I!Wm@<owH@ms!<#LfXEj*K=|Q?cS0TZZLK z&(3Mpky-K@&%hyjW|SX_ts(8g1r#x*a{VjiUng8*Y2=X=bESu<tlVlP?tXTh>TL-O zA^Baq3WgO$ioB|2cZ4Lk*egWUXGf*aCH4dhrG(i=6%g8awAR||Qezkx4JH&^t2E&` zQ+37nFs3WKbeahH4X_yb80-P)6GHDKG)3}o@lTgWyb15R@?K$aAW*A#Z6DpwU)?KF zc>|GuTQNys`7%J_pUNaws)U^$o%5s_D$La!ajy?(`jCo;|JhR1>shsx90{t<Imfd* zS(gC^%v;!;tb#Q_%qi!;S}lf-gei5!P$jh1s1%#^ylxiTLUNiL&GpvJCs@%a?w;0l z?KB0cNym1da$P@)jD+Cv!(_>Zd?v|@1N7t^6f~R`>~>z}--GYF4_$u|n(Z2h!he0W zZ(I1qNR*#+n4;1B#twb&-g6LDdHR1apMMXFGNZvv3%&X(d*iUeCQrkbL#6c3J`LHi zteRH%yp)pgzW@&*bGYR42B-MA(Q=}pQei^7r<GoVL!7gzTh)HS##tu8R_0JnYRM*~ z-15e_NAvrLnNsN|xVsz5E+o!`g)D628^e?9)%?*Zkj8etyYN*ij^5po-r!3(gNdGQ z_w4ThnZ2)Y#lv>%w73VYnhR>m<^vw9FIRztK_vFb`CeX#t!85|87Y5iCI1OEpV|3| zy-(s&(i0<6y=p&faSvDz2_*Sf7<5;*?rnABE>~-Dx7*ee@1>I%%~y9VooiAsS6xQH z+^i3iCA?Gh7a&j8tJU1DdXBovd=@nnm(@mUn-3uM+vC<55W1QLT({ueWNzo!l_cOZ zUp;DC<jhagX`I=FX)0HwJ@HFdJPxP=SHQe0p(Ku<jqkZQsi09ZRTk4Z-IrjPO{)4{ z?`zHv5*$)%Sh7eY8ePnivOiYnFnl3`i;5ZF+Eh0Xh-3%D#UTAfn$7b0SP+@s+c%sW zSG3;Fp08NJmkh1f;pC&=BolKjMCI^2M%3}TGAyja9QN9ppOuXuf_sn0k2TCU%<(V6 z?AuKQ<9C)@j<cQ$Ag4d)Tm6c{_VrkjNGoy42szNcwF8D%*OnVEjjKMmuZ%^@Hn9n< z^^b#;WJ>Es$wIAs_|<;2N~g2!Qnm8)X+|s8lhsZz^y09G`oS^^A&O;%tI8tJ%l)3m zX@4$skbiOSn+U0O+xt{5Mvdh_^RnKNj+01Q&9|)8b9~P0ds{Chte<BWd4u>BV(Yss zZ~o*mq$Czo1yL<o<j)v{^F$U?zLVm(Pv_g|W?A(+-;`-o;tF`hkIk-f1ORk+YtIPD zm3%;rB&5oYL?Hz;Ya~#KjMHP4vdVJy4`X2Gs4b_$pKRyUd&xeCdYwt6kR%zpt_-RL z(8!}X-c~9_y;Cl8QNrBF`qTuJiWqM^S#^Jt12ujp(9gu|1CCoDH_TFbBhEfmkGx+3 zK7_};+g3gGU@jDx2gS(fYQuJt8i>UaUgFOYDj-Q%|Ml@o*8IN$moqN7DBJ5CanX?# zJj*}$KZI?uk(hS6TGgsdpLXg{g-VJJPTQ|jla$y`bzc>;-)pR0HKfI&uNA!~QYaZ# z+1Typ=QWRnw0k+8`)SJ$TmSn3;qYN6@Z)dn!xaa{N((7=mW!V5VvdU4K7aCsp1}o1 zw+DwJe3B*lg4eR*h{dO89`18XNy-08vNwU18B848u0*aEF0`*ios?uQ5r1M@y~+>G zcOb>BLP8@CxfUmlHcGOLs`h-i068R#24Qv~(JO9>fLjN0H<s-^^5Vp1vwN?rHRxE$ ztQ>Y%bn3#jlw%U$mZ^}Kn&Jz$kX1a!Zr3+}RGt$Bir}Fu+@Zit(T|M7ga;BFY@f%^ zStBxA(_U`?%3r)A=;6)<3mW-4hgLcPiCla8Jluyf6*EgH17g?hEE$FqOa7Yy=xl}3 zKRwBi<DhtS+11sU5L^nuk~MINc9zi@N_e#5(Og;?PQSRu_t4t$AV?Je{S<;14D!n` zJqTvExk}^*iT*kx<7l<h@}K9F)@Gd5{!V-~nT5w|uRn9D-ZR`bvg%T9rOoYnJ76-L ze{r*Pa4}SynnG9*iWSZ!K~#Y-mMVyMZZpVV5ot>xQ$Uj|l?p9~vkNCV`T)q}f3yy8 ztN`PbC(ZhELNm?Rxi{-P2T?DOf82l)EOGV)l-K?)O0UG>_hFyTQ-y<~--OoFGCK?V z*}G|_2Zw&H(>-<xsEI8v*Kf}0c2-kSZcED87kE%fGbFkJH&07G?%AB2XehH=hG7v6 z`>)V7sO2vTy{>lb4MNsCGCXP{=_1``oP%7cozmx{X9leyTS8)=;Y6-aJ}F^>`N!f) z<J6Nkr+O5!2vJH)Zg_cbwVB?90<I$~c||v+1?YEUMX+^c!ajN)Uotjboi~uIn*qDr zBm|jPH%o7Gn7OIEh@J3^X9a=Tv~%%lh3jiP%gO<=K0hiVzi+fIf!OV3sox?Bu14C0 z3;Ev*@V~ob43WW9EHvWFp#27I2rvWJLJ9aPFrlav6KTFA=_DY*Wio<7_mAAmE?bEf zYF~*(gz&7PtLXN0aIX(`7UG~)Rx_`Kwlf+~P~xsO-`XL8%;SJ_wvq&|D=o=N=j{<; z&MN545b<LWBZ0B@kQ~nXyq(^gx?ii|lAi_mqK^9WB$<6L)BPz7Gtl%#kd6JYk*40+ z!b0?5taVarmWqBSYsSOx-tZsh{Mx?pjH%29Q;ySpe%RVIlz4&&pY9p}gQJd&!-5PZ zv0*CYNCtw-hDfKur4A8BvdJV=DO}eOWSS|Vej%mvRR32CP%h|3Z7%X58ygz0-t3iW z=Mlj}$dQ1TAH(HH1*}o>oCA`m{fgOc;(>wm&B@`y&mpFrp!W@_|Djf46V@tL{Dj$* zJVKIrK-`1TRE#$0HVJ_wuI1NV{n7Q#U|6Qn3tBkNCI(&KjD%;NE1{FNOTpRzDVF$; z+17<I+eGJpjrF}%+nj-s$w1s#^*6!bRcG_2Ln_8m@dDJCQ$IbU<=f<B{ZJ9t!pV;f zl=`=ohwBd>g|mEWL?TEhKI|<b^tyZlDz>i;{R#JX4n035pwZV0Nr%d3B2jpT_PPrb zW93H$MhzMxAu$p73^s@#g*7yC!}f*>44K;l{_IG+ypV6S6yd(1Q8xKTml6*8@x1SQ z=e73PnCiG2ec!{-e@7Q?U;V=;khyl9iKFqx8hyebRDMSJ$QsJ5Rm#EXCjgp9pe;~d zDh##!+KzqYTcWUr!%?abDKKbJtWZlL7KW9P8@%<A(^(p`><4ZUn{fyW!`$GDC>F(1 zFEp}u+yK;IupU5eI8%A9O?O@3P^0(f11y_;m;$=+`1<14$sm8JRCD1Ec_?$nB&Tb- zW8vG)v7f~SmIHjI#qU=Nlu-K;D@`7|c1DtmYn*M5gBN<omf}9Z8!5j>`EJ8FA_$|` zU~}m{0s*hFAAHNOn`M9dNbc%`?O!FadaGB%SDQZs!i`a@2JC;@=brc2Cu_LhUNU|` z#tjq={zjECKrc|UJ;v00>P0xVJ9-R`!V0;JkVqI=H(_vL%*{sXSP<h@CpSR^Z+8bu z?FJ*uxs%C|pntU+dhW1quJ_zSq3QW*m(XOD=?c!;Go7k#Vw?TB$vnB=S1;#{tA4+3 z*qJN|!V@-y$|3qK>(BnY+Ph2-Pn7iidc5ABXflyO`#zbgnDciB=Wu7GMxc4+H{ZwC zX~3%mhYLDX(#IUuBpcl+FTwB%(dG71Q-aw^u|j@p9?4HqJ8bxehX-G~W0{}fu$%d8 z=(-Sq(Y2j{Xt{G?KF5Zepz|JXGEf!GG8t|iUQn5cW<e{uH2Gp-AXuWx_NwXWWSi|v zM>Z)-k3xz8hpid3oeAqPly{yaAP!yN^R?rdBU9jM*6-06KWgj#ZTn7$eh>%q_yi6q za#+m9lMNBWBdOl-(~|oe$poijA!hqdbcjq8gvie=8o}eY_3S-1U{mv^GS?8*CJ|h` zU4E-ceWMe9fEz@8Tgmf!&in8YuiJUYH`>`4zLFI#)j$x4hi23tA0iTndbLUey**i{ z6$q+UNE551qEsnD3>iV&tue2);vC#l)s^vka$-d6d3})?4_Y$P$&a;c=E?jd6yibM zN!%Wnc_V|Y-@Cf6@P{z_#f|ys5rS#X+(=Ohc8*g4JPFK$tj|8IT#sKaeLnr0?g3`y zcl2l`me|Z5ewF4bCK$PR|Mh91fAwigs-^0x(|R_RAoO17-oq6c%a<yIWU0Y-rHI*h zU{|f`qwhbPdcCb`XTAC{9D6K$DsmL%-%V7toP18UQ1FRSMJ}lqn5N_ZbA_Dhn(v;2 zB1f7a9N}oiomFMwP^-ZO8w8^Hfme_r{apd%VXIVB8&*R4Velait5ul!WGS@)m@cAl zMQ_v>pCG_Vktz|3H~nZJ3fQo9jhibi?o)YOHS%AP22%Y~DFS46&ZJ?jYYfzH*A>HO zmhUIAwx9`bj6c00l<~2oYd`B*M-%9{1F8gV8HMs+1yB_Gbvl^(&{0N?_Zd5_Hj^$i zsX_r<bFmrN8oFZ>-vx>SD>j}?x?2F+(Q{j5&4h?n=2|w1<)vb7zg&BvO1>Y+i&zFk z0l+x7jtxOT6{;N}rRCqmXoc*!g1Q1~-Gk>TA~XsjHb|gNY#qRz5D9&va3mV`Hn!kb zR3EeZgi<QM4_9w6?>!*Wd7HYg_-00Hj<tpxL~*-c2pxMc7|cLYW7S|2Jx+mA;&_)t zCgn4T_bcH^#YzVVP#i+)FzMIjwK&~#uDFkjxuU&JE~lH3noP|OOrS8ls(s&k8)^F* zpX<}x;u+WC{g11+6UX@H;$QwZE?KVPiN}4PZKSv{kP`Ok$%*;5+%#K+)tcjV+VR3R zPO%u?X0qUQY~x3)k^i!TYTp1~kBkUXoswa+3cHMgSB+J2OCctf9*OcTA>0Pl>j62w zcbYh8ojW&-pXB<*iLhSJF0eJ@JJjTV?BlV0zb`jC6i3BOt63e+#Wtuo60)$0p_-gh zFlqwmXF|*`FA0m4k|N2wC|RwP2rXwSVEmFtYgDMk^wPJN@TMtjA<66~q8A}s(HXHM z&IC*McQ?Rvi3ptBX}9I&vI7k(fcaM>551_nqC*k|f%D=Ae2-&7O=S~1-J*@asxUCh zsH_A`tdqy%GdjTP$+8tr>sPja|NQk8byu#5nH!T~5;_=^hi9%r|Ft31+P~8en_7g? zgD<a`@m}pMAYfqV3}A@yV~1#wn8D~wp_~wG4+3HkbA)Cw(}3~#*~<=4v&Fb;Oy0b2 zgmRMbyY@J&@tZ2VoT%gq1q-3%{P(cf=clKFb2YZ}tu$pT6w;li*yWjg%;3|p4tG21 zP2M<O%{gk-g4mlApzwC}6tkJ24#c&baa3Af@0)_%2K9ZF>B3Lb?6RK|Y;2z4vJa&T ztK#uGY1z&yCWT0V#+dJz)w}(bYCTV5yx!{VxpDEx$k)La8kF*#5-<`@3WG9VAg9ta z-z{4ibM5ra(2bBUWLI*T?*B)$NL9j5XW3W+C$Dho!aS=<nnWHVG9C}j_N~bVWA=zJ z7*8c+__PZN4UUwsY8-rRIy=SR@p|>-i7qokaiKb{ZFVg!#1VGIVe9Wq#ZG_M@q$S} zqJ5gjK@`cy-+F-3WX?;=;eEnUCER|UKGjMyAPIWj$Y)E%3M{t=zM*(;(LtBLbPOzp zdMi2oYCr8N%W7$5P}Or}6aA7n%fuhkoDU?iVuFz{;ZeAZ7C(-^tl1CpVv<oCE>OrM z1i@OP2lBNZ<No-JDdPUv>JSR+Yak0`+uqZUjPCz%<i7bsK4K+3%e5dSliidY>xj{a zTtoB4B(-@LyWBym?aMQx@&sBHXj0_I-&D%xe}Gu!@yk@R2QV3q3WL0FA)dkD9P!Jd zSLK;#xW5sYUm)Y<G>3^vrS!<rY_jFq)}r|B<x%X71T?1B^+Xo$;TYj9omR%}mPu3# zgN?vqp1WFC^Mk{m424FKf?A1_Z!x47IpBg&LN$UQY3}!nBjX}KMQp6ZC@^YfC|1rk zC$LK!&&?`s)<K{GwK9DNoA?Pk*!60ZNA`zlUU>?&A1d4(1A}=UBV%)+DzJ9cf?4Y* ztOcrp=hjgii$$Wi5*f`bSH3^24+w^DQId0IQw4z-ICFWp*_wW=Sd*ISs{OC(@p5Bt zde!^k2HW%cWeC?jrS8I9JcE9Srl|Zz7Pjm-H@?mIdZPJ=I%uhensw<Vm<sIeD|=_9 zh|E@MOZeT^&WdywK3+BGA&71d2?M2u;k58~M*A?M(X3+|FpvV!9)Ridym{FQq6=85 zVTuzj$1$DnV&xy6NBwu?g#C5o<TvOq7T4c|+C{cLVFWP$W3ja7nTQfsYo0gf_4&Bm zMhH|f5AAvSc@#oOo32ym=VetB{npRmt*Efsv3bZ6C3pbsosDd_dkH4hUBs4sr-f30 zQ3n!N-m*|;5_)T^R|HJ(g<zyPlLDKb{d%8oh3SGKs?aL}u1}>+*Ie87y2@6o@lAmH z7qdLw=uLb^=oPOs%@Ug#1+rGID0%<U#$}RSn2?dNh5b!k9A7$R=dvqw?1P~5mF&3X zKO@<_J?r)M@L{=<O#d=_1s0^|WbbcrU~`Z0<Q=bil<_!hP=IWOz|gI-AePu#tF|e< z@ULY#+%q{~r&YR(=jpfzs+%*z*aTh`i6qmB238OPSxA5vm-v>`OqNQ2+j4KV@{0|# zIk?x<Nio%(3X5q6CN$Pu1*2lu5Jx}*BqH^E7zZ0Vgf-R7jtY@j`WdXmwl$XRUg6#e z6rxKjTD3e2i9x8=;_=%us6!+NTCSp<UO<y2pW!0kaCfe0b<rQCN8FVl30B_v#NO=Y zQ2L8=LJssj>^9rd`$^pko6GJu5^JJech39a_WA&l=~+x<HxIGWo}YA)^;+$Xdj@MU zk7_$L4be%@5J>k27;Sh~fE4u^#rqV6Fd1hE+m)MD^v8z<&u3!Fb1|6DB)?IX^mTzb zK19krG0)8_uSf$WG^;Fxo8dlZrW8Zo00BNtcLG`Yhu{<D0@9R~&X7~J4n<3zN&0vl z*WyJ}T{{mQhEF7po5ZkMsYf|AL-pW3L8dpApMHHX87A@Vd=o*Hq!6lT4s(4eB!`6l zOUn5m{MUNQCUN+iv2NEud!}c(tE|CDVbFQXWvQ8VYx0#??j>M!$N$Nc`~J(eRYU+t ztO^6&q7CuxYV$u@{Ll`kJkHWCC}1hl#RQproj_?32|A#7pc#rHm)R!2Kl~%SCjRN2 zqOpjw<dKo>xrKTxyWrYOb=iK!vY>9@xL1d!Nu%(4RahO1?R|O@%o@74Nh!{Bwm;p! zw<oIVUU5y<Bmw{a3o@bG=@tNoTQ89me|+4sOXbh#4u6r%RrR^S@~rg*ea+S}g{fGK z$(iuS@nwTu>53e-64i;2LU|pS2+pWJLB{BP5;nURQ_lOu^e-)YCq^>3neNr^u{h1} zjQUdw>zkDlBMX>~%%$TQHQPxhbt)Y@;?|73bZnGV@jx(A#dD^n+t`oWbjjS5zyGA{ zKP25qqSh(N*p|FsfmYA(W-mbCXpficr^Yjlb+hY&b3v>-$njC7lIgL{AGwuDh-``y zSS(caRwf8@f8e)TPzu|Q_QQ9bz-Bi>fVj3-n*O0_VzC@Sfq77Nrv?i&zbJ->6jSUA zKfU-0d3z;!9MzL%*QXY=I!xWfakG_xw|Z&OxA`YeEj{U2HFG^&xO(O!=-W|}=Mc4Z zdr$Ai6H-cruLQ7rk&xF7uX3AZ$RYuvvcFI(^Orb9E=|uCmTXww8rzFW{X4|sfAsBX zVn8rz=EvgU1t9j1gWm^bfP*TY!+E`?46fy^7yR*OCm<$|EgLD3Szg`RJwDWwc@YAE ziSL>d7hlzbb@34)bt|e`znRAt6)0lcfpH_YTE#HAKa!UkD3V^jYrE^s?ll48r6`u@ zcLq<%Ov&|I{d7X#;A>V}cAf8~uGAkihr1~<fOf7snw=f&k}nqO<pL9q0Grf*OREut z0YuLuYrR36n%3DZ4V=tG*4%DpWo*ejjvD#oMAqW>r=$LP2n6|5(QGm}-I~%M<Vgq! zxp-9Kj*Ox$AVu2sg2SHox6YEw!J9FVUtkzLk@doy4tsC1BiE@!c8T?%cka~25Xkm< zCt=8{>v0Eg`6)dmkC4>RiIHv~V?JV;NdyIW<z<>t95$M9mBoCR(;m0EEq{V^i5BM% zhof?Xi!A6_IjMNq-QwbhM-g>cO^Fbf5{*i9yE|qn*cnD*_=<ac0$o-|ZOc_KCAL(n zF$m-lQO3%7T^|XJ@fKgwnG1gj!IsN$K=MeU&ds@zc*fktdkY{_+vC-D-Ea6t@GfFt zL3#VNWY}Grqlu6-ah?4YXNw2v{7sT^!BsC+i^%P;vz-YQ)D3>^^7qh^$hQ(BA*XSx zhX9cmro!wUE>n1-fT}T)Emb(D8AqqB@CTn%47oc+5e(3<-5xUq?IA+Bu}sG^!mNt` zXn~=mP=L~Cj4`sfvcxbMG;+L9dH7UXKPE-5T|2BiDl_jbolZfY@{#-pvT-*m)si$N zS6jY>2{cOajCd{KSNMFWXSZt}AzxFjR{C}ltp~(Ct^M46;jf4WrGZ#(C|BF96D{?$ z2lwam6W3peL1XD(1@cOIK^`#f@j89RG>f{yKoVC^<H!E?kT<|zM8{<8sbZ;N&h24B z9|d_|q?}~;-y&v%*=;_5O69iJ*Q$5wmzf1f1MfF&Rui>?VZkLpAP)bKXRao&bLho% z=buau%rGKL+tBA80{1H^P~RF?aZs3Ey5TbOOxP-+21vtem69;iMSg%;G&DR_%loAR znL%{Q;{uh^$N8t(xzG;Z#`<cQAWnXMRea);zmyn0S%z||`Elrk*PjT3R%wnrO?T<a zv-W9sK6*8Gx}o}RVwAAei0&i4GLUdC^s%Gu)dWyh&;&XV8g=JDSj;S$b7fTuynp(K zwVdOpW`Y=vgl935E{k5Cl3)V^1K8gX;;zhJ>TcbIr?J&S+KJ!Uwq6Ro`Dq5ML4Nt8 zbAySrx+OH7q4?N8okkf}V7hKLvy)kUR-h&eT%58+`{}&s1<<xPjyq$gZX!_z6};{j z>CjP?9^IYs!ce}(x<Zve#!dw@Fi#UEja``XZ#{;|;V-j|QnB}cuUX#CIN}r#O#TV% z1qm0i1s_ZHg6UtSfm9YZIjXXMs%yuK7lzNs`~0G+JUbOMrTh@U$Tw!fP1BDkdLz;w z+Xai3PJ~w5Tj{>}9oK0AtymJDy$bWp_1Sis?qUk)_pvy9?;n4m?D}eMX{U7!B)=6w zivRh@TGjz*Y%qinvBL566~TH=zqGX(jvvW~@v%Jh!If~7&(F?;ixb94C#-Oc@ZfY` z&x%=laTl%UL@^#EvU$FR*oys&#FciT|F#teFQ5yifVc`=5zn-k9_hpqqJ`+`gB3u7 zr}bD1=A|f|HcQ-&OyOX*8Iq&XXpk<Q1uz)xDhHCaCCF96V)rVuYJ@dWpwt16E(&|} zPi4Gae;agoH*gv!-h+=eqy9uQYDf78l43J8jD{8u!}+RSzG3!CD1qfbfA-ti-%q|w z)+2_0kSChBi|@2QrV~tQ_#bvG@d&_KCa4}K{neLQ9vJMFU?M%qKr-jR^Jh<O&s9Sk zYH#kJeJA#=V{>hZe1^}qgLZiO1Hb4a_A?n!Lo%1XPvHrD8k+D<T)I8{;kg(xZc)rH zN8ZAFFdo&X-F5mnwx<QA=E({)h-Kb~(VZT^QPP3E91KGm??u{+0-5dRw`Z+~yy?0g z9V#UY43qdX08~l&*E;PCOcGRJXM>|!@{>A_UhEsq_puv)xlc&U#D11->8(E}j;_ty z0>$DSVd`8uEkTTRHw>i)rF~zRVQBy(xbo>r3ugUcCvK@ZMy}Q)1Ul_Hpdgpp9AWTY zpjjP^6IUgC9{q1cJrxJ5sDtjJDP$u!ce*ZjN}K0cj-U1ub;IQyoiCvQTT>FRC(Wb& zT{Cic&FVb?(Cwo~e@>=ZS!x9eBclLF!*8Q4AmPjaeR7HxXAp^ehF=8h<?ggShtn+r zEVEEl4Al^5q5|h??Xe9IDkFS4VB%*NpT`dLpyl?KVbfgh8#d^YFLZi5zfTShPM8(S zj=yjMvQ?pWR9543SC?Q?F~~3R!1tCuAs134CIpTJh{&%QHa~;nm9xni`=Es<u!Z5v z%p&=Ck~&jDAzTGMy@9Ux&Mt?hHs+43Sf(Ief)%Z52`!$-kc(Ve$y>ceKd|A^;rKK? zF+EH9f0#yu&s%_W4humur9Xd>`T5X7%)SLCRO*9z0jE-q1`C19VK+$T!C5J|{9fqE z3%!9|(_dIoT%<8Rw-MjCl&OH-j0*A=G`t*rFm-55J~M;+fCrD;X?O1O?sQoONa2#S zLkDfO4;$DQ6}Dv{HJ~)z_XnHQa$kQUyL_&Wfyc%CMcur1lSR6Myo@=w)2-B4k+N=~ zON`c<BnE@os@(it$bO3-c4nYLDm#3-&W_97nid3Z5o~5OPG-LyWpE(8|LZ^~5mNR5 zaV7~zO&1IGQ@{gY<of(tR-m9yq7u897BJsGocf;^FUR~ZvWZ~u<0_t7g$wr6S9XhX zVffr^faXQKil#k9$lCT(g0oXfwW-pB^_9SLzZtq5P(|D#L$)G(2iA@L?D^o~v@y5s z`JAf!;@()k0*TbOJ`g;QS7zAXYH$=HE*tumqUEe-8Lleb-AE#7Ypj+VOorY?$LEZO z4oKLU(ml*o%f4pG(^Yj=!lcm(0XbkMo0y(0`}17s>Z6@F*H6U#keGk{kt~iB^qE}j z4tQJ~pas5HVA7OLV^EUXP}C9%7MBGC0E<~tR}=j^5p0$?+Mbj};VodyTCi-7>hO5J zX|x!yU6%hJgBd%VN&+LpJ#b_Qo+r5ycLmf`{d0)@?WhG~WRXBdpbc6$2KC!8LS7$L z9ar^2s-8$6EBHC3k<bu)u*X|PfH^Y0#eOOcHDI;<yXmMg%pYBMS1Nap7J{EicGOXB zR!jfwW+iW(Ot!1?Vpn}su0{@S{`~2TFA}pa>c<BOik2PgEb)MIupT*JVsot@T)v<X zN_U$7Qa4wmSWgaS24)t%_CI?A)U}U*guA$Kv}Y|CMtg(Dl>U#Rv4+9*9QQyD(z?%k zGgonpMf6#4M{W{a-N5^krWp==IeMKHP%akv7=%-_rJN`)qg_FG2nqpMm91^bnNL#% zAmjrQl@;HLXLp?VK#w(5XNV8FTi1C{e0-R|HHJ>{4d`>nvPoUbKB_gC&@H%8`<Hn6 zA~NffqqyywzlCqOPaspMhz8f~4#tMhI1-QNH9D9f`pddmdvv#8E{<@n8Mm}4LBgK! zxfumf*EA5TU>zf8R+51u04PU>sorz`M}nft0k*GmDe!L_(SgqO0nB%~FwY%9GoAP} zT*9#l@fOH`C^Z_8Q+QmL$=NKoMPev*Vje+>@(^nB(ZTun9v-4c1pGZuTeAuRNKv4H z%uz)O)j9^+iAS%}%=xs#z+_^Cz6;=9&C|-xvEmgCMsMxIR&=;H$$gAwberDEX9Q{4 zDaFndON6$h0EgQ&^S;x`%#{R3aZ|O`I3DPzM!}zqk8m3(4vc+6KZP~%Gv#9br)vb4 z?IqqNn7bZHD-Nc#crVI00|x^Ga7ngxWeFW1)H&?<1KoWMQg@`Z9mpCQZWn|91!<AV z)*H#T0&n7PD;T+>D2cBGG|6-<>&f}vY$@*WlHi?Op^@YKU4x@ql?U1NIWGE{E%GBf zg=h`)g(Ai}$Gt4_w?iA!@koe-+=(HNN}I`FVD*#1;lf;wD@(K8XN2CjNMw?+qieQ+ zZ(SPl<qg2+t}5|DKbKirXmr2ikR|R;b(>k><&Ge6wd2*Ml=^p}34r7}v=B}Pi%UwN zb{~>)1m@PC#*<DeiAYZ!UUQY|++jR<j&Y%8m;<)&<^pX^;vKPFbejkVaN~=*_?QqN zMF^mSZ<2)UM1->6{v9|u48TyOI{C>wc?{uauZQMki-ixG9{>2zvHj2vF&8T{R+<6x zyX1dnG!deWIdJhAw}z-F#FRgW)A24wg<cE7Pry!?YsQ?fgNQMAS*T6C56N_Rjc!iD zD`IDkXX!tz;meH%JHT952mPpmB!%SY|JB!@qAndty9Js?G&|jKJx8b6KPu`^U@g$^ zUb@Grkb}s<^SYn46^xtDjdiP+e|^0#*y@M$82{59R3H`@>y_TfgW8fv2yEP-2!WxH zG&Drj<&e3!p1b^o^I>s9y}KD@w|diWrE>W=1IafU<qe3E<DLE|8$`liUOS#Cq8YUU zvyGRPDA+j~j387RW<cDDWnGahokJ>8yL#t#F<kAPN+bxsW~g2(YxE+irC76C?uQK& zW+RC!W6b;tVuY6<7duRTt^d{YIUp+GJCVP%WU|z6`$_UHILrAm0x+Tkd8Av38i7u$ zAs{NdXXR+6%SvDy6s}+p`x|s5KR@t1LZ7~W20kDx0q{q_q2#x+AP6OM*n<vg%zCPC z<xPp%5Syje=hK5+MYNNf6YD14aM>Q^iFtm2yu|Z6lP`CM7S@;Mz5=iS0%>e~1Z>?Q z<H(T1CBo|A(U#evJ&;|X>PoDl6EZ$ZrCQ)4Px^IxOGup_fy;Tz_Y9Y9YdIQX`vGsN z`Gvw>>i_vnpzzW8fU`!gTl=Y1RMJ&?rW;65REpIy<k32AiRCKYER%R#XNn8|AAA4d zj`jck55SQ^$O?(<5t2R1y6l;~vx@8)C1hoU?7gzd9vPLaC@XtrCnQ^0MRcF9>fO7( z_xpF;f581Yjt<AwbzQIL^&IE(+~?z*<bBg!IVn{cg}Q*{sKIh3_VP&(y@YUi$;G3t zbV<=@2J^VCPfEAQbR(hosk>%_hWs;rQzONO5%yCaD^z(hJFI7+AY$FC^n4;GF{cD8 zCl`>~RZ8JxWpsXeAT@Ker_WzN0ey4db^6;H5FCR7nXb|o)I#56-$7ZGT4nrEb}2yP z`_<mICSv?2)OHN}3zYowcwHYHDX`fy(tUBqPj=SHR5lB;q{q*wDUhLEzqBe+y*ylK z^0u!@VD~n;)b(o!pDt-p*)Jz3{U;Y-QNBqNR+GePzzeb4OgV5$pOIH^vRC;u)a`dR zh;VIYd9=t6z`@SV7t|(;`#mt7rIWqU$c&S|<XBzlTDERERGzkf%!nb8b57vOohmF? z96OO*tvEVA4J_`Yc-})b3@XukL6dGcv%70TwfQ-6X_V<}Z~dV*MqEeilo&mYmRATV z+i^EqJr%`7PTJGdlYRi~W27R``P*%aTymyv3@7ey`zsCzkDh0msO|`xY!DHGm|Y!> zzKPiIXC?=LFGU)S)Ly0*x?{UKRewB~xY1mUb6e0BVd4DSl!+K}C}v<6SA5kbxiRi1 z306Yfsmg9tUZ4n+s~fLBOV=Kp7|)dHb{9Naz@D-`O+CJ%!zjzt^--q#G%3QpPxvV$ z$Telbmg(`9yS!oZ?a;_Gh3<Z}*z(oH1@6sx>8SLE;}!Q_vre6V;j-=n5QWR@ZKr?V zCWd#A<d#I!8<14_;l|KMCn{MMK>A<`r#^3nvzptWw>1Lkg=MmjJ$65aFojXP#lfn_ z69Q%zAcDaQ%mGCYvqx&5qA~8r31;Vw5_dsD>W#x%ExAXmH{12V?@!07SsZw<x3O~0 z<IRWLatZulrJ_oS{Q(b~Nts{)0e*_3a!ogo1zgN<k&SDr_t=XqWFB>xRE@h$lF02Q zj6?=sq(2g}ovb=>Ug-8<7|JLzj_l?_fFA$9;uPAe#F&3$d|WY;EgPX)=yJl~+1-BA zh}o8tnMj}7YPBvh-<xlAvH+8eN_rs24L?PoR2-N)HslN_0I&w_>+YAJ@}L8eupYa+ zYK@H!2}Nt;H4+DRN^T#F7dX3qyR(*@=T*j};19VA*^Zg$$@6}++g}%7aC*<)=q^+w z<p^}&v9^gP6NlqzmcIUw_}$Lvd$c?c1`}GuWIUr(o0Zg*2XXyOQ=RV5r(X&>-7r0b zyh?%FtW%zAZ^fhc-gv2FBZc4gdr28N6y!p`>lW`KheV+C5R95^>)X8)9f*0yUwkb9 z!6w0o*L4lrT5Od48lpujuaK6^d9>MrUyS3ECVdeROSYDlXMsuJAYzG#;TQ&+Q=l#2 z&G?h|oFocT_8B$q!Ge9P1el1M@pApj+1Q#zYT%|$9Vp?~dm<3$`Q0CzczNCC@o7la z!(`Pi4F}id@Y<c9&aHb>hi`rVRS66r;LarFob`(ZwDnKBE!x>mKO>Fy?a52`iH+dU zs}(Lac)nawsrfl7MAVjn;4~<lr!a4a%cC>A!-qLSX$dtOk;!|d&8$xUq<0m$=HPGG z8Lg|xUSKd0fSp_=b=qH{q0v%RNGN!>bMvZk4pah(0JH4M_NznHet(CCAm%{xSMfdn zhoIix@v1oiW$B|+35Zhr>t9nG(2E2wGc?M4_+)|A?dmFkG;n<AN^YI=b7@7yNB*@& zu5c=m^@_z5hX&vC6Z~}clh3a3X{2I=A|`^VpgIl+`%N(YPKla^n^2)cUaY7#2!}Kl zK7BtB4kJPI^)zTg$i6<fFevMQ0p0u%vwr_XzRAmPiF~G~_6`j{b~Jb$23~dgvZ>bi ztmdcIwN=C+^oIe*2CT;4Q91uSt!Cyo=CeHRf;V0T5lR$w2@OFRUw@vRtnJLF=d#R3 zcV|x&8^0j38qDJ=i>aF21}0;Mpu`ZB<H5cn&@sbuKEXWa#H|Rx>v&S`Oa(h|M>`7l zE6V5~Rd+gFXtQ6QBtkSgwT2sf-!@w!ysSzz3xs7NC_m^J7XcDkF6M~C+xY|!Koffz zc)N&p*!<5z2`ofQzn^vbJmhnSOndHotf@U!zKZ*CM)rZsLCEf!0M?uMvh7nQ-=3*m z*pnweK+jqv6o<=4NO<xbDC-%%0DpoPdEtf->?OAFJScdnw0ycL$X<qX)YGG}0mRSE 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 V+kE4IeggiHxuGakC}HIPe*jkA_O$>2 literal 0 HcmV?d00001 diff --git a/doc/unittestscenario/resources/ScoreAcceptanceScn.json b/doc/unittestscenario/resources/ScoreAcceptanceScn.json index cdd80e7..81546bb 100644 --- a/doc/unittestscenario/resources/ScoreAcceptanceScn.json +++ b/doc/unittestscenario/resources/ScoreAcceptanceScn.json @@ -69,6 +69,14 @@ "variables": { "score": 67 } + }, + { + "type": "USERTASK", + "taskId": "CallApplicant", + "processId": "ScoreAcceptance", + "variables": { + "phoneNumber": "(+1) 542 778 2352" + } } ], "verifications": { diff --git a/doc/unittestscenario/resources/UnittestAutomator.yaml b/doc/unittestscenario/resources/UnittestAutomator.yaml new file mode 100644 index 0000000..ec6b9bf --- /dev/null +++ b/doc/unittestscenario/resources/UnittestAutomator.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: process-execution-automator + labels: + app: process-execution-automator +spec: + selector: + matchLabels: + app: process-execution-automator + replicas: 1 + template: + metadata: + labels: + app: process-execution-automator + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8088" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: process-execution-automator + image: pycamunda/camunda-hub:process-execution-automator-1.8.7 + imagePullPolicy: Always + env: + - name: JAVA_TOOL_OPTIONS + value: >- + -Dautomator.servers.camunda8.zeebeGatewayAddress=camunda-zeebe-gateway:26500 + -Dautomator.servers.camunda8.operateUserName=demo + -Dautomator.servers.camunda8.operateUserPassword=demo + -Dautomator.servers.camunda8.operateUrl=http://camunda-operate:80 + -Dautomator.servers.camunda8.taskListUrl=http://camunda-tasklist:80 + -Dautomator.servers.camunda8.workerExecutionThreads=2000 + -Dautomator.startup.scenarioPath=/scenarii + -Dautomator.startup.contentAtStartup=file:/ScoreAcceptanceScn.json + -Dautomator.startup.logLevel=MAIN + resources: + limits: + cpu: "1" + memory: 2Gi + requests: + cpu: "1" + memory: 1Gi + volumeMounts: + - name: scenarii + mountPath: ScoreAcceptanceScn.json + subPath: ScoreAcceptanceScn.json + readOnly: true + volumes: + - name: scenarii + configMap: + name: scoreacceptancescn + +# Upload your scenario in this config map +# kubectl create configmap ScoreAcceptanceScn --from-file=ScoreAcceptanceScn.json -n camunda +--- +apiVersion: v1 +kind: Service +metadata: + name: process-execution-automator +spec: + selector: + app: process-execution-automator + ports: + - protocol: TCP + port: 8381 + targetPort: 8381 + type: ClusterIP diff --git a/pom.xml b/pom.xml index 3a78b70..4764ae2 100644 --- a/pom.xml +++ b/pom.xml @@ -18,9 +18,19 @@ <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> + <!-- 8.5 : OK + <version.spring-boot-starter-camunda>8.5.7</version.zeebe> + <version.zeebe-client>8.5.5</version.zeebe-client> + + 8.5.11 : ? +--> <version.zeebe>8.5.7</version.zeebe> + + <!-- 8.6.6 works --> <version.zeebe-client>8.5.5</version.zeebe-client> + + <camunda7.version>7.19.0</camunda7.version> <junit.jupiter.version>5.9.1</junit.jupiter.version> @@ -55,23 +65,65 @@ <dependencies> <!-- Camunda operate client is included --> + <!-- + 8.5 : OK + <version.spring-boot-starter-camunda>8.5.7</version.zeebe> + <version.zeebe-client>8.5.5</version.zeebe-client> + + 8.5.11 : ? + <dependency> <groupId>io.camunda.spring</groupId> <artifactId>spring-boot-starter-camunda</artifactId> - <version>${version.zeebe}</version> + <version>8.5.7</version> </dependency> <dependency> <groupId>io.camunda</groupId> <artifactId>zeebe-client-java</artifactId> - <version>${version.zeebe-client}</version> + <version>8.5.7</version> + </dependency> +--> + + + <dependency> + <groupId>io.camunda</groupId> + <artifactId>spring-boot-starter-camunda-sdk</artifactId> + <version>8.6.5</version> + </dependency> + + <!-- https://github.com/camunda-community-hub/camunda-operate-client-java --> + <dependency> + <groupId>io.camunda.spring</groupId> + <artifactId>java-client-operate</artifactId> + <version>8.6.2</version> </dependency> + + <!-- 1.6.1, --> + <!-- 8.5.3.5 https://mvnrepository.com/artifact/io.camunda/camunda-tasklist-client-java + 8.5.3.5 : Ok with spring-boot-starter-camunda + 8.6.6 : incompatible types: io.camunda.common.auth.Authentication cannot be converted to io.camunda.tasklist.auth.Authentication + 8.5.10 Bug fix on ZeebeUserTask (actually, no) + 8.6.6 last version available--> + + <dependency> + <groupId>io.camunda</groupId> + <artifactId>camunda-tasklist-client-java</artifactId> + <version>8.6.6</version> + </dependency> + + <!-- Camunda 7 --> <dependency> <groupId>org.camunda.bpm</groupId> <artifactId>camunda-external-task-client</artifactId> <version>${camunda7.version}</version> </dependency> + <dependency> + <groupId>org.camunda.community</groupId> + <artifactId>camunda-engine-rest-client-openapi-java</artifactId> + <version>7.18.0</version> + </dependency> <!-- Web --> <dependency> @@ -91,22 +143,12 @@ <version>2.10.1</version> </dependency> - <dependency> - <groupId>org.camunda.community</groupId> - <artifactId>camunda-engine-rest-client-openapi-java</artifactId> - <version>7.18.0</version> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> </dependency> - <!-- 1.6.1, --> - <!-- 8.5.3.4 https://mvnrepository.com/artifact/io.camunda/camunda-tasklist-client-java reference io.camunda - .tasklist.auth, not exist --> - <dependency> - <groupId>io.camunda</groupId> - <artifactId>camunda-tasklist-client-java</artifactId> - <version>8.5.3.5</version> - </dependency> diff --git a/src/main/java/org/camunda/automator/AutomatorCLI.java b/src/main/java/org/camunda/automator/AutomatorCLI.java index 93665bc..39d4d34 100644 --- a/src/main/java/org/camunda/automator/AutomatorCLI.java +++ b/src/main/java/org/camunda/automator/AutomatorCLI.java @@ -16,7 +16,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Component; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/main/java/org/camunda/automator/AutomatorRest.java b/src/main/java/org/camunda/automator/AutomatorRest.java index ce9c260..5a7003d 100644 --- a/src/main/java/org/camunda/automator/AutomatorRest.java +++ b/src/main/java/org/camunda/automator/AutomatorRest.java @@ -29,10 +29,11 @@ public class AutomatorRest { public static final String JSON_STATUS = "status"; public static final String JSON_GENERAL_STATUS = "generalStatus"; public static final String JSON_NAME = "name"; + public static final String JSON_PROCESSINSTANCESID = "processInstancesId"; public static final String JSON_DETAIL = "detail"; public static final String JSON_MESSAGE = "message"; public static final String JSON_INFO = "info"; - private static final Logger logger = LoggerFactory.getLogger(ContentManager.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(AutomatorRest.class.getName()); private final ConfigurationStartup configurationStartup; private final ContentManager contentManager; private final AutomatorAPI automatorAPI; @@ -48,6 +49,7 @@ public AutomatorRest(ConfigurationStartup configurationStartup, ContentManager c @PostMapping(value = "/api/unittest/run", produces = "application/json") public Map<String, Object> runUnitTest(@RequestParam(name = "name") String scenarioName, @RequestParam(name = "server", required = false) String serverName, @RequestParam(name = "wait", required = false) Boolean wait) { + logger.info("AutomatorRest: runUnitTest scenario[{}] Wait? {}", scenarioName, wait != null && wait); Map<String, Object> resultMap = new HashMap<>(); resultMap.put("senarioName", scenarioName); @@ -98,6 +100,7 @@ public List<Map<String, Object>> getListUnitTest() { * Start a test */ private void startTest(String scenarioName, String serverName, String unitTestId) { + Map<String, Object> resultMap = new HashMap<>(); cacheExecution.put(unitTestId, resultMap); @@ -109,8 +112,7 @@ private void startTest(String scenarioName, String serverName, String unitTestId resultMap.put(JSON_SERVER_NAME, runParameters.getServerName()); resultMap.put(JSON_SCENARIO_NAME, scenarioName); - logger.info( - "Unit Test parameters serverName[{}] ", + logger.info("AutomatorRest: Start Test scenario[{}] unitTestId[{}] serverName[{}] ", scenarioName, unitTestId, runParameters.getServerName()); // now proceed the scenario @@ -120,7 +122,7 @@ private void startTest(String scenarioName, String serverName, String unitTestId try { scenario = automatorAPI.loadFromFile(scenarioFile); } catch (Exception e) { - logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.toAbsolutePath().toString(), + logger.error("Error during accessing InputStream from File [{}]: {}", scenarioFile.toAbsolutePath(), e.getMessage()); } if (scenario == null) { @@ -171,6 +173,7 @@ private Map<String, Object> resultToJson(RunResult runResult) { } Map<String, Object> recordResult = new HashMap<>(); listVerificationsJson.add(recordResult); + recordResult.put(JSON_PROCESSINSTANCESID, String.join(", ", runResultUnit.getListProcessInstancesId())); recordResult.put(JSON_NAME, runResultUnit.getScnExecution().getName()); recordResult.put(JSON_STATUS, runResultUnit.isSuccess() ? StatusTest.SUCCESS : StatusTest.FAIL); recordResult.put(JSON_DETAIL, runResultUnit.getListVerifications().stream() diff --git a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java index f2be874..b0490a2 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java +++ b/src/main/java/org/camunda/automator/bpmnengine/BpmnEngine.java @@ -128,7 +128,7 @@ RegisteredTask registerServiceTask(String workerId, * @return list of taskId * @throws AutomatorException in case of error */ - List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + List<String> activateServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException; /** diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java index 7f4cb16..2f12df7 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda7/BpmnEngineCamunda7.java @@ -287,7 +287,7 @@ public RegisteredTask registerServiceTask(String workerId, * @throws AutomatorException any error during search */ @Override - public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + public List<String> activateServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException { if (logDebug) { logger.info("BpmnEngine7.searchForActivity: Process[{}] taskName[{}]", processInstanceId, serviceTaskId); diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java index 4084d77..9240704 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java @@ -1,34 +1,19 @@ package org.camunda.automator.bpmnengine.camunda8; -import io.camunda.common.auth.*; -import io.camunda.common.auth.identity.IdentityConfig; -import io.camunda.common.auth.identity.IdentityContainer; -import io.camunda.common.json.SdkObjectMapper; -import io.camunda.identity.sdk.Identity; -import io.camunda.identity.sdk.IdentityConfiguration; -import io.camunda.operate.CamundaOperateClient; -import io.camunda.operate.CamundaOperateClientBuilder; -import io.camunda.operate.exception.OperateException; -import io.camunda.operate.model.*; -import io.camunda.operate.search.*; -import io.camunda.tasklist.CamundaTaskListClient; -import io.camunda.tasklist.CamundaTaskListClientBuilder; + import io.camunda.tasklist.dto.Variable; -import io.camunda.tasklist.dto.*; -import io.camunda.tasklist.exception.TaskListException; import io.camunda.zeebe.client.ZeebeClient; import io.camunda.zeebe.client.ZeebeClientBuilder; -import io.camunda.zeebe.client.api.command.FinalCommandStep; -import io.camunda.zeebe.client.api.response.*; +import io.camunda.zeebe.client.api.response.DeploymentEvent; +import io.camunda.zeebe.client.api.response.ProcessInstanceEvent; +import io.camunda.zeebe.client.api.response.Topology; import io.camunda.zeebe.client.api.worker.JobHandler; import io.camunda.zeebe.client.api.worker.JobWorkerBuilderStep1; import io.camunda.zeebe.client.impl.oauth.OAuthCredentialsProvider; import io.camunda.zeebe.client.impl.oauth.OAuthCredentialsProviderBuilder; import org.camunda.automator.bpmnengine.BpmnEngine; -import org.camunda.automator.bpmnengine.camunda8.refactoring.RefactoredCommandWrapper; import org.camunda.automator.configuration.BpmnEngineList; import org.camunda.automator.definition.ScenarioDeployment; -import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.engine.AutomatorException; import org.camunda.automator.engine.flow.FixedBackoffSupplier; import org.slf4j.Logger; @@ -36,18 +21,14 @@ import java.io.File; import java.net.URI; -import java.net.URL; import java.time.Duration; import java.util.*; -import java.util.stream.Collectors; public class BpmnEngineCamunda8 implements BpmnEngine { public static final String THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME = "ThisIsACompleteImpossibleVariableName"; - public static final int SEARCH_MAX_SIZE = 100; public static final String SAAS_AUTHENTICATE_URL = "https://login.cloud.camunda.io/oauth/token"; private final Logger logger = LoggerFactory.getLogger(BpmnEngineCamunda8.class); - private final BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy; boolean hightFlowMode = false; /** * It is not possible to search user task for a specific processInstance. So, to realize this, a marker is created in each process instance. Retrieving the user task, @@ -55,15 +36,16 @@ public class BpmnEngineCamunda8 implements BpmnEngine { */ Map<String, Long> cacheProcessInstanceMarker = new HashMap<>(); Random random = new Random(System.currentTimeMillis()); - private BpmnEngineList.BpmnServerDefinition serverDefinition; + private final BpmnEngineList.BpmnServerDefinition serverDefinition; private ZeebeClient zeebeClient; - private CamundaOperateClient operateClient; - private CamundaTaskListClient taskClient; - // Default - private BpmnEngineList.CamundaEngine typeCamundaEngine = BpmnEngineList.CamundaEngine.CAMUNDA_8; + private final TaskListClient taskListClient; + private final OperateClient operateClient; + - private BpmnEngineCamunda8(BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy) { - this.exceptionHandlingStrategy = exceptionHandlingStrategy; + private BpmnEngineCamunda8(BpmnEngineList.BpmnServerDefinition serverDefinition, BenchmarkStartPiExceptionHandlingStrategy exceptionHandlingStrategy) { + this.serverDefinition = serverDefinition; + this.taskListClient = new TaskListClient(this); + this.operateClient = new OperateClient(this); } /** @@ -75,10 +57,7 @@ private BpmnEngineCamunda8(BenchmarkStartPiExceptionHandlingStrategy exceptionHa public static BpmnEngineCamunda8 getFromServerDefinition(BpmnEngineList.BpmnServerDefinition serverDefinition, BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy, boolean logDebug) { - BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); - bpmnEngineCamunda8.serverDefinition = serverDefinition; - return bpmnEngineCamunda8; - + return new BpmnEngineCamunda8(serverDefinition, benchmarkStartPiExceptionHandlingStrategy); } /** @@ -100,25 +79,21 @@ public static BpmnEngineCamunda8 getFromCamunda8(String zeebeSelfGatewayAddress, String operateUserPassword, String tasklistUrl, BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { - BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); - bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; - bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - bpmnEngineCamunda8.serverDefinition.zeebeGatewayAddress = zeebeSelfGatewayAddress; - bpmnEngineCamunda8.serverDefinition.zeebeGrpcAddress = zeebeGrpcAddress; - bpmnEngineCamunda8.serverDefinition.zeebeRestAddress = zeebeRestAddress; - bpmnEngineCamunda8.serverDefinition.zeebePlainText = zeebePlainText; - - + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + serverDefinition.zeebeGatewayAddress = zeebeSelfGatewayAddress; + serverDefinition.zeebeGrpcAddress = zeebeGrpcAddress; + serverDefinition.zeebeRestAddress = zeebeRestAddress; + serverDefinition.zeebePlainText = zeebePlainText; /* * Connection to Operate */ - bpmnEngineCamunda8.serverDefinition.operateUserName = operateUserName; - bpmnEngineCamunda8.serverDefinition.operateUserPassword = operateUserPassword; - bpmnEngineCamunda8.serverDefinition.operateUrl = operateUrl; - bpmnEngineCamunda8.serverDefinition.taskListUrl = tasklistUrl; - return bpmnEngineCamunda8; + serverDefinition.operateUserName = operateUserName; + serverDefinition.operateUserPassword = operateUserPassword; + serverDefinition.operateUrl = operateUrl; + serverDefinition.taskListUrl = tasklistUrl; + return new BpmnEngineCamunda8(serverDefinition, benchmarkStartPiExceptionHandlingStrategy); } /** @@ -144,29 +119,29 @@ public static BpmnEngineCamunda8 getFromCamunda8SaaS(String zeebeSaasCloudRegion String operateUserPassword, String tasklistUrl, BenchmarkStartPiExceptionHandlingStrategy benchmarkStartPiExceptionHandlingStrategy) { - BpmnEngineCamunda8 bpmnEngineCamunda8 = new BpmnEngineCamunda8(benchmarkStartPiExceptionHandlingStrategy); - bpmnEngineCamunda8.serverDefinition = new BpmnEngineList.BpmnServerDefinition(); - bpmnEngineCamunda8.serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + + BpmnEngineList.BpmnServerDefinition serverDefinition = new BpmnEngineList.BpmnServerDefinition(); + serverDefinition.serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; /* * SaaS Zeebe */ - bpmnEngineCamunda8.serverDefinition.zeebeSaasRegion = zeebeSaasCloudRegion; - bpmnEngineCamunda8.serverDefinition.zeebeSaasClusterId = zeebeSaasCloudClusterId; - bpmnEngineCamunda8.serverDefinition.zeebeClientId = zeebeSaasCloudClientId; - bpmnEngineCamunda8.serverDefinition.zeebeClientSecret = zeebeSaasClientSecret; - bpmnEngineCamunda8.serverDefinition.authenticationUrl = zeebeSaasAuthenticationUrl; - bpmnEngineCamunda8.serverDefinition.zeebeAudience = zeebeSaasAudience; + serverDefinition.zeebeSaasRegion = zeebeSaasCloudRegion; + serverDefinition.zeebeSaasClusterId = zeebeSaasCloudClusterId; + serverDefinition.zeebeClientId = zeebeSaasCloudClientId; + serverDefinition.zeebeClientSecret = zeebeSaasClientSecret; + serverDefinition.authenticationUrl = zeebeSaasAuthenticationUrl; + serverDefinition.zeebeAudience = zeebeSaasAudience; /* * Connection to Operate */ - bpmnEngineCamunda8.serverDefinition.operateUserName = operateUserName; - bpmnEngineCamunda8.serverDefinition.operateUserPassword = operateUserPassword; - bpmnEngineCamunda8.serverDefinition.operateUrl = operateUrl; - bpmnEngineCamunda8.serverDefinition.taskListUrl = tasklistUrl; - return bpmnEngineCamunda8; + serverDefinition.operateUserName = operateUserName; + serverDefinition.operateUserPassword = operateUserPassword; + serverDefinition.operateUrl = operateUrl; + serverDefinition.taskListUrl = tasklistUrl; + return new BpmnEngineCamunda8(serverDefinition, benchmarkStartPiExceptionHandlingStrategy); } @Override @@ -175,13 +150,11 @@ public void init() { } public void connection() throws AutomatorException { - - this.typeCamundaEngine = this.serverDefinition.serverType; StringBuilder analysis = new StringBuilder(); try { connectZeebe(analysis); - connectOperate(analysis); - connectTaskList(analysis); + operateClient.connectOperate(analysis); + taskListClient.connectTaskList(analysis); logger.info("Zeebe: OK, Operate: OK, TaskList:OK {}", analysis); } catch (AutomatorException e) { @@ -218,27 +191,26 @@ public void turnHighFlowMode(boolean highFlowMode) { this.hightFlowMode = highFlowMode; } + public boolean isHightFlowMode() { + return hightFlowMode; + } + @Override public String createProcessInstance(String processId, String starterEventId, Map<String, Object> variables) throws AutomatorException { try { String marker = null; if (!hightFlowMode) { - marker = getUniqueMarker(processId, starterEventId); + marker = getUniqueMarker(processId); variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); } - FinalCommandStep createCommand = zeebeClient.newCreateInstanceCommand() + ProcessInstanceEvent processInstanceEvent = zeebeClient.newCreateInstanceCommand() .bpmnProcessId(processId) .latestVersion() - .variables(variables); - RefactoredCommandWrapper command = new RefactoredCommandWrapper(createCommand, - System.currentTimeMillis() + 5 * 60 * 1000, - // 5 minutes - "CreatePi" + processId, exceptionHandlingStrategy); - - ProcessInstanceEvent workflowInstanceEvent = (ProcessInstanceEvent) command.executeSync(); - Long processInstanceId = workflowInstanceEvent.getProcessInstanceKey(); + .variables(variables) + .send().join(); + Long processInstanceId = processInstanceEvent.getProcessInstanceKey(); if (!hightFlowMode) { cacheProcessInstanceMarker.put(marker, processInstanceId); } @@ -253,7 +225,7 @@ public String createProcessInstanceDirect(String processId, String starterEventI try { String marker = null; if (!hightFlowMode) { - marker = getUniqueMarker(processId, starterEventId); + marker = getUniqueMarker(processId); variables.put(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME, marker); } @@ -295,76 +267,12 @@ public void endProcessInstance(String processInstanceId, boolean cleanAll) throw @Override public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) throws AutomatorException { - try { - // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one - Long processInstanceIdLong = Long.valueOf(processInstanceId); - - TaskSearch taskSearch = new TaskSearch(); - taskSearch.setState(TaskState.CREATED); - taskSearch.setAssigned(Boolean.FALSE); - taskSearch.setWithVariables(true); - taskSearch.setPagination(new Pagination().setPageSize(maxResult)); - - TaskList tasksList = taskClient.getTasks(taskSearch); - boolean getAllTasks = tasksList.size() < maxResult; - List<String> listTasksResult = new ArrayList<>(); - do { - if (!hightFlowMode) { - // We check that the task is the one expected - listTasksResult.addAll(tasksList.getItems().stream().filter(t -> { - List<Variable> listVariables = t.getVariables(); - Optional<Variable> markerTask = listVariables.stream() - .filter(v -> v.getName().equals(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME)) - .findFirst(); - if (markerTask.isEmpty()) - return false; - Long processInstanceIdTask = cacheProcessInstanceMarker.get(markerTask.get().getValue()); - return (processInstanceIdLong.equals(processInstanceIdTask)); - }).map(Task::getId) // Task to ID - .toList()); - } else { - listTasksResult.addAll(tasksList.getItems().stream() - .map(Task::getId) // Task to ID - .toList()); - } - - if (tasksList.size() > 0 && !getAllTasks) - tasksList = taskClient.after(tasksList); - } while (tasksList.size() > 0 && !getAllTasks); - - return listTasksResult; - - } catch (TaskListException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); - } + return taskListClient.searchUserTasksByProcessInstance(processInstanceId, userTaskId, maxResult); } @Override public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { - try { - // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one - - TaskSearch taskSearch = new TaskSearch(); - taskSearch.setState(TaskState.CREATED); - taskSearch.setAssigned(Boolean.FALSE); - taskSearch.setWithVariables(true); - taskSearch.setPagination(new Pagination().setPageSize(maxResult)); - - TaskList tasksList = taskClient.getTasks(taskSearch); - List<String> listTasksResult = new ArrayList<>(); - do { - listTasksResult.addAll(tasksList.getItems().stream().map(Task::getId) // Task to ID - .toList()); - - if (tasksList.size() > 0) - tasksList = taskClient.after(tasksList); - } while (tasksList.size() > 0); - - return listTasksResult; - - } catch (TaskListException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); - } + return taskListClient.searchUserTasks(userTaskId, maxResult); } /* ******************************************************************** */ @@ -414,57 +322,13 @@ public RegisteredTask registerServiceTask(String workerId, @Override public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) throws AutomatorException { - try { - taskClient.claim(userTaskId, serverDefinition.operateUserName); - taskClient.completeTask(userTaskId, variables); - } catch (TaskListException e) { - throw new AutomatorException("Can't execute task [" + userTaskId + "]"); - } catch (Exception e) { - throw new AutomatorException("Can't execute task [" + userTaskId + "]"); - } + taskListClient.executeUserTask(userTaskId, userId, variables); } @Override - public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + public List<String> activateServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - long processInstanceIdLong = Long.parseLong(processInstanceId); - - ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder() - .parentKey(processInstanceIdLong) - .build(); - - SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter).size(100).build(); - - List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); - Set<Long> setProcessInstances = listProcessInstances.stream() - .map(ProcessInstance::getKey) - .collect(Collectors.toSet()); - setProcessInstances.add(processInstanceIdLong); - - ActivateJobsResponse jobsResponse = zeebeClient.newActivateJobsCommand() - .jobType(topic) - .maxJobsToActivate(10000) - .workerName(Thread.currentThread().getName()) - .send() - .join(); - List<String> listJobsId = new ArrayList<>(); - - for (ActivatedJob job : jobsResponse.getJobs()) { - if (setProcessInstances.contains(job.getProcessInstanceKey())) - listJobsId.add(String.valueOf(job.getKey())); - else { - zeebeClient.newFailCommand(job.getKey()).retries(2).send().join(); - } - } - return listJobsId; - - } catch (Exception e) { - throw new AutomatorException("Can't search service task topic[" + topic + "] : " + e.getMessage()); - } + return operateClient.activateServiceTasks(processInstanceId, serviceTaskId, topic, maxResult); } @@ -486,13 +350,23 @@ public void executeServiceTask(String serviceTaskId, String workerId, Map<String } } + /** + * ThrowBpmsServiceTask + * + * @param serviceTaskId taskId + * @param workerId workerId + * @param errorCode code to throw + * @param errorMessage Message to throw + * @param variables Variable + * @throws AutomatorException if the message can't be throw + */ public void throwBpmnServiceTask(String serviceTaskId, String workerId, String errorCode, String errorMessage, Map<String, Object> variables) throws AutomatorException { try { - zeebeClient.newThrowErrorCommand(Long.valueOf(serviceTaskId)) + zeebeClient.newThrowErrorCommand(Long.parseLong(serviceTaskId)) .errorCode(errorCode) .errorMessage(errorMessage) .variables(variables) @@ -513,120 +387,19 @@ public void throwBpmnServiceTask(String serviceTaskId, @Override public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String filterTaskId, int maxResult) throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee - FlowNodeInstanceFilter flownodeFilter = FlowNodeInstanceFilter.builder() - .processInstanceKey(Long.valueOf(processInstanceId)) - .build(); - - SearchQuery flowNodeQuery = new SearchQuery.Builder().filter(flownodeFilter).size(maxResult).build(); - List<FlowNodeInstance> flowNodes = operateClient.searchFlowNodeInstances(flowNodeQuery); - return flowNodes.stream() - .filter(t -> filterTaskId == null || filterTaskId.equals(t.getFlowNodeId())) // Filter by name - .map(t -> { - TaskDescription taskDescription = new TaskDescription(); - taskDescription.taskId = t.getFlowNodeId(); - taskDescription.processInstanceId = String.valueOf(t.getProcessInstanceKey()); - taskDescription.startDate = t.getStartDate(); - taskDescription.endDate = t.getEndDate(); - taskDescription.type = getTaskType(t.getType()); // to implement - taskDescription.isCompleted = FlowNodeInstanceState.COMPLETED.equals(t.getState()); // to implement - return taskDescription; - }).toList(); - - } catch (OperateException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); - } + return operateClient.searchTasksByProcessInstanceId(processInstanceId, filterTaskId, maxResult); } public List<ProcessDescription> searchProcessInstanceByVariable(String processId, Map<String, Object> filterVariables, int maxResult) throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee - ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder().bpmnProcessId(processId).build(); - - SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter) - .size(maxResult) - .build(); - List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); - - List<ProcessDescription> listProcessInstanceFind = new ArrayList<>(); - // now, we have to filter based on variableName/value - - for (ProcessInstance processInstance : listProcessInstances) { - Map<String, Object> processVariables = getVariables(processInstance.getKey().toString()); - List<Map.Entry<String, Object>> entriesNotFiltered = filterVariables.entrySet() - .stream() - .filter( - t -> processVariables.containsKey(t.getKey()) && processVariables.get(t.getKey()).equals(t.getValue())) - .toList(); - - if (entriesNotFiltered.isEmpty()) { - - ProcessDescription processDescription = new ProcessDescription(); - processDescription.processInstanceId = processInstance.getKey().toString(); - - listProcessInstanceFind.add(processDescription); - } - } - return listProcessInstanceFind; - } catch (OperateException e) { - throw new AutomatorException("Can't search users task " + e.getMessage()); - } + return operateClient.searchProcessInstanceByVariable(processId, filterVariables, maxResult); } - private ScenarioStep.Step getTaskType(String taskTypeC8) { - if (taskTypeC8.equals("SERVICE_TASK")) - return ScenarioStep.Step.SERVICETASK; - else if (taskTypeC8.equals("USER_TASK")) - return ScenarioStep.Step.USERTASK; - else if (taskTypeC8.equals("START_EVENT")) - return ScenarioStep.Step.STARTEVENT; - else if (taskTypeC8.equals("END_EVENT")) - return ScenarioStep.Step.ENDEVENT; - else if (taskTypeC8.equals("EXCLUSIVE_GATEWAY")) - return ScenarioStep.Step.EXCLUSIVEGATEWAY; - else if (taskTypeC8.equals("PARALLEL_GATEWAY")) - return ScenarioStep.Step.PARALLELGATEWAY; - else if (taskTypeC8.equals("TASK")) - return ScenarioStep.Step.TASK; - else if (taskTypeC8.equals("SCRIPT_TASK")) - return ScenarioStep.Step.SCRIPTTASK; - - return null; - } @Override public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { - try { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee - VariableFilter variableFilter = VariableFilter.builder() - .processInstanceKey(Long.valueOf(processInstanceId)) - .build(); - - SearchQuery variableQuery = new SearchQuery.Builder().filter(variableFilter).build(); - List<io.camunda.operate.model.Variable> listVariables = operateClient.searchVariables(variableQuery); - - Map<String, Object> variables = new HashMap<>(); - listVariables.forEach(t -> variables.put(t.getName(), t.getValue())); - - return variables; - } catch (OperateException e) { - throw new AutomatorException("Can't search variables task " + e.getMessage()); - } + return operateClient.getVariables(processInstanceId); } /* ******************************************************************** */ @@ -636,69 +409,12 @@ public Map<String, Object> getVariables(String processInstanceId) throws Automat /* ******************************************************************** */ public long countNumberOfProcessInstancesCreated(String processId, Date startDate, Date endDate) throws AutomatorException { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); - try { - int cumul = 0; - SearchResult<ProcessInstance> searchResult = null; - queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId).build()); - queryBuilder.sort(new Sort("key", SortOrder.ASC)); - int maxLoop = 0; - do { - maxLoop++; - if (searchResult != null && !searchResult.getItems().isEmpty()) { - queryBuilder.searchAfter(searchResult.getSortValues()); - } - SearchQuery searchQuery = queryBuilder.build(); - searchQuery.setSize(SEARCH_MAX_SIZE); - searchResult = operateClient.searchProcessInstanceResults(searchQuery); - - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate)).count(); - - } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - } catch (Exception e) { - throw new AutomatorException("Search countNumberProcessInstanceCreated " + e.getMessage()); - } + return operateClient.countNumberOfProcessInstancesCreated(processId, startDate, endDate); } public long countNumberOfProcessInstancesEnded(String processId, Date startDate, Date endDate) throws AutomatorException { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); - try { - int cumul = 0; - SearchResult<ProcessInstance> searchResult = null; - - queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId) - // .startDate(startDate) - // .endDate(endDate) - .state(ProcessInstanceState.COMPLETED).build()); - - queryBuilder.sort(new Sort("key", SortOrder.ASC)); - int maxLoop = 0; - do { - maxLoop++; - if (searchResult != null && !searchResult.getItems().isEmpty()) { - queryBuilder.searchAfter(searchResult.getSortValues()); - } - SearchQuery searchQuery = queryBuilder.build(); - searchQuery.setSize(SEARCH_MAX_SIZE); - searchResult = operateClient.searchProcessInstanceResults(searchQuery); - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().after(startDate)).count(); - - } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - - } catch (Exception e) { - throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); - } + return operateClient.countNumberOfProcessInstancesEnded(processId, startDate, endDate); } /* ******************************************************************** */ /* */ @@ -707,33 +423,7 @@ public long countNumberOfProcessInstancesEnded(String processId, Date startDate, /* ******************************************************************** */ public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { - if (operateClient == null) { - throw new AutomatorException("No Operate connection was provided"); - } - - try { - - int cumul = 0; - SearchResult<FlowNodeInstance> searchResult = null; - int maxLoop = 0; - do { - maxLoop++; - - SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); - queryBuilder = queryBuilder.filter(FlowNodeInstanceFilter.builder().flowNodeId(taskId).build()); - queryBuilder.sort(new Sort("key", SortOrder.ASC)); - if (searchResult != null && !searchResult.getItems().isEmpty()) { - queryBuilder.searchAfter(searchResult.getSortValues()); - } - SearchQuery searchQuery = queryBuilder.build(); - searchQuery.setSize(SEARCH_MAX_SIZE); - searchResult = operateClient.searchFlowNodeInstanceResults(searchQuery); - cumul += (long) searchResult.getItems().size(); - } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); - return cumul; - } catch (Exception e) { - throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); - } + return operateClient.countNumberOfTasks(processId, taskId); } @@ -759,13 +449,13 @@ public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) thr @Override public BpmnEngineList.CamundaEngine getTypeCamundaEngine() { - return typeCamundaEngine; + return serverDefinition.serverType; } @Override public String getSignature() { - String signature = typeCamundaEngine.toString() + " "; - if (typeCamundaEngine.equals(BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS)) + String signature = serverDefinition.serverType.toString() + " "; + if (serverDefinition.serverType.equals(BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS)) signature += "Cloud ClientId[" + serverDefinition.zeebeClientId + "] ClusterId[" + serverDefinition.zeebeSaasClusterId + "]"; @@ -781,7 +471,7 @@ public int getWorkerExecutionThreads() { return serverDefinition != null ? serverDefinition.workerExecutionThreads : 0; } - private String getUniqueMarker(String processId, String starterEventId) { + private String getUniqueMarker(String processId) { return processId + "-" + random.nextInt(1000000); } @@ -789,8 +479,15 @@ public ZeebeClient getZeebeClient() { return zeebeClient; } - - + protected Long getProcessInstanceIdFromMarker(List<Variable> listVariables) { + Optional<Variable> markerOptional = listVariables.stream() + .filter(v -> v.getName().equals(THIS_IS_A_COMPLETE_IMPOSSIBLE_VARIABLE_NAME)) + .findFirst(); + if (markerOptional.isEmpty()) + return null; + String marker = (String) markerOptional.get().getValue(); + return cacheProcessInstanceMarker.get(marker); + } /* ******************************************************************** */ /* */ /* Connection to each component */ @@ -804,12 +501,11 @@ private void connectZeebe(StringBuilder analysis) throws AutomatorException { boolean isOk = true; isOk = stillOk(serverDefinition.name, "ZeebeConnection", analysis, false, true, isOk); - this.typeCamundaEngine = this.serverDefinition.serverType; ZeebeClientBuilder clientBuilder; // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(serverDefinition.serverType)) { analysis.append("SaaS;"); String gatewayAddressCloud = @@ -847,7 +543,7 @@ private void connectZeebe(StringBuilder analysis) throws AutomatorException { } //---------------------------- Camunda 8 Self Manage - else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(serverDefinition.serverType)) { analysis.append("SelfManage;"); isOk = stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); if (serverDefinition.isAuthenticationUrl()) { @@ -948,278 +644,9 @@ else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) } } - /** - * Connect Operate - * - * @param analysis to cpmplete the analysis - * @throws AutomatorException in case of error - */ - private void connectOperate(StringBuilder analysis) throws AutomatorException { - if (!serverDefinition.isOperate()) { - analysis.append("No operate connection required, "); - return; - } - analysis.append("Operate connection..."); - - boolean isOk = true; - isOk = stillOk(serverDefinition.operateUrl, "operateUrl", analysis, true, true, isOk); - - CamundaOperateClientBuilder camundaOperateClientBuilder = new CamundaOperateClientBuilder(); - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { - - try { - isOk = stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientId, "zeebeClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeClientSecret, "zeebeClientSecret", analysis, true, false, isOk); - - URL operateUrl = URI.create("https://" + serverDefinition.zeebeSaasRegion + ".operate.camunda.io/" - + serverDefinition.zeebeSaasClusterId).toURL(); - - SaaSAuthenticationBuilder saaSAuthenticationBuilder = SaaSAuthentication.builder(); - JwtConfig jwtConfig = new JwtConfig(); - jwtConfig.addProduct(Product.OPERATE, - new JwtCredential(serverDefinition.zeebeClientId, serverDefinition.zeebeClientSecret, - serverDefinition.operateAudience != null ? serverDefinition.operateAudience : "operate.camunda.io", - serverDefinition.authenticationUrl != null ? - serverDefinition.authenticationUrl : - SAAS_AUTHENTICATE_URL)); - - Authentication saasAuthentication = SaaSAuthentication.builder() - .withJwtConfig(jwtConfig) - .withJsonMapper(new SdkObjectMapper()) - .build(); - - camundaOperateClientBuilder.authentication(saasAuthentication) - .operateUrl(serverDefinition.operateUrl) - .setup() - .build(); - - } catch (Exception e) { - zeebeClient = null; - logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } - - //---------------------------- Camunda 8 Self Manage - } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { - - isOk = stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); - - try { - if (serverDefinition.isAuthenticationUrl()) { - isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.operateClientId, "operateClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.operateClientSecret, "operateClientSecret", analysis, true, false, isOk); - - IdentityConfiguration identityConfiguration = new IdentityConfiguration.Builder().withBaseUrl( - serverDefinition.identityUrl) - .withIssuer(serverDefinition.authenticationUrl) - .withIssuerBackendUrl(serverDefinition.authenticationUrl) - .withClientId(serverDefinition.operateClientId) - .withClientSecret(serverDefinition.operateClientSecret) - .withAudience(serverDefinition.operateAudience) - .build(); - Identity identity = new Identity(identityConfiguration); - - IdentityConfig identityConfig = new IdentityConfig(); - identityConfig.addProduct(Product.OPERATE, new IdentityContainer(identity, identityConfiguration)); - - JwtConfig jwtConfig = new JwtConfig(); - jwtConfig.addProduct(Product.OPERATE, new JwtCredential(serverDefinition.operateClientId, // clientId - serverDefinition.operateClientSecret, // clientSecret - "zeebe-api", // audience - serverDefinition.authenticationUrl)); - - io.camunda.common.auth.SelfManagedAuthenticationBuilder identityAuthenticationBuilder = io.camunda.common.auth.SelfManagedAuthentication.builder(); - identityAuthenticationBuilder.withJwtConfig(jwtConfig); - identityAuthenticationBuilder.withIdentityConfig(identityConfig); - - Authentication identityAuthentication = identityAuthenticationBuilder.build(); - camundaOperateClientBuilder.authentication(identityAuthentication) - .operateUrl(serverDefinition.operateUrl) - .setup() - .build(); - - } else { - // Simple authentication - isOk = stillOk(serverDefinition.operateUserName, "operateUserName", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.operateUserPassword, "operateUserPassword", analysis, true, false, isOk); - - SimpleCredential simpleCredential = new SimpleCredential(serverDefinition.operateUrl, - serverDefinition.operateUserName, serverDefinition.operateUserPassword); - - SimpleConfig jwtConfig = new io.camunda.common.auth.SimpleConfig(); - jwtConfig.addProduct(Product.OPERATE, simpleCredential); - - io.camunda.common.auth.SimpleAuthenticationBuilder simpleAuthenticationBuilder = SimpleAuthentication.builder(); - simpleAuthenticationBuilder.withSimpleConfig(jwtConfig); - - Authentication simpleAuthentication = simpleAuthenticationBuilder.build(); - camundaOperateClientBuilder.authentication(simpleAuthentication) - .operateUrl(serverDefinition.operateUrl) - .setup() - .build(); - } - } catch (Exception e) { - logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } - - } else - throw new AutomatorException("Invalid configuration"); - - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); - - // ---------------- connection - try { - - operateClient = camundaOperateClientBuilder.build(); - - analysis.append("successfully, "); - - } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } - } - - /** - * Connect to TaskList - * - * @param analysis complete the analysis - * @throws AutomatorException in case of error - */ - private void connectTaskList(StringBuilder analysis) throws AutomatorException { - - if (!serverDefinition.isTaskList()) { - analysis.append("No TaskList connection required, "); - return; - } - analysis.append("Tasklist ..."); - - boolean isOk = true; - isOk = stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, true, true, isOk); - - CamundaTaskListClientBuilder taskListBuilder = CamundaTaskListClient.builder(); - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { - try { - isOk = stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); - - String taskListUrl = "https://" + serverDefinition.zeebeSaasRegion + ".tasklist.camunda.io/" - + serverDefinition.zeebeSaasClusterId; - - taskListBuilder.taskListUrl(taskListUrl) - .saaSAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret); - } catch (Exception e) { - logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } - - //---------------------------- Camunda 8 Self Manage - } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { - - if (serverDefinition.isAuthenticationUrl()) { - isOk = stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); - isOk = stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListKeycloakUrl, "taskListKeycloakUrl", analysis, true, true, isOk); - - taskListBuilder.taskListUrl(serverDefinition.taskListUrl) - .selfManagedAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret, - serverDefinition.taskListKeycloakUrl); - } else { - isOk = stillOk(serverDefinition.taskListUserName, "User", analysis, true, true, isOk); - isOk = stillOk(serverDefinition.taskListUserPassword, "Password", analysis, true, false, isOk); - - SimpleConfig simpleConf = new SimpleConfig(); - simpleConf.addProduct(Product.TASKLIST, - new SimpleCredential(serverDefinition.taskListUrl, serverDefinition.taskListUserName, - serverDefinition.taskListUserPassword)); - Authentication auth = SimpleAuthentication.builder().withSimpleConfig(simpleConf).build(); - - taskListBuilder.taskListUrl(serverDefinition.taskListUrl) - .authentication(auth) - .cookieExpiration(Duration.ofSeconds(5)); - } - } else - throw new AutomatorException("Invalid configuration"); - - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); - - // ---------------- connection - try { - taskListBuilder.zeebeClient(zeebeClient); - taskListBuilder.useZeebeUserTasks(); - taskClient = taskListBuilder.build(); - - analysis.append("successfully, "); - - } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } - - /* 1.6.1 - boolean isOk = true; - io.camunda.tasklist.auth.AuthInterface saTaskList; - - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { - try { - saTaskList = new io.camunda.tasklist.auth.SaasAuthentication(serverDefinition.zeebeSaasClientId, - serverDefinition.zeebeSaasClientSecret); - } catch (Exception e) { - logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } - - //---------------------------- Camunda 8 Self Manage - } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { - saTaskList = new io.camunda.tasklist.auth.SimpleAuthentication(serverDefinition.operateUserName, - serverDefinition.operateUserPassword); - } else - throw new AutomatorException("Invalid configuration"); - - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); - - // ---------------- connection - try { - isOk = stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, false, isOk); - analysis.append("Tasklist ..."); - - taskClient = new CamundaTaskListClient.Builder().taskListUrl(serverDefinition.taskListUrl) - .authentication(saTaskList) - .build(); - analysis.append("successfully, "); - //get tasks assigned to demo - logger.info("Zeebe: OK, Operate: OK, TaskList:OK " + analysis); - - } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } - */ + public BpmnEngineList.BpmnServerDefinition getServerDefinition() { + return serverDefinition; } /** @@ -1233,12 +660,12 @@ private void connectTaskList(StringBuilder analysis) throws AutomatorException { * @param wasOkBefore previous value, is returned if this check is Ok * @return previous value is ok false else */ - private boolean stillOk(Object value, - String message, - StringBuilder analysis, - boolean check, - boolean displayValueInAnalysis, - boolean wasOkBefore) { + protected boolean stillOk(Object value, + String message, + StringBuilder analysis, + boolean check, + boolean displayValueInAnalysis, + boolean wasOkBefore) { analysis.append(message); analysis.append("["); analysis.append(getDisplayValue(value, displayValueInAnalysis)); diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java new file mode 100644 index 0000000..a4011df --- /dev/null +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java @@ -0,0 +1,429 @@ +package org.camunda.automator.bpmnengine.camunda8; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.camunda.operate.CamundaOperateClient; +import io.camunda.operate.CamundaOperateClientConfiguration; +import io.camunda.operate.auth.*; +import io.camunda.operate.auth.TokenResponseMapper.JacksonTokenResponseMapper; +import io.camunda.operate.exception.OperateException; +import io.camunda.operate.model.*; +import io.camunda.operate.search.*; +import io.camunda.zeebe.client.api.response.ActivateJobsResponse; +import io.camunda.zeebe.client.api.response.ActivatedJob; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.camunda.automator.bpmnengine.BpmnEngine; +import org.camunda.automator.configuration.BpmnEngineList; +import org.camunda.automator.definition.ScenarioStep; +import org.camunda.automator.engine.AutomatorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.net.URL; +import java.time.Duration; +import java.util.*; +import java.util.stream.Collectors; + + +public class OperateClient { + + public static final int SEARCH_MAX_SIZE = 100; + private final Logger logger = LoggerFactory.getLogger(OperateClient.class); + BpmnEngineCamunda8 engineCamunda8; + private CamundaOperateClient operateClient; + + protected OperateClient(BpmnEngineCamunda8 engineCamunda8) { + this.engineCamunda8 = engineCamunda8; + } + + /** + * Connect Operate + * + * @param analysis to cpmplete the analysis + * @throws AutomatorException in case of error + */ + protected void connectOperate(StringBuilder analysis) throws AutomatorException { + BpmnEngineList.BpmnServerDefinition serverDefinition = engineCamunda8.getServerDefinition(); + + if (!serverDefinition.isOperate()) { + analysis.append("No operate connection required, "); + return; + } + analysis.append("Operate connection..."); + + boolean isOk = true; + isOk = engineCamunda8.stillOk(serverDefinition.operateUrl, "operateUrl", analysis, true, true, isOk); + + // CamundaOperateClientBuilder camundaOperateClientBuilder = new CamundaOperateClientBuilder(); + CamundaOperateClientConfiguration configuration = null; + // ---------------------------- Camunda Saas + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(serverDefinition.serverType)) { + + try { + isOk = engineCamunda8.stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.zeebeClientId, "zeebeClientId", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.zeebeClientSecret, "zeebeClientSecret", analysis, true, false, isOk); + + URL operateUrl = URI.create("https://" + serverDefinition.zeebeSaasRegion + ".operate.camunda.io/" + + serverDefinition.zeebeSaasClusterId).toURL(); + URL authUrl = URI.create("https://login.cloud.camunda.io/oauth/token").toURL(); + JwtCredential credentials = + new JwtCredential(serverDefinition.zeebeClientId, serverDefinition.zeebeClientSecret, "operate.camunda.io", authUrl, null); + ObjectMapper objectMapper = new ObjectMapper(); + TokenResponseMapper tokenResponseMapper = new JacksonTokenResponseMapper(objectMapper); + JwtAuthentication authentication = new JwtAuthentication(credentials, tokenResponseMapper); + configuration = + new CamundaOperateClientConfiguration( + authentication, operateUrl, objectMapper, HttpClients.createDefault()); + + + } catch (Exception e) { + logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + //---------------------------- Camunda 8 Self Manage + } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(serverDefinition.serverType)) { + + isOk = engineCamunda8.stillOk(serverDefinition.zeebeGatewayAddress, "GatewayAddress", analysis, true, true, isOk); + + try { + if (serverDefinition.isAuthenticationUrl()) { + isOk = engineCamunda8.stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.operateClientId, "operateClientId", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.operateClientSecret, "operateClientSecret", analysis, true, false, isOk); + + String scope = ""; + URL authUrl = + URI.create( + "http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token") + .toURL(); + URL operateUrl = URI.create(serverDefinition.operateUrl).toURL(); + // bootstrapping + JwtCredential credentials = + new JwtCredential(serverDefinition.operateClientId, serverDefinition.operateClientSecret, "operate-api", authUrl, scope); + ObjectMapper objectMapper = new ObjectMapper(); + TokenResponseMapper tokenResponseMapper = new JacksonTokenResponseMapper(objectMapper); + JwtAuthentication authentication = new JwtAuthentication(credentials, tokenResponseMapper); + configuration = + new CamundaOperateClientConfiguration( + authentication, operateUrl, objectMapper, HttpClients.createDefault()); + + } else { + // Simple authentication + isOk = engineCamunda8.stillOk(serverDefinition.operateUserName, "operateUserName", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.operateUserPassword, "operateUserPassword", analysis, true, false, isOk); + URL operateUrl = URI.create(serverDefinition.operateUrl).toURL(); + + SimpleCredential credentials = new SimpleCredential(serverDefinition.operateUserName, serverDefinition.operateUserPassword, operateUrl, Duration.ofMinutes(10)); + SimpleAuthentication authentication = new SimpleAuthentication(credentials); + ObjectMapper objectMapper = new ObjectMapper(); + configuration = new CamundaOperateClientConfiguration(authentication, operateUrl, objectMapper, HttpClients.createDefault()); + } + } catch (Exception e) { + logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + } else + throw new AutomatorException("Invalid configuration"); + + if (!isOk) + throw new AutomatorException("Invalid configuration " + analysis); + + // ---------------- connection + try { + + operateClient = new CamundaOperateClient(configuration); + + analysis.append("successfully, "); + + } catch (Exception e) { + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); + } + } + + public List<String> activateServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + long processInstanceIdLong = Long.parseLong(processInstanceId); + + ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder() + .parentKey(processInstanceIdLong) + .build(); + + SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter).size(100).build(); + + List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); + Set<Long> setProcessInstances = listProcessInstances.stream() + .map(ProcessInstance::getKey) + .collect(Collectors.toSet()); + setProcessInstances.add(processInstanceIdLong); + + ActivateJobsResponse jobsResponse = engineCamunda8.getZeebeClient().newActivateJobsCommand() + .jobType(topic) + .maxJobsToActivate(10000) + .workerName(Thread.currentThread().getName()) + .send() + .join(); + List<String> listJobsId = new ArrayList<>(); + + for (ActivatedJob job : jobsResponse.getJobs()) { + if (setProcessInstances.contains(job.getProcessInstanceKey())) + listJobsId.add(String.valueOf(job.getKey())); + else { + engineCamunda8.getZeebeClient().newFailCommand(job.getKey()).retries(2).send().join(); + } + } + return listJobsId; + + } catch (Exception e) { + throw new AutomatorException("Can't search service task topic[" + topic + "] : " + e.getMessage()); + } + } + + /** + * @param processInstanceId filter on the processInstanceId. may be null + * @param filterTaskId filter on the taskId + * @param maxResult maximum Result + * @return list of Task + * @throws AutomatorException + */ + public List<BpmnEngine.TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String filterTaskId, int maxResult) + throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee + FlowNodeInstanceFilter flownodeFilter = FlowNodeInstanceFilter.builder() + .processInstanceKey(Long.valueOf(processInstanceId)) + .build(); + + SearchQuery flowNodeQuery = new SearchQuery.Builder().filter(flownodeFilter).size(maxResult).build(); + List<FlowNodeInstance> flowNodes = operateClient.searchFlowNodeInstances(flowNodeQuery); + return flowNodes.stream() + .filter(t -> filterTaskId == null || filterTaskId.equals(t.getFlowNodeId())) // Filter by name + .map(t -> { + BpmnEngine.TaskDescription taskDescription = new BpmnEngine.TaskDescription(); + taskDescription.taskId = t.getFlowNodeId(); + taskDescription.processInstanceId = String.valueOf(t.getProcessInstanceKey()); + taskDescription.startDate = t.getStartDate().getDate(); + taskDescription.endDate = t.getEndDate().getDate(); + taskDescription.type = getTaskType(t.getType()); // to implement + taskDescription.isCompleted = FlowNodeInstanceState.COMPLETED.equals(t.getState()); // to implement + return taskDescription; + }).toList(); + + } catch (OperateException e) { + logger.error("Can't search FlowNode: " + e.getMessage()); + throw new AutomatorException("Can't search FlowNode: " + e.getMessage()); + } + // We must not be here + catch (Exception e) { + logger.error("Can't search FlowNode EXCEPTION NOT EXPECTED: " + e.getMessage()); + throw new AutomatorException("Can't search FlowNode: " + e.getMessage()); + } + } + + public List<BpmnEngine.ProcessDescription> searchProcessInstanceByVariable(String processId, + Map<String, Object> filterVariables, + int maxResult) throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee + ProcessInstanceFilter processInstanceFilter = ProcessInstanceFilter.builder().bpmnProcessId(processId).build(); + + SearchQuery processInstanceQuery = new SearchQuery.Builder().filter(processInstanceFilter) + .size(maxResult) + .build(); + List<ProcessInstance> listProcessInstances = operateClient.searchProcessInstances(processInstanceQuery); + + List<BpmnEngine.ProcessDescription> listProcessInstanceFind = new ArrayList<>(); + // now, we have to filter based on variableName/value + + for (ProcessInstance processInstance : listProcessInstances) { + Map<String, Object> processVariables = getVariables(processInstance.getKey().toString()); + List<Map.Entry<String, Object>> entriesNotFiltered = filterVariables.entrySet() + .stream() + .filter( + t -> processVariables.containsKey(t.getKey()) && processVariables.get(t.getKey()).equals(t.getValue())) + .toList(); + + if (entriesNotFiltered.isEmpty()) { + + BpmnEngine.ProcessDescription processDescription = new BpmnEngine.ProcessDescription(); + processDescription.processInstanceId = processInstance.getKey().toString(); + + listProcessInstanceFind.add(processDescription); + } + } + return listProcessInstanceFind; + } catch (OperateException e) { + logger.error("Can't search flowNodeByVariable: " + e.getMessage()); + throw new AutomatorException("Can't search flowNodeByVariable " + e.getMessage()); + } + // We must not be here + catch (Exception e) { + logger.error("Can't search flowNodeByVariable EXCEPTION NOT EXPECTED: " + e.getMessage()); + throw new AutomatorException("Can't search FlowNode: " + e.getMessage()); + } + } + + + public Map<String, Object> getVariables(String processInstanceId) throws AutomatorException { + try { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + // impossible to filter by the task name/ task tyoe, so be ready to get a lot of flowNode and search the correct onee + VariableFilter variableFilter = VariableFilter.builder() + .processInstanceKey(Long.valueOf(processInstanceId)) + .build(); + + SearchQuery variableQuery = new SearchQuery.Builder().filter(variableFilter).build(); + List<io.camunda.operate.model.Variable> listVariables = operateClient.searchVariables(variableQuery); + + Map<String, Object> variables = new HashMap<>(); + listVariables.forEach(t -> variables.put(t.getName(), t.getValue())); + + return variables; + } catch (OperateException e) { + logger.error("Can't getVariables: " + e.getMessage()); + throw new AutomatorException("Can't search variables task " + e.getMessage()); + } + // We must not be here + catch (Exception e) { + logger.error("Can't getVariables EXCEPTION NOT EXPECTED: " + e.getMessage()); + throw new AutomatorException("Can't getVariables: " + e.getMessage()); + } + } + + public long countNumberOfProcessInstancesCreated(String processId, Date startDate, Date endDate) + throws AutomatorException { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + try { + int cumul = 0; + SearchResult<ProcessInstance> searchResult = null; + queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + int maxLoop = 0; + do { + maxLoop++; + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchProcessInstanceResults(searchQuery); + + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().getDate().after(startDate)).count(); + + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessInstanceCreated " + e.getMessage()); + } + } + + public long countNumberOfProcessInstancesEnded(String processId, Date startDate, Date endDate) + throws AutomatorException { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + try { + int cumul = 0; + SearchResult<ProcessInstance> searchResult = null; + + queryBuilder = queryBuilder.filter(ProcessInstanceFilter.builder().bpmnProcessId(processId) + // .startDate(startDate) + // .endDate(endDate) + .state(ProcessInstanceState.COMPLETED).build()); + + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + int maxLoop = 0; + do { + maxLoop++; + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchProcessInstanceResults(searchQuery); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().getDate().after(startDate)).count(); + + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); + } + } + + public long countNumberOfTasks(String processId, String taskId) throws AutomatorException { + if (operateClient == null) { + throw new AutomatorException("No Operate connection was provided"); + } + + try { + int cumul = 0; + SearchResult<FlowNodeInstance> searchResult = null; + int maxLoop = 0; + do { + maxLoop++; + + SearchQuery.Builder queryBuilder = new SearchQuery.Builder(); + queryBuilder = queryBuilder.filter(FlowNodeInstanceFilter.builder().flowNodeId(taskId).build()); + queryBuilder.sort(new Sort("key", SortOrder.ASC)); + if (searchResult != null && !searchResult.getItems().isEmpty()) { + queryBuilder.searchAfter(searchResult.getSortValues()); + } + SearchQuery searchQuery = queryBuilder.build(); + searchQuery.setSize(SEARCH_MAX_SIZE); + searchResult = operateClient.searchFlowNodeInstanceResults(searchQuery); + cumul += (long) searchResult.getItems().size(); + } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); + return cumul; + } catch (Exception e) { + throw new AutomatorException("Search countNumberProcessEnded " + e.getMessage()); + } + } + + private ScenarioStep.Step getTaskType(String taskTypeC8) { + return switch (taskTypeC8) { + case "SERVICE_TASK" -> ScenarioStep.Step.SERVICETASK; + case "USER_TASK" -> ScenarioStep.Step.USERTASK; + case "START_EVENT" -> ScenarioStep.Step.STARTEVENT; + case "END_EVENT" -> ScenarioStep.Step.ENDEVENT; + case "EXCLUSIVE_GATEWAY" -> ScenarioStep.Step.EXCLUSIVEGATEWAY; + case "PARALLEL_GATEWAY" -> ScenarioStep.Step.PARALLELGATEWAY; + case "TASK" -> ScenarioStep.Step.TASK; + case "SCRIPT_TASK" -> ScenarioStep.Step.SCRIPTTASK; + default -> null; + }; + + } + +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java new file mode 100644 index 0000000..3fe8ea2 --- /dev/null +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java @@ -0,0 +1,245 @@ +package org.camunda.automator.bpmnengine.camunda8; + +import io.camunda.tasklist.CamundaTaskListClient; +import io.camunda.tasklist.CamundaTaskListClientBuilder; +import io.camunda.tasklist.auth.Authentication; +import io.camunda.tasklist.auth.SimpleAuthentication; +import io.camunda.tasklist.auth.SimpleCredential; +import io.camunda.tasklist.dto.*; +import io.camunda.tasklist.exception.TaskListException; +import org.camunda.automator.configuration.BpmnEngineList; +import org.camunda.automator.engine.AutomatorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class TaskListClient { + private final Logger logger = LoggerFactory.getLogger(TaskListClient.class); + BpmnEngineCamunda8 engineCamunda8; + private CamundaTaskListClient taskClient; + + protected TaskListClient(BpmnEngineCamunda8 engineCamunda8) { + this.engineCamunda8 = engineCamunda8; + } + + /** + * Connect to TaskList + * + * @param analysis complete the analysis + * @throws AutomatorException in case of error + */ + public void connectTaskList(StringBuilder analysis) throws AutomatorException { + + BpmnEngineList.BpmnServerDefinition serverDefinition = engineCamunda8.getServerDefinition(); + if (!serverDefinition.isTaskList()) { + analysis.append("No TaskList connection required, "); + return; + } + analysis.append("Tasklist ..."); + + boolean isOk = true; + isOk = engineCamunda8.stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, true, true, isOk); + + CamundaTaskListClientBuilder taskListBuilder = CamundaTaskListClient.builder(); + // ---------------------------- Camunda Saas + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(serverDefinition.serverType)) { + try { + isOk = engineCamunda8.stillOk(serverDefinition.zeebeSaasRegion, "zeebeSaasRegion", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.zeebeSaasClusterId, "zeebeSaasClusterId", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); + + String taskListUrl = "https://" + serverDefinition.zeebeSaasRegion + ".tasklist.camunda.io/" + + serverDefinition.zeebeSaasClusterId; + + taskListBuilder.taskListUrl(taskListUrl) + .saaSAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret); + } catch (Exception e) { + logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + //---------------------------- Camunda 8 Self Manage + } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(serverDefinition.serverType)) { + + if (serverDefinition.isAuthenticationUrl()) { + isOk = engineCamunda8.stillOk(serverDefinition.taskListClientId, "taskListClientId", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.taskListClientSecret, "taskListClientSecret", analysis, true, false, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.authenticationUrl, "authenticationUrl", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.taskListKeycloakUrl, "taskListKeycloakUrl", analysis, true, true, isOk); + + taskListBuilder.taskListUrl(serverDefinition.taskListUrl) + .selfManagedAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret, + serverDefinition.taskListKeycloakUrl); + } else { + isOk = engineCamunda8.stillOk(serverDefinition.taskListUserName, "User", analysis, true, true, isOk); + isOk = engineCamunda8.stillOk(serverDefinition.taskListUserPassword, "Password", analysis, true, false, isOk); + try { + SimpleCredential credentials = new SimpleCredential(serverDefinition.taskListUserName, + serverDefinition.taskListUserPassword, new URL(serverDefinition.taskListUrl), Duration.ofHours(1000)); + Authentication authentication = new SimpleAuthentication(credentials); + taskListBuilder.taskListUrl(serverDefinition.taskListUrl).authentication(authentication); + } catch (MalformedURLException e) { + throw new AutomatorException("Invalid Url for TaskList: [" + serverDefinition.taskListUrl + "]"); + + } + } + } else + throw new AutomatorException("Invalid configuration"); + + if (!isOk) + throw new AutomatorException("Invalid configuration " + analysis); + + // ---------------- connection + try { + taskListBuilder.zeebeClient(engineCamunda8.getZeebeClient()); + taskListBuilder.useZeebeUserTasks(); + taskClient = taskListBuilder.build(); + + analysis.append("successfully, "); + + } catch (Exception e) { + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); + } + + /* 1.6.1 + boolean isOk = true; + io.camunda.tasklist.auth.AuthInterface saTaskList; + + // ---------------------------- Camunda Saas + if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { + try { + saTaskList = new io.camunda.tasklist.auth.SaasAuthentication(serverDefinition.zeebeSaasClientId, + serverDefinition.zeebeSaasClientSecret); + } catch (Exception e) { + logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + + e.getMessage()); + } + + //---------------------------- Camunda 8 Self Manage + } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { + saTaskList = new io.camunda.tasklist.auth.SimpleAuthentication(serverDefinition.operateUserName, + serverDefinition.operateUserPassword); + } else + throw new AutomatorException("Invalid configuration"); + + if (!isOk) + throw new AutomatorException("Invalid configuration " + analysis); + + // ---------------- connection + try { + isOk = engineCamunda8.stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, false, isOk); + analysis.append("Tasklist ..."); + + taskClient = new CamundaTaskListClient.Builder().taskListUrl(serverDefinition.taskListUrl) + .authentication(saTaskList) + .build(); + analysis.append("successfully, "); + //get tasks assigned to demo + logger.info("Zeebe: OK, Operate: OK, TaskList:OK " + analysis); + + } catch (Exception e) { + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + throw new AutomatorException( + "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); + } + */ + + } + + public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) + throws AutomatorException { + try { + // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one + Long processInstanceIdLong = Long.valueOf(processInstanceId); + + TaskSearch taskSearch = new TaskSearch(); + taskSearch.setState(TaskState.CREATED); + taskSearch.setAssigned(Boolean.FALSE); + taskSearch.setWithVariables(true); + taskSearch.setPagination(new Pagination().setPageSize(maxResult)); + + TaskList tasksList = taskClient.getTasks(taskSearch); + boolean getAllTasks = tasksList.size() < maxResult; + List<String> listTasksResult = new ArrayList<>(); + do { + if (!engineCamunda8.isHightFlowMode()) { + // We check that the task is the one expected + listTasksResult.addAll(tasksList.getItems().stream().filter(t -> { + List<Variable> listVariables = t.getVariables(); + Long processInstanceIdTask = engineCamunda8.getProcessInstanceIdFromMarker(listVariables); + if (processInstanceIdTask == null) { + return false; + } + return (processInstanceIdLong.equals(processInstanceIdTask)); + }).map(Task::getId) // Task to ID + .toList()); + } else { + listTasksResult.addAll(tasksList.getItems().stream() + .map(Task::getId) // Task to ID + .toList()); + } + + if (tasksList.size() > 0 && !getAllTasks) + tasksList = taskClient.after(tasksList); + } while (tasksList.size() > 0 && !getAllTasks); + + return listTasksResult; + + } catch (TaskListException e) { + logger.error("TaskListClient: error during search task [{}]", e.getMessage()); + throw new AutomatorException("Can't search users task " + e.getMessage()); + } + } + + public List<String> searchUserTasks(String userTaskId, int maxResult) throws AutomatorException { + try { + // impossible to filter by the task name/ task type, so be ready to get a lot of flowNode and search the correct one + + TaskSearch taskSearch = new TaskSearch(); + taskSearch.setState(TaskState.CREATED); + taskSearch.setAssigned(Boolean.FALSE); + taskSearch.setWithVariables(true); + taskSearch.setPagination(new Pagination().setPageSize(maxResult)); + + TaskList tasksList = taskClient.getTasks(taskSearch); + List<String> listTasksResult = new ArrayList<>(); + do { + listTasksResult.addAll(tasksList.getItems().stream().map(Task::getId) // Task to ID + .toList()); + + if (tasksList.size() > 0) + tasksList = taskClient.after(tasksList); + } while (tasksList.size() > 0); + + return listTasksResult; + + } catch (TaskListException e) { + throw new AutomatorException("Can't search users task " + e.getMessage()); + } + } + + public void executeUserTask(String userTaskId, String userId, Map<String, Object> variables) + throws AutomatorException { + try { + taskClient.claim(userTaskId, engineCamunda8.getServerDefinition().operateUserName); + taskClient.completeTask(userTaskId, variables); + } catch (TaskListException e) { + throw new AutomatorException("Can't execute task [" + userTaskId + "]"); + } catch (Exception e) { + throw new AutomatorException("Can't execute task [" + userTaskId + "]"); + } + } +} diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java deleted file mode 100644 index ae5c95f..0000000 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/refactoring/RefactoredCommandWrapper.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.camunda.automator.bpmnengine.camunda8.refactoring; - -import io.camunda.zeebe.client.api.ZeebeFuture; -import io.camunda.zeebe.client.api.command.FinalCommandStep; -import io.camunda.zeebe.client.api.worker.BackoffSupplier; -import io.camunda.zeebe.spring.client.jobhandling.CommandWrapper; -import io.camunda.zeebe.spring.client.jobhandling.DefaultCommandExceptionHandlingStrategy; - -import java.time.Instant; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * Copied from CommandWrapper from spring-zeebe. Refactor over there to be able to use built-in stuff directly - */ -public class RefactoredCommandWrapper extends CommandWrapper { - - private final FinalCommandStep<Void> command; - private final long deadline; - private final String entityLogInfo; - private final DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy; - private final int maxRetries = 20; - private long currentRetryDelay = 50L; - private int invocationCounter = 0; - - public RefactoredCommandWrapper(FinalCommandStep<Void> command, - long deadline, - String entityLogInfo, - DefaultCommandExceptionHandlingStrategy commandExceptionHandlingStrategy) { - super(command, null, commandExceptionHandlingStrategy); - this.command = command; - this.deadline = deadline; - this.entityLogInfo = entityLogInfo; - this.commandExceptionHandlingStrategy = commandExceptionHandlingStrategy; - } - - @Override - public void executeAsync() { - ++this.invocationCounter; - ZeebeFuture<Void> zeebeFuture = this.command.send(); - if (commandExceptionHandlingStrategy != null) - zeebeFuture.exceptionally(t -> { - this.commandExceptionHandlingStrategy.handleCommandError(this, t); - return null; - }); - } - - public Object executeSync() { - ++this.invocationCounter; - ZeebeFuture<Void> zeebeFutur = this.command.send(); - if (commandExceptionHandlingStrategy != null) - zeebeFutur.exceptionally(t -> { - this.commandExceptionHandlingStrategy.handleCommandError(this, t); - return null; - }); - return zeebeFutur.join(); - } - - @Override - public void increaseBackoffUsing(BackoffSupplier backoffSupplier) { - this.currentRetryDelay = backoffSupplier.supplyRetryDelay(this.currentRetryDelay); - } - - @Override - public void scheduleExecutionUsing(ScheduledExecutorService scheduledExecutorService) { - scheduledExecutorService.schedule(this::executeAsync, this.currentRetryDelay, TimeUnit.MILLISECONDS); - } - - @Override - public String toString() { - return "{command=" + this.command.getClass() + ", entity=" + this.entityLogInfo + ", currentRetryDelay=" - + this.currentRetryDelay + '}'; - } - - @Override - public boolean hasMoreRetries() { - if (this.jobDeadlineExceeded()) { - return false; - } else { - return this.invocationCounter < this.maxRetries; - } - } - - @Override - public boolean jobDeadlineExceeded() { - return Instant.now().getEpochSecond() > this.deadline; - } - -} diff --git a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java index 5ed76ea..ae31b7d 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java +++ b/src/main/java/org/camunda/automator/bpmnengine/dummy/BpmnEngineDummy.java @@ -86,7 +86,7 @@ public RegisteredTask registerServiceTask(String workerId, } @Override - public List<String> searchServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) + public List<String> activateServiceTasks(String processInstanceId, String serviceTaskId, String topic, int maxResult) throws AutomatorException { return Collections.emptyList(); } diff --git a/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java b/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java index efa90bf..6ecc70c 100644 --- a/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java +++ b/src/main/java/org/camunda/automator/configuration/BpmnEngineList.java @@ -516,7 +516,8 @@ public enum CamundaEngine {CAMUNDA_7, CAMUNDA_8, CAMUNDA_8_SAAS, DUMMY} public static class BpmnServerDefinition { public String name; - public CamundaEngine serverType; + public CamundaEngine serverType = BpmnEngineList.CamundaEngine.CAMUNDA_8; + /** * My Zeebe Address diff --git a/src/main/java/org/camunda/automator/content/ContentManager.java b/src/main/java/org/camunda/automator/content/ContentManager.java index 4b2f866..0df289d 100644 --- a/src/main/java/org/camunda/automator/content/ContentManager.java +++ b/src/main/java/org/camunda/automator/content/ContentManager.java @@ -14,7 +14,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; -import java.io.*; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -42,10 +42,10 @@ public Path getFromName(String scenarioName) { } - @PostConstruct public void init() { try { + logger.info("ContentManager: start initialization"); repositoryManager.initializeRepository(repositoryPath); loadUploadPath(); LoadContentResource(); @@ -70,7 +70,7 @@ public List<Scenario> getContentScenario() { List<Path> listContent = repositoryManager.getContentRepository(); for (Path path : listContent) { // The content can have multiple files, not only scenario - if (! path.getFileName().toString().endsWith(".json")) + if (!path.getFileName().toString().endsWith(".json")) continue; try { Scenario scenario = Scenario.createFromFile(path); @@ -83,7 +83,6 @@ public List<Scenario> getContentScenario() { } - public Path addFile(Path file) throws IOException { return repositoryManager.addFile(file); } @@ -124,7 +123,7 @@ private void LoadContentResource() { private void loadUploadPath() { try { Path sourceDirectory = Paths.get(uploadPath); - logger.info("ContentManager/Upload: from [{}]", sourceDirectory); + logger.info("ContentManager/Upload: from [{}]", sourceDirectory.toAbsolutePath()); int nbFilesCopied = 0; // Copy all files from source to target List<Path> listFiles = Files.walk(sourceDirectory) diff --git a/src/main/java/org/camunda/automator/content/ContentRestController.java b/src/main/java/org/camunda/automator/content/ContentRestController.java index 3f75eba..bef88dd 100644 --- a/src/main/java/org/camunda/automator/content/ContentRestController.java +++ b/src/main/java/org/camunda/automator/content/ContentRestController.java @@ -31,15 +31,16 @@ public class ContentRestController { **/ @PostMapping(value = "/api/content/add", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE}, produces = MediaType.APPLICATION_JSON_VALUE) - public List<Map<String, Object>> upload(@RequestPart("File") List<MultipartFile> uploadedfiles) { + public List<Map<String, Object>> upload(@RequestPart("FileToUpload") List<MultipartFile> uploadedfiles) { List<Map<String, Object>> result = new ArrayList<>(); for (MultipartFile file : uploadedfiles) { try { Path fileSaved = contentManager.addFromMultipart(file, file.getOriginalFilename()); result.add(Map.of("filename", fileSaved.getFileName(), "status", "UPLOADED")); + logger.info("ControlRestController: uploaded file[{}] with success", file.getOriginalFilename()); } catch (Exception e) { + logger.info("ControlRestController: Errir upload file [{}] : {}", file.getOriginalFilename(), e.getMessage()); result.add(Map.of("filename", file.getOriginalFilename(), "status", "ERROR", "error", e.getMessage())); - } } return result; @@ -48,11 +49,15 @@ public List<Map<String, Object>> upload(@RequestPart("File") List<MultipartFile> @GetMapping("/api/content/list") List<Map<String, Object>> getContentScenario() { + logger.debug("ControlRestController/getContentScenario: start"); try { - return contentManager.getContentScenario().stream() + List<Map<String, Object>> listScenario = contentManager.getContentScenario().stream() .map(Scenario::getDescription) .toList(); + logger.info("ControlRestController/getContentScenario: found {} scenario", listScenario.size()); + return listScenario; } catch (Exception e) { + logger.info("ControlRestController/getContentScenario: Error during getContentScenario {} ", e.getMessage()); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Error during Content : " + e.getMessage()); } } diff --git a/src/main/java/org/camunda/automator/content/RepositoryManager.java b/src/main/java/org/camunda/automator/content/RepositoryManager.java index 3b424e9..55a4403 100644 --- a/src/main/java/org/camunda/automator/content/RepositoryManager.java +++ b/src/main/java/org/camunda/automator/content/RepositoryManager.java @@ -6,10 +6,7 @@ import org.springframework.core.io.Resource; import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.nio.file.*; import java.util.Collections; import java.util.List; import java.util.stream.Stream; @@ -20,25 +17,35 @@ public class RepositoryManager { private Path repositoryPath; public void initializeRepository(String repositoryProposition) throws AutomatorException { + logger.info("RepositoryManager: initialisation proposition: {}", repositoryProposition); + + repositoryPath = null; if (repositoryProposition != null && !repositoryProposition.isEmpty()) { Path path = Paths.get(repositoryProposition); if (Files.exists(path) && Files.isDirectory(path)) { repositoryPath = path; + logger.info("RepositoryManager: PathFromConfiguration [{}]", repositoryPath.toAbsolutePath()); } - } else { + } + if (repositoryPath == null) { // Not exist: create a subfolder Path tempDir = Paths.get(System.getProperty("java.io.tmpdir")); + logger.info("RepositoryManager/initialization: TemporaryFolder [{}]", tempDir.toAbsolutePath()); // Create a new folder in the temporary directory try { - repositoryPath = Files.createDirectory(tempDir.resolve("repository")); + repositoryPath = tempDir.resolve("repository"); + Files.createDirectory(repositoryPath); + logger.info("RepositoryManager/initialization: PathFromTemporaryFolder [{}]", repositoryPath.toAbsolutePath()); + } catch (FileAlreadyExistsException e) { + logger.info("RepositoryManager/initialization: File already exists [{}]", repositoryPath.toAbsolutePath()); } catch (Exception e) { - logger.error("Can't create folder [{}]", tempDir.toAbsolutePath() + "/repository"); + logger.error("RepositoryManager/initialization: Can't create folder [{}]", repositoryPath.toAbsolutePath()); + repositoryPath = null; throw new AutomatorException("Can't create folder[" + tempDir.toAbsolutePath() + "/repository]"); } } - logger.info("RepositoryManager: directory under [{}] ", repositoryPath.toAbsolutePath()); - + logger.info("RepositoryManager/initialization: Folder [{}]", repositoryPath.toAbsolutePath()); } public Path addResource(Resource resource) throws IOException { diff --git a/src/main/java/org/camunda/automator/definition/Scenario.java b/src/main/java/org/camunda/automator/definition/Scenario.java index 461abee..660b6c8 100644 --- a/src/main/java/org/camunda/automator/definition/Scenario.java +++ b/src/main/java/org/camunda/automator/definition/Scenario.java @@ -199,11 +199,11 @@ private void afterUnSerialize() { public Map<String, Object> getDescription() { - return Map.of("name", name==null?"":name,// - "server", serverName==null? "": serverName, // - "serverType", serverType==null?"": serverType, // - "processId", processId==null?"":processId, // - "typeScenario", typeScenario==null? "": typeScenario.toString()); + return Map.of("name", name == null ? "" : name,// + "server", serverName == null ? "" : serverName, // + "serverType", serverType == null ? "" : serverType, // + "processId", processId == null ? "" : processId, // + "typeScenario", typeScenario == null ? "" : typeScenario.toString()); } public enum TYPESCENARIO {FLOW, UNIT} diff --git a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java index 0d0af65..e49e5a0 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java @@ -120,6 +120,7 @@ public ScenarioExecution setName(String name) { return this; } + public int getNumberOfThreads() { return (numberOfThreads == null ? 1 : numberOfThreads <= 0 ? 1 : numberOfThreads); } diff --git a/src/main/java/org/camunda/automator/engine/RunResult.java b/src/main/java/org/camunda/automator/engine/RunResult.java index a112d40..54a764e 100644 --- a/src/main/java/org/camunda/automator/engine/RunResult.java +++ b/src/main/java/org/camunda/automator/engine/RunResult.java @@ -203,6 +203,17 @@ public void merge(RunResult result) { } } + /** + * Two execution on the exact same execution: we go for a merge plus one step more, we collect additionnal information, like processinstanceIdList + * @param result + */ + public void mergeDuplicateExecution(RunResult result) { + merge(result); + listProcessInstancesId.addAll(result.listProcessInstancesId); + listDetailsSteps.addAll(result.listDetailsSteps); + + } + public void add(RunResult runResult) { // We keep track of the result in a list listRunResults.add(runResult); @@ -230,6 +241,13 @@ public String getFirstProcessInstanceId() { return listProcessInstancesId.isEmpty() ? null : listProcessInstancesId.get(0); } + public List<String> getListProcessInstancesId() { + return listProcessInstancesId; + } + + public void addListProcessInstancesId(List<String> listProcessInstancesId) { + listProcessInstancesId.addAll(listProcessInstancesId); + } public List<String> getProcessInstanceId() { return this.listProcessInstancesId; } diff --git a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java index bb799a8..c4ed9d1 100644 --- a/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java +++ b/src/main/java/org/camunda/automator/engine/flow/RunScenarioFlowServiceTask.java @@ -6,15 +6,11 @@ /* ******************************************************************** */ package org.camunda.automator.engine.flow; -import io.camunda.zeebe.client.api.command.CompleteJobCommandStep1; -import io.camunda.zeebe.client.api.command.FinalCommandStep; import io.camunda.zeebe.client.api.response.ActivatedJob; import io.camunda.zeebe.client.api.worker.JobClient; import io.camunda.zeebe.client.api.worker.JobHandler; -import io.camunda.zeebe.spring.client.jobhandling.CommandWrapper; import org.camunda.automator.bpmnengine.BpmnEngine; import org.camunda.automator.bpmnengine.camunda8.BenchmarkCompleteJobExceptionHandlingStrategy; -import org.camunda.automator.bpmnengine.camunda8.refactoring.RefactoredCommandWrapper; import org.camunda.automator.definition.ScenarioStep; import org.camunda.automator.engine.RunResult; import org.camunda.automator.engine.RunScenario; @@ -36,12 +32,11 @@ public class RunScenarioFlowServiceTask extends RunScenarioFlowBasic { private static final TrackActiveWorker trackAsynchronousWorkers = new TrackActiveWorker(); private final TaskScheduler scheduler; private final Semaphore semaphore; + private final BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy = null; Logger logger = LoggerFactory.getLogger(RunScenarioFlowServiceTask.class); private BpmnEngine.RegisteredTask registeredTask; private boolean stopping; - private final BenchmarkCompleteJobExceptionHandlingStrategy exceptionHandlingStrategy = null; - public RunScenarioFlowServiceTask(TaskScheduler scheduler, ScenarioStep scenarioStep, RunScenario runScenario, @@ -173,6 +168,15 @@ public void handle(JobClient jobClient, ActivatedJob activatedJob) throws Except } } + /** + * This method execute the jib and wait for the result of the sending + * + * @param externalTask external task (C7 engine) + * @param externalTaskService service (C7 engine) + * @param jobClient jobClient (C8 engine) + * @param activatedJob activated Job (C8 engine) + * @param waitTimeInMs Wait time to simulate a worker + */ private void manageWaitExecution(org.camunda.bpm.client.task.ExternalTask externalTask, ExternalTaskService externalTaskService, JobClient jobClient, @@ -200,11 +204,7 @@ private void manageWaitExecution(org.camunda.bpm.client.task.ExternalTask extern /* C8 */ if (jobClient != null) { currentVariables = activatedJob.getVariablesAsMap(); - CompleteJobCommandStep1 completeCommand = jobClient.newCompleteCommand(activatedJob.getKey()); - CommandWrapper command = new RefactoredCommandWrapper((FinalCommandStep) completeCommand, - activatedJob.getDeadline(), activatedJob.toString(), exceptionHandlingStrategy); - - command.executeAsync(); + jobClient.newCompleteCommand(activatedJob.getKey()).send().join(); } flowServiceTask.runResult.registerAddStepExecution(); @@ -232,6 +232,16 @@ private void manageWaitExecution(org.camunda.bpm.client.task.ExternalTask extern } + /** + * Run the server in a different thread, so the library does not wait for the answer. Simulate a Reactive Programming + * In the thread, the execution wait for the durationSleep, then it call the manageWaitExecution, with a delay of 0. + * Then, no thread is engaged during the waiting. + * + * @param externalTask external task (C7 engine) + * @param externalTaskService service (C7 engine) + * @param jobClient jobClient (C8 engine) + * @param activatedJob activated Job (C8 engine) + */ private void manageAsynchronousExecution(org.camunda.bpm.client.task.ExternalTask externalTask, ExternalTaskService externalTaskService, JobClient jobClient, @@ -248,6 +258,17 @@ public void run() { }, Instant.now().plusMillis(durationSleep.toMillis())); } + /** + * Simulate a Asynchronous Token implementation. + * A token is get (number of token is limited in the step), and when it gets a token, manage the execution asynchronously. + * With that implementation, we ensure there are only <tokens> execution at a time, to control the access to an external service + * Because the getToken is in the method, the library waits if there are no more token, and don't acquire new job + * + * @param externalTask external task (C7 engine) + * @param externalTaskService service (C7 engine) + * @param jobClient jobClient (C8 engine) + * @param activatedJob activated Job (C8 engine) + */ private void manageAsynchronousLimitedExecution(org.camunda.bpm.client.task.ExternalTask externalTask, ExternalTaskService externalTaskService, JobClient jobClient, diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java index d4c20f2..2e6967e 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnit.java @@ -63,8 +63,9 @@ public RunResult runExecution() { try { for (Future<?> f : listFutures) { Object scnRunResult = f.get(); - resultExecution.merge((RunResult) scnRunResult); - + if (scnRunResult instanceof RunResult runResultInstances) { + resultExecution.mergeDuplicateExecution(runResultInstances); + } } } catch (Exception e) { diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java index 3773877..9e4a310 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java @@ -56,7 +56,7 @@ public RunResult executeServiceTask(ScenarioStep step, RunResult result) { do { listActivities = runScenario.getBpmnEngine() - .searchServiceTasks(result.getFirstProcessInstanceId(), step.getTaskId(), step.getTopic(), 1); + .activateServiceTasks(result.getFirstProcessInstanceId(), step.getTaskId(), step.getTopic(), 1); if (listActivities.isEmpty()) { try { diff --git a/src/main/java/org/camunda/automator/services/AutomatorStartup.java b/src/main/java/org/camunda/automator/services/AutomatorStartup.java index c5eb633..5f48609 100644 --- a/src/main/java/org/camunda/automator/services/AutomatorStartup.java +++ b/src/main/java/org/camunda/automator/services/AutomatorStartup.java @@ -75,6 +75,61 @@ private void runFixedWarmup() { } } + private List<Path> loadStartupScenario() { + List<Path> scenarioList = new ArrayList<>(); + // File + if (configurationStartup.getScenarioFileAtStartup().isEmpty()) { + logger.info("AutomatorStartup/StartupScenario: no scenario [File] from {} given", configurationStartup.getScenarioFileAtStartupName()); + } else { + logger.info("Detect {} scenario [File] from variable [{}] ScenarioPath[{}]", + configurationStartup.getScenarioFileAtStartup().size(), configurationStartup.getScenarioFileAtStartupName(), + configurationStartup.scenarioPath); + + for (String scenarioFileName : configurationStartup.getScenarioFileAtStartup()) { + logger.info("AutomatorStartup/StartupScenario: Register scenario [File] [{}]", scenarioFileName); + + Path scenarioFile = Paths.get(configurationStartup.scenarioPath + "/" + scenarioFileName); + if (!Files.exists(scenarioFile)) { + scenarioFile = Paths.get(scenarioFileName); + } + if (Files.exists(scenarioFile)) { + try { + contentManager.addFile(scenarioFile); + } catch (IOException e) { + logger.error("AutomatorStartup/StartupScenario: File [{}] Can't add in the repository: {}", scenarioFile.toAbsolutePath(), e.getMessage()); + } + } else { + logger.error("AutomatorStartup/StartupScenario:: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, + scenarioFileName, scenarioFileName); + continue; + } + } + + } + + // Resource + if (configurationStartup.getScenarioResourceAtStartup().isEmpty()) { + logger.info("No scenario [Resource] from variable {} given", + configurationStartup.getScenarioResourceAtStartupName()); + } else { + List<Resource> scenarioResource = configurationStartup.getScenarioResourceAtStartup().stream() + .filter(t -> t != null) + .collect(Collectors.toList()); + + logger.info("Detect {} scenario [Resource] from variable [{}]", + scenarioResource.size(), + configurationStartup.getScenarioResourceAtStartupName()); + for (Resource resource : scenarioResource) { + try { + scenarioList.add(contentManager.addResource(resource)); + } catch (IOException e) { + logger.error("Error loading resource [{}]", resource.getFilename()); + } + } + } + + return scenarioList; + } /** * AutomatorSetupRunnable - run in parallel @@ -220,61 +275,5 @@ public void run() { } } - private List<Path> loadStartupScenario() { - List<Path> scenarioList = new ArrayList<>(); - // File - if (configurationStartup.getScenarioFileAtStartup().isEmpty()) { - logger.info("AutomatorStartup/StartupScenario: no scenario [File] from {} given", configurationStartup.getScenarioFileAtStartupName()); - } else { - logger.info("Detect {} scenario [File] from variable [{}] ScenarioPath[{}]", - configurationStartup.getScenarioFileAtStartup().size(), configurationStartup.getScenarioFileAtStartupName(), - configurationStartup.scenarioPath); - - for (String scenarioFileName : configurationStartup.getScenarioFileAtStartup()) { - logger.info("AutomatorStartup/StartupScenario: Register scenario [File] [{}]", scenarioFileName); - - Path scenarioFile = Paths.get(configurationStartup.scenarioPath + "/" + scenarioFileName); - if (!Files.exists(scenarioFile)) { - scenarioFile = Paths.get(scenarioFileName); - } - if (Files.exists(scenarioFile)) { - try { - contentManager.addFile(scenarioFile); - }catch (IOException e) { - logger.error("AutomatorStartup/StartupScenario: File [{}] Can't add in the repository: {}", scenarioFile.toAbsolutePath().toString(), e.getMessage()); - } - } else { - logger.error("AutomatorStartup/StartupScenario:: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, - scenarioFileName, scenarioFileName); - continue; - } - } - - } - - // Resource - if (configurationStartup.getScenarioResourceAtStartup().isEmpty()) { - logger.info("No scenario [Resource] from variable {} given", - configurationStartup.getScenarioResourceAtStartupName()); - } else { - List<Resource> scenarioResource = configurationStartup.getScenarioResourceAtStartup().stream() - .filter(t -> t != null) - .collect(Collectors.toList()); - - logger.info("Detect {} scenario [Resource] from variable [{}]", - scenarioResource.size(), - configurationStartup.getScenarioResourceAtStartupName()); - for (Resource resource : scenarioResource) { - try { - scenarioList.add(contentManager.addResource(resource)); - } catch (IOException e) { - logger.error("Error loading resource [{}]", resource.getFilename()); - } - } - } - - return scenarioList; - } - } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 729dc17..a99b1f0 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -3,8 +3,14 @@ automator: scheduler: content: - repositoryPath: "c:/temp/processautomator" - uploadPath: "C:/temp/upload" + # RepositoryPath is null, so it will use the temporary path of the machine + repositoryPath: + + # for convenience raison, upload all files on this path directly + # Does not start by /, so use the default location of the starter + uploadPath: "src/test/resources/uploadpath" + + # A scenario can be load via this variable scenario: startup: @@ -68,6 +74,21 @@ automator: # -1 means : align the jobsActive to the workerExecutionThreads workerMaxJobsActive: -1 + - type: "camunda8" + description: "Kubernetes, Simple authentication" + name: "Camunda8Topaz" + zeebeGatewayAddress: "camunda-zeebe-gateway:26500" + zeebeRestAddress: "http://camunda-zeebe-gateway:9600" + operateUserName: "demo" + operateUserPassword: "demo" + operateUrl: "http://camunda-operate:80" + taskListUserName: "demo" + taskListUserPassword: "demo" + taskListUrl: "http://camunda-tasklist:80" + workerExecutionThreads: 200 + # -1 means : align the jobsActive to the workerExecutionThreads + workerMaxJobsActive: -1 + - type: "camunda8" name: "Camunda8Lazuli" description: "A Zeebe+Identity server" diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index a349ce2..f1be3d6 100644 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -5,4 +5,4 @@ __________ __ |____| |__| \____/ \___ >___ >____ >____ > (____ /____/ |__| \____/|__|_| (____ /__| \____/|__| \/ \/ \/ \/ \/ \/ \/ - (v1.7.0) + (v1.8.7) From f35800d223d411a284d187e038002fab9a018263 Mon Sep 17 00:00:00 2001 From: Pierre-yves-monnet <pierre-yves.lonnet@laposte.net> Date: Fri, 6 Dec 2024 11:12:59 -0800 Subject: [PATCH 4/5] Migrate to last version of API --- .../java/org/camunda/automator/AutomatorRest.java | 15 ++++++++------- .../bpmnengine/camunda8/BpmnEngineCamunda8.java | 2 +- .../bpmnengine/camunda8/OperateClient.java | 2 +- .../automator/definition/ScenarioExecution.java | 3 +-- .../engine/unit/RunScenarioUnitServiceTask.java | 1 + .../automator/services/AutomatorStartup.java | 7 +++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/camunda/automator/AutomatorRest.java b/src/main/java/org/camunda/automator/AutomatorRest.java index 5a7003d..0c6fb8c 100644 --- a/src/main/java/org/camunda/automator/AutomatorRest.java +++ b/src/main/java/org/camunda/automator/AutomatorRest.java @@ -34,6 +34,7 @@ public class AutomatorRest { public static final String JSON_MESSAGE = "message"; public static final String JSON_INFO = "info"; private static final Logger logger = LoggerFactory.getLogger(AutomatorRest.class.getName()); + public static final String JSON_STATUS_V_NOTEXIST = "NOTEXIST"; private final ConfigurationStartup configurationStartup; private final ContentManager contentManager; private final AutomatorAPI automatorAPI; @@ -76,7 +77,7 @@ public Map<String, Object> getUnitTest(@RequestParam(name = "id") String unitTes if (resultTest != null) { return resultTest; } else { - return Map.of("status", "NOTEXIST"); + return Map.of(AutomatorRest.JSON_STATUS, JSON_STATUS_V_NOTEXIST); } } @@ -85,7 +86,7 @@ public List<Map<String, Object>> getListUnitTest() { List<Map<String, Object>> listUnitTest = new ArrayList<>(); for (Map.Entry entryTest : cacheExecution.entrySet()) { if (entryTest.getValue() instanceof Map<?, ?> resultMap) { - listUnitTest.add(Map.of("id", entryTest.getKey(), + listUnitTest.add(Map.of(AutomatorRest.JSON_ID, entryTest.getKey(), JSON_SCENARIO_NAME, getSecureValue(resultMap.get(JSON_SCENARIO_NAME)))); } else { listUnitTest.add(Map.of(JSON_ID, entryTest.getKey(), @@ -159,8 +160,8 @@ private void startTest(String scenarioName, String serverName, String unitTestId } /** - * @param runResult - * @return + * @param runResult result to transform in JSON + * @return result ready for a JSON format */ private Map<String, Object> resultToJson(RunResult runResult) { Map<String, Object> resultMap = new HashMap<>(); @@ -198,9 +199,9 @@ private Map<String, Object> completeMessage(Map<String, Object> result, StatusTe * Connect to the BPM Engine * * @param scenario scenario to use to connect - * @param runParameters - * @param result - * @return + * @param runParameters running parameters + * @param result result of the connection + * @return BPMN Engine */ private BpmnEngine connectToEngine(Scenario scenario, RunParameters runParameters, Map<String, Object> result) { BpmnEngine bpmnEngine = null; diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java index 9240704..4d9ec1a 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java @@ -382,7 +382,7 @@ public void throwBpmnServiceTask(String serviceTaskId, * @param filterTaskId filter on the taskId * @param maxResult maximum Result * @return list of Task - * @throws AutomatorException + * @throws AutomatorException in case of error */ @Override public List<TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String filterTaskId, int maxResult) diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java index a4011df..0f5c220 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java @@ -198,7 +198,7 @@ public List<String> activateServiceTasks(String processInstanceId, String servic * @param filterTaskId filter on the taskId * @param maxResult maximum Result * @return list of Task - * @throws AutomatorException + * @throws AutomatorException in case of error */ public List<BpmnEngine.TaskDescription> searchTasksByProcessInstanceId(String processInstanceId, String filterTaskId, int maxResult) throws AutomatorException { diff --git a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java index e49e5a0..4aa0554 100644 --- a/src/main/java/org/camunda/automator/definition/ScenarioExecution.java +++ b/src/main/java/org/camunda/automator/definition/ScenarioExecution.java @@ -1,7 +1,6 @@ package org.camunda.automator.definition; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -77,7 +76,7 @@ public ScenarioExecution addStep(ScenarioStep step) { } public List<ScenarioStep> getSteps() { - return steps == null ? Collections.emptyList() : steps; + return steps; } public ScenarioVerification getVerifications() { diff --git a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java index 9e4a310..5e6ddca 100644 --- a/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java +++ b/src/main/java/org/camunda/automator/engine/unit/RunScenarioUnitServiceTask.java @@ -62,6 +62,7 @@ public RunResult executeServiceTask(ScenarioStep step, RunResult result) { try { Thread.sleep(500); } catch (InterruptedException e) { + // nothing to do here } } } while (listActivities.isEmpty() && System.currentTimeMillis() - beginTimeWait < waitingTimeInMs); diff --git a/src/main/java/org/camunda/automator/services/AutomatorStartup.java b/src/main/java/org/camunda/automator/services/AutomatorStartup.java index 5f48609..11a776f 100644 --- a/src/main/java/org/camunda/automator/services/AutomatorStartup.java +++ b/src/main/java/org/camunda/automator/services/AutomatorStartup.java @@ -24,7 +24,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +import java.util.Objects; @Service public class AutomatorStartup { @@ -101,7 +101,6 @@ private List<Path> loadStartupScenario() { } else { logger.error("AutomatorStartup/StartupScenario:: Can't find File [{}/{}] or [{}]", configurationStartup.scenarioPath, scenarioFileName, scenarioFileName); - continue; } } @@ -113,8 +112,8 @@ private List<Path> loadStartupScenario() { configurationStartup.getScenarioResourceAtStartupName()); } else { List<Resource> scenarioResource = configurationStartup.getScenarioResourceAtStartup().stream() - .filter(t -> t != null) - .collect(Collectors.toList()); + .filter(Objects::nonNull) + .toList(); logger.info("Detect {} scenario [Resource] from variable [{}]", scenarioResource.size(), From a74a59ffc369c9ef1c55b35ae076c7d49c25f040 Mon Sep 17 00:00:00 2001 From: Pierre-yves-monnet <pierre-yves.lonnet@laposte.net> Date: Mon, 9 Dec 2024 17:04:08 -0800 Subject: [PATCH 5/5] Test with last version of API --- doc/scenarioreference/README.md | 9 ++- .../resources/ScoreAcceptance.bpmn | 18 +++-- .../resources/ScoreAcceptanceScn.json | 14 +++- pom.xml | 25 +------ .../camunda8/BpmnEngineCamunda8.java | 3 + .../bpmnengine/camunda8/OperateClient.java | 14 ++-- .../bpmnengine/camunda8/TaskListClient.java | 55 ++------------ .../automator/content/RepositoryManager.java | 14 ++-- .../camunda/automator/engine/RunResult.java | 9 +-- .../dataoperation/DataOperationNow.java | 73 +++++++++++++++++++ 10 files changed, 131 insertions(+), 103 deletions(-) create mode 100644 src/main/java/org/camunda/automator/services/dataoperation/DataOperationNow.java diff --git a/doc/scenarioreference/README.md b/doc/scenarioreference/README.md index 550f751..a51e5ca 100644 --- a/doc/scenarioreference/README.md +++ b/doc/scenarioreference/README.md @@ -270,7 +270,7 @@ the variable `loopcrawl` will be a list of 500 random string. **generateuniqueid(<Prefix>)** Generate a unique sequential number. -The prefix is used to allo wmultiple counter +The prefix is used to allow multiple counter Example: ```` "tidblue": "generateuniqueid(blue)" @@ -278,6 +278,13 @@ Example: ```` Variables `tidblue` and `tidred` got a unique id, each following a different counter. +**now(LOCALDATETIME|DATE|ZONEDATETIME|LOCALDATE)** +Generate a String object, containing the current date and time. + + +**stringToDate(LOCALDATETIME|DATE|ZONEDATETIME|LOCALDATE, dateSt)** +Transform a String to a Date (LocalDateTime, Date, ZoneDateTime or LocalDate) + ## Verification diff --git a/doc/unittestscenario/resources/ScoreAcceptance.bpmn b/doc/unittestscenario/resources/ScoreAcceptance.bpmn index f7f46ec..a4cecb8 100644 --- a/doc/unittestscenario/resources/ScoreAcceptance.bpmn +++ b/doc/unittestscenario/resources/ScoreAcceptance.bpmn @@ -29,10 +29,6 @@ <bpmn:incoming>Flow_09z898p</bpmn:incoming> </bpmn:endEvent> <bpmn:sequenceFlow id="Flow_09z898p" sourceRef="ActSendAcceptation" targetRef="EndAccepted" /> - <bpmn:task id="ActSendRejection" name="Send rejection"> - <bpmn:incoming>Flow_0wya675</bpmn:incoming> - <bpmn:outgoing>Flow_1pxztl2</bpmn:outgoing> - </bpmn:task> <bpmn:sequenceFlow id="Flow_0a4sjzy" name="Rejected" sourceRef="Gateway_01ox9rs" targetRef="CallApplicant" /> <bpmn:endEvent id="EndRejected" name="rejected"> <bpmn:incoming>Flow_1pxztl2</bpmn:incoming> @@ -44,6 +40,13 @@ <bpmn:incoming>Flow_0a4sjzy</bpmn:incoming> <bpmn:outgoing>Flow_0wya675</bpmn:outgoing> </bpmn:userTask> + <bpmn:serviceTask id="ActSendRejection" name="Send rejection"> + <bpmn:extensionElements> + <zeebe:taskDefinition type="send-rejection" /> + </bpmn:extensionElements> + <bpmn:incoming>Flow_0wya675</bpmn:incoming> + <bpmn:outgoing>Flow_1pxztl2</bpmn:outgoing> + </bpmn:serviceTask> </bpmn:process> <bpmndi:BPMNDiagram id="BPMNDiagram_1"> <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ScoreAcceptance"> @@ -79,14 +82,13 @@ <dc:Bounds x="870" y="315" width="40" height="14" /> </bpmndi:BPMNLabel> </bpmndi:BPMNShape> - <bpmndi:BPMNShape id="Activity_1dmaofg_di" bpmnElement="ActSendRejection"> - <dc:Bounds x="710" y="250" width="100" height="80" /> - <bpmndi:BPMNLabel /> - </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_1kkk9hs_di" bpmnElement="CallApplicant"> <dc:Bounds x="530" y="250" width="100" height="80" /> <bpmndi:BPMNLabel /> </bpmndi:BPMNShape> + <bpmndi:BPMNShape id="Activity_0bhloo7_di" bpmnElement="ActSendRejection"> + <dc:Bounds x="710" y="250" width="100" height="80" /> + </bpmndi:BPMNShape> <bpmndi:BPMNEdge id="Flow_1wuzgpt_di" bpmnElement="Flow_1wuzgpt"> <di:waypoint x="228" y="180" /> <di:waypoint x="280" y="180" /> diff --git a/doc/unittestscenario/resources/ScoreAcceptanceScn.json b/doc/unittestscenario/resources/ScoreAcceptanceScn.json index 81546bb..bd37dea 100644 --- a/doc/unittestscenario/resources/ScoreAcceptanceScn.json +++ b/doc/unittestscenario/resources/ScoreAcceptanceScn.json @@ -77,12 +77,24 @@ "variables": { "phoneNumber": "(+1) 542 778 2352" } + }, + { + "type": "SERVICETASK", + "taskId": "ActSendRejection", + "topic": "send-rejection", + "waitingTime": "PT0S", + "modeExecution": "ASYNCHRONOUS", + "variablesOperation": { + "sendMessage": "now(LocalDateTime)" + } } + + ], "verifications": { "activities": [ { - "type": "TASK", + "type": "SERVICETASK", "taskId": "ActSendRejection" }, { diff --git a/pom.xml b/pom.xml index 4764ae2..06480bd 100644 --- a/pom.xml +++ b/pom.xml @@ -64,28 +64,6 @@ <dependencies> - <!-- Camunda operate client is included --> - <!-- - 8.5 : OK - <version.spring-boot-starter-camunda>8.5.7</version.zeebe> - <version.zeebe-client>8.5.5</version.zeebe-client> - - 8.5.11 : ? - - <dependency> - <groupId>io.camunda.spring</groupId> - <artifactId>spring-boot-starter-camunda</artifactId> - <version>8.5.7</version> - </dependency> - - <dependency> - <groupId>io.camunda</groupId> - <artifactId>zeebe-client-java</artifactId> - <version>8.5.7</version> - </dependency> ---> - - <dependency> <groupId>io.camunda</groupId> <artifactId>spring-boot-starter-camunda-sdk</artifactId> @@ -96,7 +74,7 @@ <dependency> <groupId>io.camunda.spring</groupId> <artifactId>java-client-operate</artifactId> - <version>8.6.2</version> + <version>8.6.3</version> </dependency> @@ -106,7 +84,6 @@ 8.6.6 : incompatible types: io.camunda.common.auth.Authentication cannot be converted to io.camunda.tasklist.auth.Authentication 8.5.10 Bug fix on ZeebeUserTask (actually, no) 8.6.6 last version available--> - <dependency> <groupId>io.camunda</groupId> <artifactId>camunda-tasklist-client-java</artifactId> diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java index 4d9ec1a..9eaec4c 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/BpmnEngineCamunda8.java @@ -346,6 +346,7 @@ public void executeServiceTask(String serviceTaskId, String workerId, Map<String try { zeebeClient.newCompleteCommand(Long.valueOf(serviceTaskId)).variables(variables).send().join(); } catch (Exception e) { + logger.error("executeServiceTask:Can't execute service task[{}] WorkerId[{}] : {}", serviceTaskId, workerId, e.getMessage()); throw new AutomatorException("Can't execute service task " + e.getMessage()); } } @@ -373,6 +374,7 @@ public void throwBpmnServiceTask(String serviceTaskId, .send() .join(); } catch (Exception e) { + logger.error("throwBpmnServiceTask: Can't execute service task[{}] WorkerId[{}] errorCode[{}] errorMessage[{}]: {}", serviceTaskId, workerId, errorCode, errorMessage, e.getMessage()); throw new AutomatorException("Can't execute service task " + e.getMessage()); } } @@ -443,6 +445,7 @@ public String deployBpmn(File processFile, ScenarioDeployment.Policy policy) thr return String.valueOf(event.getKey()); } catch (Exception e) { + logger.error("deployBpmn: Can't deploy File[{}] Policy[{}] : {}", processFile.getAbsolutePath(), policy, e.getMessage()); throw new AutomatorException("Can't deploy " + e.getMessage()); } } diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java index 0f5c220..9b4e483 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/OperateClient.java @@ -80,7 +80,7 @@ protected void connectOperate(StringBuilder analysis) throws AutomatorException } catch (Exception e) { - logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e.getMessage()); throw new AutomatorException( "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + e.getMessage()); @@ -125,7 +125,7 @@ protected void connectOperate(StringBuilder analysis) throws AutomatorException configuration = new CamundaOperateClientConfiguration(authentication, operateUrl, objectMapper, HttpClients.createDefault()); } } catch (Exception e) { - logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e.getMessage()); throw new AutomatorException( "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + e.getMessage()); @@ -145,7 +145,7 @@ protected void connectOperate(StringBuilder analysis) throws AutomatorException analysis.append("successfully, "); } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e.getMessage()); throw new AutomatorException( "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); } @@ -220,8 +220,8 @@ public List<BpmnEngine.TaskDescription> searchTasksByProcessInstanceId(String pr BpmnEngine.TaskDescription taskDescription = new BpmnEngine.TaskDescription(); taskDescription.taskId = t.getFlowNodeId(); taskDescription.processInstanceId = String.valueOf(t.getProcessInstanceKey()); - taskDescription.startDate = t.getStartDate().getDate(); - taskDescription.endDate = t.getEndDate().getDate(); + taskDescription.startDate = t.getStartDate()==null? null: t.getStartDate().getDate(); + taskDescription.endDate = t.getEndDate()==null? null : t.getEndDate().getDate(); taskDescription.type = getTaskType(t.getType()); // to implement taskDescription.isCompleted = FlowNodeInstanceState.COMPLETED.equals(t.getState()); // to implement return taskDescription; @@ -337,7 +337,7 @@ public long countNumberOfProcessInstancesCreated(String processId, Date startDat searchQuery.setSize(SEARCH_MAX_SIZE); searchResult = operateClient.searchProcessInstanceResults(searchQuery); - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().getDate().after(startDate)).count(); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate()!=null && t.getStartDate().getDate().after(startDate)).count(); } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); return cumul; @@ -372,7 +372,7 @@ public long countNumberOfProcessInstancesEnded(String processId, Date startDate, SearchQuery searchQuery = queryBuilder.build(); searchQuery.setSize(SEARCH_MAX_SIZE); searchResult = operateClient.searchProcessInstanceResults(searchQuery); - cumul += searchResult.getItems().stream().filter(t -> t.getStartDate().getDate().after(startDate)).count(); + cumul += searchResult.getItems().stream().filter(t -> t.getStartDate() !=null && t.getStartDate().getDate().after(startDate)).count(); } while (searchResult.getItems().size() >= SEARCH_MAX_SIZE && maxLoop < 1000); return cumul; diff --git a/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java b/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java index 3fe8ea2..49a193e 100644 --- a/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java +++ b/src/main/java/org/camunda/automator/bpmnengine/camunda8/TaskListClient.java @@ -61,7 +61,7 @@ public void connectTaskList(StringBuilder analysis) throws AutomatorException { taskListBuilder.taskListUrl(taskListUrl) .saaSAuthentication(serverDefinition.taskListClientId, serverDefinition.taskListClientSecret); } catch (Exception e) { - logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + logger.error("Can't connect to SaaS environemnt[{}] Analysis:{} : {}", serverDefinition.name, analysis, e.getMessage()); throw new AutomatorException( "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " + e.getMessage()); @@ -107,56 +107,10 @@ public void connectTaskList(StringBuilder analysis) throws AutomatorException { analysis.append("successfully, "); } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); + logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e.getMessage()); throw new AutomatorException( "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); } - - /* 1.6.1 - boolean isOk = true; - io.camunda.tasklist.auth.AuthInterface saTaskList; - - // ---------------------------- Camunda Saas - if (BpmnEngineList.CamundaEngine.CAMUNDA_8_SAAS.equals(this.typeCamundaEngine)) { - try { - saTaskList = new io.camunda.tasklist.auth.SaasAuthentication(serverDefinition.zeebeSaasClientId, - serverDefinition.zeebeSaasClientSecret); - } catch (Exception e) { - logger.error("Can't connect to SaaS environment[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to SaaS environment[" + serverDefinition.name + "] Analysis:" + analysis + " fail : " - + e.getMessage()); - } - - //---------------------------- Camunda 8 Self Manage - } else if (BpmnEngineList.CamundaEngine.CAMUNDA_8.equals(this.typeCamundaEngine)) { - saTaskList = new io.camunda.tasklist.auth.SimpleAuthentication(serverDefinition.operateUserName, - serverDefinition.operateUserPassword); - } else - throw new AutomatorException("Invalid configuration"); - - if (!isOk) - throw new AutomatorException("Invalid configuration " + analysis); - - // ---------------- connection - try { - isOk = engineCamunda8.stillOk(serverDefinition.taskListUrl, "taskListUrl", analysis, false, isOk); - analysis.append("Tasklist ..."); - - taskClient = new CamundaTaskListClient.Builder().taskListUrl(serverDefinition.taskListUrl) - .authentication(saTaskList) - .build(); - analysis.append("successfully, "); - //get tasks assigned to demo - logger.info("Zeebe: OK, Operate: OK, TaskList:OK " + analysis); - - } catch (Exception e) { - logger.error("Can't connect to Server[{}] Analysis:{} : {}", serverDefinition.name, analysis, e); - throw new AutomatorException( - "Can't connect to Server[" + serverDefinition.name + "] Analysis:" + analysis + " Fail : " + e.getMessage()); - } - */ - } public List<String> searchUserTasksByProcessInstance(String processInstanceId, String userTaskId, int maxResult) @@ -199,7 +153,7 @@ public List<String> searchUserTasksByProcessInstance(String processInstanceId, S return listTasksResult; } catch (TaskListException e) { - logger.error("TaskListClient: error during search task [{}]", e.getMessage()); + logger.error("TaskListClient: error during search task: {}", e.getMessage()); throw new AutomatorException("Can't search users task " + e.getMessage()); } } @@ -227,6 +181,7 @@ public List<String> searchUserTasks(String userTaskId, int maxResult) throws Aut return listTasksResult; } catch (TaskListException e) { + logger.error("SearchUserTask: userId[{}] : {}", userTaskId, e.getMessage()); throw new AutomatorException("Can't search users task " + e.getMessage()); } } @@ -237,8 +192,10 @@ public void executeUserTask(String userTaskId, String userId, Map<String, Object taskClient.claim(userTaskId, engineCamunda8.getServerDefinition().operateUserName); taskClient.completeTask(userTaskId, variables); } catch (TaskListException e) { + logger.error("ExecuteUserTask: taskId[{}] userId[{}] : {}", userTaskId, userId, e.getMessage()); throw new AutomatorException("Can't execute task [" + userTaskId + "]"); } catch (Exception e) { + logger.error("ExecuteUserTask: Exception on taskId[{}] userId[{}] : {}", userTaskId, userId, e.getMessage()); throw new AutomatorException("Can't execute task [" + userTaskId + "]"); } } diff --git a/src/main/java/org/camunda/automator/content/RepositoryManager.java b/src/main/java/org/camunda/automator/content/RepositoryManager.java index 55a4403..08bd177 100644 --- a/src/main/java/org/camunda/automator/content/RepositoryManager.java +++ b/src/main/java/org/camunda/automator/content/RepositoryManager.java @@ -49,37 +49,35 @@ public void initializeRepository(String repositoryProposition) throws AutomatorE } public Path addResource(Resource resource) throws IOException { + if (resource==null) + return null; Path targetPath = repositoryPath.resolve(resource.getFilename()); Files.copy(resource.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING); - logger.info("CopiedReource: [{}] tp [{}]", resource.getFilename(), targetPath); + logger.info("CopiedResource: [{}] to [{}]", resource.getFilename(), targetPath); return targetPath; } public Path addFile(Path sourcePath) throws IOException { Path sourceFileName = sourcePath.getFileName(); // Get the directory from targetPath - Path targetDir = repositoryPath.getParent(); // Combine the directory of targetPath with the filename of sourcePath - Path targetPath = targetDir.resolve(sourceFileName); + Path targetPath = repositoryPath.resolve(sourceFileName); Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); - logger.info("CopiedFile: [{}] tp [{}]", sourcePath, targetPath); + logger.info("CopiedFile: [{}] to [{}]", sourcePath, targetPath); return targetPath; } public Path addFromInputStream(InputStream inputStream, String fileName) throws IOException { OutputStream outputStream = null; - File fileContent = null; try { - fileContent = new File(repositoryPath + File.separator + fileName); + File fileContent = new File(repositoryPath + File.separator + fileName); // Open an OutputStream to the temporary file outputStream = new FileOutputStream(fileContent); // Transfer data from InputStream to OutputStream byte[] buffer = new byte[1024 * 100]; // 100Ko int bytesRead; - int count = 0; while ((bytesRead = inputStream.read(buffer)) != -1) { - count += bytesRead; outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); diff --git a/src/main/java/org/camunda/automator/engine/RunResult.java b/src/main/java/org/camunda/automator/engine/RunResult.java index 54a764e..13322d3 100644 --- a/src/main/java/org/camunda/automator/engine/RunResult.java +++ b/src/main/java/org/camunda/automator/engine/RunResult.java @@ -147,7 +147,6 @@ public List<ErrorDescription> getListErrors() { public void addError(ScenarioStep step, String explanation) { this.listErrors.add(new ErrorDescription(step, explanation)); logger.error((step == null ? "" : step.getType().toString()) + " " + explanation); - } public void addError(ScenarioStep step, AutomatorException e) { @@ -205,7 +204,7 @@ public void merge(RunResult result) { /** * Two execution on the exact same execution: we go for a merge plus one step more, we collect additionnal information, like processinstanceIdList - * @param result + * @param result the result to merge */ public void mergeDuplicateExecution(RunResult result) { merge(result); @@ -245,8 +244,8 @@ public List<String> getListProcessInstancesId() { return listProcessInstancesId; } - public void addListProcessInstancesId(List<String> listProcessInstancesId) { - listProcessInstancesId.addAll(listProcessInstancesId); + public void addListProcessInstancesId(List<String> listProcessInstancesIdParam) { + listProcessInstancesId.addAll(listProcessInstancesIdParam); } public List<String> getProcessInstanceId() { return this.listProcessInstancesId; @@ -310,7 +309,7 @@ public String getSynthesis(boolean fullDetail) { synthesis.append(runScenario.getScenario().getProcessId()); synthesis.append("): "); - StringBuilder append = synthesis.append(timeExecution); + synthesis.append(timeExecution); synthesis.append(" timeExecution(ms), "); RecordCreationPI recordCreationPI = recordCreationPIMap.get(runScenario.getScenario().getProcessId()); synthesis.append(recordCreationPI == null ? 0 : recordCreationPI.nbCreated); diff --git a/src/main/java/org/camunda/automator/services/dataoperation/DataOperationNow.java b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationNow.java new file mode 100644 index 0000000..e96d2dc --- /dev/null +++ b/src/main/java/org/camunda/automator/services/dataoperation/DataOperationNow.java @@ -0,0 +1,73 @@ +package org.camunda.automator.services.dataoperation; + +import org.camunda.automator.engine.AutomatorException; +import org.camunda.automator.engine.RunScenario; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +@Component + +public class DataOperationNow extends DataOperation { + + public static final String FCT_LOCALDATETIME = "LOCALDATETIME"; + public static final String FCT_DATE = "DATE"; + public static final String FCT_ZONEDATETIME = "ZONEDATETIME"; + public static final String FCT_LOCALDATE = "LOCALDATE"; + public static final String ISO_8601_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + public static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd"; + + @Override + public boolean match(String value) { + return matchFunction(value, "now"); + } + + @Override + public String getName() { + return "Now"; + } + + @Override + public String getHelp() { + return "now(" + FCT_LOCALDATETIME + "|" + FCT_DATE + "|" + FCT_ZONEDATETIME + "|" + + FCT_LOCALDATE + ")"; + } + + @Override + public Object execute(String value, RunScenario runScenario, int index) throws AutomatorException { + List<String> args = extractArgument(value, true); + + if (args.size() != 1) { + throw new AutomatorException("Bad argument: " + getHelp()); + } + String formatArgs = args.get(0).toUpperCase(Locale.ROOT); + + try { + if (FCT_LOCALDATETIME.equals(formatArgs)) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_8601_DATETIME_FORMAT); + return LocalDateTime.now().format(formatter); + } else if (FCT_DATE.equals(formatArgs)) { + SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_DATETIME_FORMAT); + return dateFormat.format(new Date()); + } else if (FCT_ZONEDATETIME.equals(formatArgs)) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_8601_DATETIME_FORMAT); + return ZonedDateTime.now().format(formatter); + } else if (FCT_LOCALDATE.equals(formatArgs)) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_8601_DATE_FORMAT); + return LocalDate.now().format(formatter); + } else + throw new AutomatorException("Unknown date formatter [" + formatArgs + "]"); + } catch (Exception e) { + throw new AutomatorException( + "parsing error function[" + formatArgs + "] : " + e.getMessage()); + } + + } +}