diff --git a/pom.xml b/pom.xml index 2e7492b..53edb5b 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,15 @@ 17 ${java.version} ${java.version} - 8.1.0 + + 8.1.4 + + + 0.3.0 + + 0.2.2 + 0.2.2 + 5.9.1 2.0.3 2.7.4 @@ -66,25 +74,21 @@ io.camunda.connector connector-core - 0.2.0 - - - io.camunda.connector - connector-runtime-job-worker - 0.2.2 + ${connector-core.version} + provided + + + io.camunda.connector connector-validation - 0.2.2 - - - - jakarta.validation - jakarta.validation-api - 3.0.2 + ${connector-validation.version} + provided + + com.fasterxml.jackson.datatype @@ -147,7 +151,7 @@ io.zeebe zeebe-worker-java-testutils - 8.0.0 + 8.1.3 test diff --git a/src/main/java/io/camunda/cherry/admin/RunnerInformation.java b/src/main/java/io/camunda/cherry/admin/RunnerInformation.java index 2dfbbfc..620e199 100644 --- a/src/main/java/io/camunda/cherry/admin/RunnerInformation.java +++ b/src/main/java/io/camunda/cherry/admin/RunnerInformation.java @@ -1,6 +1,6 @@ /* ******************************************************************** */ /* */ -/* WorkerInformation */ +/* RunnerInformation */ /* */ /* Collect worker information from a worker. */ /* This class works as a facade. It's easy then to get the JSON */ @@ -110,7 +110,8 @@ public void setDisplayLogo(boolean displayLogo) { * @return the list of errors */ public String getDefinitionErrors() { - return String.join(", ", runner.getDefinitionErrors()); + return String.join(", ", runner.checkValidDefinition()); + } public enum TYPE_RUNNER {WORKER, CONNECTOR} diff --git a/src/main/java/io/camunda/cherry/admin/RunnerRestController.java b/src/main/java/io/camunda/cherry/admin/RunnerRestController.java index 593c3ff..15413e2 100644 --- a/src/main/java/io/camunda/cherry/admin/RunnerRestController.java +++ b/src/main/java/io/camunda/cherry/admin/RunnerRestController.java @@ -116,7 +116,7 @@ public RunnerInformation stopWorker(@RequestParam(name = "name") String runnerNa RunnerInformation runnerInfo = RunnerInformation.getRunnerInformation(runner); return completeRunnerInformation(runnerInfo, false, false, null); } catch (CherryJobRunnerFactory.OperationException e) { - if (e.exceptionCode.equals(CherryJobRunnerFactory.WORKER_NOT_FOUND)) + if (CherryJobRunnerFactory.RUNNER_NOT_FOUND.equals(e.getExceptionCode())) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "WorkerName [" + runnerName + "] not found"); throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "WorkerName [" + runnerName + "] error " + e); } @@ -138,7 +138,7 @@ public RunnerInformation startWorker(@RequestParam(name = "name") String runnerN RunnerInformation runnerInfo = RunnerInformation.getRunnerInformation(runner); return completeRunnerInformation(runnerInfo, false, false, null); } catch (CherryJobRunnerFactory.OperationException e) { - if (e.exceptionCode.equals(CherryJobRunnerFactory.WORKER_NOT_FOUND)) + if (CherryJobRunnerFactory.RUNNER_NOT_FOUND.equals(e.getExceptionCode())) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "WorkerName [" + runnerName + "] not found"); throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "WorkerName [" + runnerName + "] error " + e); } diff --git a/src/main/java/io/camunda/cherry/definition/AbstractConnectorInput.java b/src/main/java/io/camunda/cherry/definition/AbstractConnectorInput.java index af4efaa..c8cceab 100644 --- a/src/main/java/io/camunda/cherry/definition/AbstractConnectorInput.java +++ b/src/main/java/io/camunda/cherry/definition/AbstractConnectorInput.java @@ -22,4 +22,15 @@ public List getInputParameters() { return Collections.emptyList(); } + /** + * Create the list and give a class. + * If the Cherry input connector is created from a basic connector, give the Input connector. + * The Cherry will be able to verify the list againts the Input: all fields are declared? All RunnerParameters exists as a member in the class? + */ + public record InputParametersInfo (List listRunners, Class inputClass){} + + public InputParametersInfo getInputParametersInfo() { + return new InputParametersInfo(Collections.emptyList(), null); + } + } diff --git a/src/main/java/io/camunda/cherry/definition/AbstractRunner.java b/src/main/java/io/camunda/cherry/definition/AbstractRunner.java index 9c0ac6d..39e6e08 100644 --- a/src/main/java/io/camunda/cherry/definition/AbstractRunner.java +++ b/src/main/java/io/camunda/cherry/definition/AbstractRunner.java @@ -25,10 +25,12 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.File; +import java.lang.reflect.Field; import java.time.Duration; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; public abstract class AbstractRunner { @@ -688,7 +690,7 @@ private boolean containsKeyInJob(String parameterName, final ActivatedJob activa * * @param parameterName parameter to get the value * @param activatedJob activated job - * @return + * @return an object */ protected Object getValueFromJob(String parameterName, final ActivatedJob activatedJob) { if (activatedJob.getVariablesAsMap().containsKey(parameterName)) @@ -739,17 +741,6 @@ public List getListBpmnErrors() { return listBpmnErrors; } - /** - * Check parameters. If something is not correct in the definition, then throw an error - * - * @return a list of errors - */ - public List getDefinitionErrors() { - List listOfErrors = new ArrayList<>(); - listOfErrors.addAll(checkListParameters(listInput)); - listOfErrors.addAll(checkListParameters(listOutput)); - return listOfErrors; - } /** * Return the list of variable to fetch if this is possible, else null. * To calculate the list: @@ -825,7 +816,7 @@ public String getDescription() { * Image must be a string like * "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18.0' viewBox='0 0 18 18.0' %3E%3Cg id='XMLID_238_'%3E %3Cpath id='XMLID_239_' d='m 14.708846 10.342394 c -1.122852 0.0,-2.528071 0.195852,-2.987768 0.264774 C 9.818362 8.6202,9.277026 7.4907875,9.155265 7.189665 C 9.320285 6.765678,9.894426 5.155026,9.976007 3.0864196 C 10.016246 2.0507226,9.797459 1.2768387,9.325568 .... -0.00373,0.03951 0.00969,0.0425 0.030567 z'/%3E%3C/svg%3E"; * - * @return + * @return the logo */ public String getLogo() { return logo; @@ -856,10 +847,51 @@ private boolean isStringEmpty(String value) { * * @return */ - public boolean isValidDefinition() { - return (getIdentification().isEmpty()); + public List checkValidDefinition() { + List listOfErrors= new ArrayList<>(); + if (getIdentification().isEmpty()) + listOfErrors.add("No identification"); + + if (this instanceof AbstractConnector) { + AbstractConnectorInput.InputParametersInfo parameterInfo=((AbstractConnector) this).getAbstractConnectorInput().getInputParametersInfo(); + if (parameterInfo!=null && ! parameterInfo.listRunners().isEmpty() && parameterInfo.inputClass()!=null) + listOfErrors.addAll(confrontParameterWithClass( parameterInfo.inputClass(), parameterInfo.listRunners())); + } + + listOfErrors.addAll(checkListParameters(listInput)); + listOfErrors.addAll(checkListParameters(listOutput)); + + return listOfErrors; } + /** + * Confront a list of RunnerParameter with a class. + * @param clazz class to confront + * @param parameters list of Runner. + * @return empty is every thing is OK, else an analysis + */ + private List confrontParameterWithClass(Class clazz, List parameters) { + List listOfErrors= new ArrayList<>(); + + Field[] fields = clazz.getDeclaredFields(); + // All fields are part of parameters? + for (Field field: fields) { + long number = parameters.stream().filter(t -> t.getName().equals(field.getName())).count(); + if (number != 1) + listOfErrors.add("Class Field[" + field.getName() + "] is not part of parameters"); + } + + // All parameters must be part of the fields + for (RunnerParameter parameter : parameters) { + if (parameter.getName().equals("*")) + continue; + long number = Stream.of(fields).filter(t -> t.getName().equals(parameter.getName())).count(); + if (number != 1) + listOfErrors.add("Parameter[" + parameter.getName() + "] is not part of fields in the class"); + } + + return listOfErrors; + } private List checkListParameters(List listParameters) { diff --git a/src/main/java/io/camunda/cherry/ping/PingConnectorInput.java b/src/main/java/io/camunda/cherry/ping/PingConnectorInput.java index 559d466..7b976ad 100644 --- a/src/main/java/io/camunda/cherry/ping/PingConnectorInput.java +++ b/src/main/java/io/camunda/cherry/ping/PingConnectorInput.java @@ -6,7 +6,8 @@ import java.util.Arrays; import java.util.List; -import jakarta.validation.constraints.NotEmpty; +import javax.validation.constraints.NotEmpty; + public class PingConnectorInput extends AbstractConnectorInput { diff --git a/src/main/java/io/camunda/cherry/runtime/CherryJobRunnerFactory.java b/src/main/java/io/camunda/cherry/runtime/CherryJobRunnerFactory.java index 9da51f0..7738ad7 100644 --- a/src/main/java/io/camunda/cherry/runtime/CherryJobRunnerFactory.java +++ b/src/main/java/io/camunda/cherry/runtime/CherryJobRunnerFactory.java @@ -6,7 +6,7 @@ /* ******************************************************************** */ package io.camunda.cherry.runtime; -import io.camunda.connector.runtime.jobworker.api.outbound.ConnectorJobHandler; +import io.camunda.connector.runtime.util.outbound.ConnectorJobHandler; import io.camunda.zeebe.client.api.worker.JobWorker; import io.camunda.zeebe.client.api.worker.JobWorkerBuilderStep1; import io.camunda.cherry.definition.AbstractConnector; @@ -26,7 +26,7 @@ @Service public class CherryJobRunnerFactory { - public static final String WORKER_NOT_FOUND = "WorkerNotFound"; + public static final String RUNNER_NOT_FOUND = "WorkerNotFound"; public static final String UNKNOWN_WORKER_CLASS = "UnknownWorkerClass"; public static final String WORKER_INVALID_DEFINITION = "WORKER_INVALID_DEFINITION"; @@ -56,9 +56,9 @@ public void startAll() { for (AbstractRunner runner : listRunners) { - String errors = String.join(", ", runner.getDefinitionErrors()); - if (!errors.isEmpty()) { - logger.error("Runner [" + runner.getIdentification() + "] can't start, errors " + errors); + List listOfErrors = runner.checkValidDefinition(); + if (!listOfErrors.isEmpty()) { + logger.error("Runner [" + runner.getIdentification() + "] can't start, errors " + String.join(", ",listOfErrors)); continue; } @@ -83,11 +83,9 @@ public void stopAll() { if (running.runner != null) { try { stopRunner(running.runner.getIdentification()); - } catch (OperationException e) { - logger.error("Error on worker [" + running.runner.getIdentification() + "]"); } catch (Exception e) { - logger.error("Error on worker [" + running.runner.getIdentification() + "]"); + logger.error("Error on runner [" + running.runner.getIdentification() + "] : "+e); } } } @@ -104,12 +102,12 @@ public void stopAll() { public boolean stopRunner(String runnerName) throws OperationException { for (Running running : listRunnerRunning) { if (running.runner().getIdentification().equals(runnerName)) { - closeJobWorker(running.containerJobWorker.jobWorker); - running.containerJobWorker.jobWorker = null; + closeJobWorker(running.containerJobWorker.getJobWorker()); + running.containerJobWorker.setJobWorker(null); return true; } } - throw new OperationException(WORKER_NOT_FOUND, "Worker not found"); + throw new OperationException(RUNNER_NOT_FOUND, "Runner not found"); } /** @@ -117,32 +115,34 @@ public boolean stopRunner(String runnerName) throws OperationException { * * @param runnerName name of the runner (connector/worker) * @return true if the runner started - * @throws Exception + * @throws OperationException runner can't start */ public boolean startRunner(String runnerName) throws OperationException { for (Running running : listRunnerRunning) { if (running.runner().getIdentification().equals(runnerName)) { - if (!running.runner.isValidDefinition()) + List listOfErrors= running.runner.checkValidDefinition(); + if (! listOfErrors.isEmpty()) throw new OperationException(WORKER_INVALID_DEFINITION, "Worker has error in the definition : " - + String.join(";", running.runner.getDefinitionErrors())); + + String.join(";", listOfErrors)); - closeJobWorker(running.containerJobWorker.jobWorker); - running.containerJobWorker.jobWorker = null; + closeJobWorker(running.containerJobWorker.getJobWorker()); + running.containerJobWorker.setJobWorker( null); JobWorkerBuilderStep1.JobWorkerBuilderStep3 jobWorkerBuild = createJobWorker(running.runner); - running.containerJobWorker.jobWorker = jobWorkerBuild.open(); + running.containerJobWorker.setJobWorker( jobWorkerBuild.open()); return true; } } - throw new OperationException(WORKER_NOT_FOUND, "Worker not found"); + throw new OperationException(RUNNER_NOT_FOUND, "Worker not found"); } public boolean isRunnerActive(String runnerName) throws OperationException { for (Running running : listRunnerRunning) { if (running.runner().getIdentification().equals(runnerName)) { - return running.containerJobWorker.jobWorker != null; + + return running.containerJobWorker.getJobWorker() != null; } } - throw new OperationException(WORKER_NOT_FOUND, "Worker not found"); + throw new OperationException(RUNNER_NOT_FOUND, "Worker not found"); } @@ -202,11 +202,19 @@ else if (runner instanceof AbstractConnector abstractConnector) * Not possible to restart a jobWorker: must be created again ! */ private static class ContainerJobWorker { - public JobWorker jobWorker; + private JobWorker jobWorker; public ContainerJobWorker(JobWorker jobWorker) { this.jobWorker = jobWorker; } + + public JobWorker getJobWorker() { + return jobWorker; + } + + public void setJobWorker(JobWorker jobWorker) { + this.jobWorker = jobWorker; + } } record Running(AbstractRunner runner, ContainerJobWorker containerJobWorker) { @@ -216,13 +224,22 @@ record Running(AbstractRunner runner, ContainerJobWorker containerJobWorker) { /** * Declare an exception on an operation */ - public class OperationException extends Exception { - public String exceptionCode; - public String explanation; + public static class OperationException extends Exception { + private final String exceptionCode; + private final String explanation; OperationException(String exceptionCode, String explanation) { this.exceptionCode = exceptionCode; this.explanation = explanation; } + + public String getExceptionCode() { + return exceptionCode; + } + + public String getExplanation() { + return explanation; + } + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 73ba891..2299e89 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,13 +1,14 @@ # use a OnPremise Zeebe engine -# zeebe.client.broker.gateway-address=127.0.0.1:26500 +zeebe.client.broker.gateway-address=127.0.0.1:26500 # zeebe.client.broker.gateway-address=host.docker.internal:26500 -# zeebe.client.security.plaintext=true +zeebe.client.security.plaintext=true # use a cloud Zeebe engine -zeebe.client.cloud.region=bru-2 -zeebe.client.cloud.clusterId=f867aa3d-5ee7-4324-96e8-21f557f104af -zeebe.client.cloud.clientId=si71NBAWtUlREmfKja5oQC3M.WsT~sHa -zeebe.client.cloud.clientSecret=pCTITndkvyNsaSkfJ-ji5w38vaP19QAXWURM3UVC1.J.eVyxa44uITFv3_c8iFi2 +# zeebe.client.cloud.region=bru-2 +# zeebe.client.cloud.clusterId=f867aa3d-5ee7-4324-96e8-21f557f104af +# zeebe.client.cloud.clientId=si71NBAWtUlREmfKja5oQC3M.WsT~sHa +# zeebe.client.cloud.clientSecret=pCTITndkvyNsaSkfJ-ji5w38vaP19QAXWURM3UVC1.J.eVyxa44uITFv3_c8iFi2 + zeebe.client.worker.maxJobsActive=32 diff --git a/src/test/resources/connectorworker/connectorworker.bpmn b/src/test/resources/connectorworker/PingConnectorWorker.bpmn similarity index 99% rename from src/test/resources/connectorworker/connectorworker.bpmn rename to src/test/resources/connectorworker/PingConnectorWorker.bpmn index 53f9294..9e4b9ee 100644 --- a/src/test/resources/connectorworker/connectorworker.bpmn +++ b/src/test/resources/connectorworker/PingConnectorWorker.bpmn @@ -1,6 +1,6 @@ - + Flow_00mgsl9 @@ -85,24 +85,24 @@ - + + + + + + + - - - - - - - - + + @@ -150,4 +150,4 @@ - + \ No newline at end of file diff --git a/src/test/resources/files/Cherry.jpg b/src/test/resources/files/Cherry.jpg new file mode 100644 index 0000000..ffc7a67 Binary files /dev/null and b/src/test/resources/files/Cherry.jpg differ