Blog

17.06.2021. | Autor: Dmitrii Pisarenko

How to programmatically validate BPMN models

Did it happen to you? Someone makes a minor change to the BPMN model, then, when the system is deployed the next time, the deployment fails because Camunda thinks that BPMN file is invalid. (Even though all the tests are green.)

There is a way to catch such errors earlier – in scope of automated tests rather than during deployment.

Why you should validate BPMN files in automated tests

When you deploy a BPMN file into a Camunda process engine, it performs a number of basic checks. If any errors are detected, the BPMN file is not deployed.

This means that in order to check the validity of a BPMN file you need to try to deploy it to Camunda. This has several disadvantages.

First of all, this validation approach requires a running Camunda engine. You can do it inside a JUnit test by starting Camunda there. However, these tests are usually slower than normal JUnit tests and if you have many BPMN files, running those tests can slow down the build. This, in turn, can encourage developers to turn off those tests.

Big Question

The question is: How can we validate BPMN files just like Camunda does during deployment without a running Camunda engine?

Where does Camunda validate BPMN files during deployment?

First, we need to download Camunda source code. After IntelliJ Idea finished indexing it, you can search for valid in that code base. Among the search results you will find the file BpmnDeployer. There, you can find the following piece of code:

@Override
	protected List<ProcessDefinitionEntity> transformDefinitions(
    DeploymentEntity deployment, ResourceEntity resource,
  Properties properties) {
	  byte[] bytes = resource.getBytes();
	  ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);

	  BpmnParse bpmnParse = bpmnParser
	      .createParse()
	      .sourceInputStream(inputStream)
	      .deployment(deployment)
	      .name(resource.getName());

	  if (!deployment.isValidatingSchema()) {
	    bpmnParse.setSchemaResource(null);
	  }

	  bpmnParse.execute();

	  if (!properties.contains(JOB_DECLARATIONS_PROPERTY)) {
	    properties.set(JOB_DECLARATIONS_PROPERTY, new HashMap<String, List<JobDeclaration<?, ?>>>());
	  }
	  properties.get(JOB_DECLARATIONS_PROPERTY).putAll(bpmnParse.getJobDeclarations());

	  return bpmnParse.getProcessDefinitions();
	}

The line bpmnParse.execute(); leads us to the class BpmnParse where we find another interesting piece of code:

  @Override
  public BpmnParse execute() {
    super.execute(); // schema validation

    try {
      parseRootElement();
    } catch (BpmnParseException e) {
      addError(e);

    } catch (Exception e) {
      LOG.parsingFailure(e);

      // ALL unexpected exceptions should bubble up since they are not handled
      // accordingly by underlying parse-methods and the process can't be
      // deployed
      throw LOG.parsingProcessException(e);

    } finally {
      if (hasWarnings()) {
        logWarnings();
      }
      if (hasErrors()) {
        throwExceptionForErrors();
      }
    }

    return this;
  }

It seems that it throws an exception if errors are detected in a BPMN file.

Calling BpmnParse from inside a minimal JUnit test

Now we need to call BpmnParse in a JUnit test. With a little experimenting we came up with this piece of code:

private void validateFile(String fileName) throws IOException {
        try (FileInputStream inputStream = FileUtils.openInputStream(new File(fileName))) {
            final ExpressionManager testExpressionManager = new ExpressionManager();

            ProcessEngineConfigurationImpl processEngineConfiguration = new ProcessEngineConfigurationImpl() {
                @Override
                protected Collection<? extends CommandInterceptor> getDefaultCommandInterceptorsTxRequired() {
                    return null;
                }

                @Override
                protected Collection<? extends CommandInterceptor> getDefaultCommandInterceptorsTxRequiresNew() {
                    return null;
                }

                @Override
                public ExpressionManager getExpressionManager() {
                    return testExpressionManager;
                }

                @Override
                public FormTypes getFormTypes() {
                    final FormTypes formTypes = new FormTypes();
                    formTypes.addFormType(new BooleanFormType());
                    formTypes.addFormType(new StringFormType());
                    formTypes.addFormType(new LongFormType());
                    return formTypes;
                }
            };

            Context.setProcessEngineConfiguration(processEngineConfiguration);

            BpmnParseFactory bpmnParseFactory = new DefaultBpmnParseFactory();
            BpmnParser bpmnParser = new BpmnParser(testExpressionManager, bpmnParseFactory);
            BpmnParse bpmnParse = bpmnParser.createParse()
                    .sourceInputStream(inputStream)
                    .deployment(new DeploymentEntity())
                    .name(fileName);
            bpmnParse.execute();
        }
        catch (final ParseException exception) {
            exception.printStackTrace();
            Assert.fail("BPMN is invalid, see log output for details");
        }
    }

It detects syntactic errors in BPMN diagrams just like Camunda does at deployment.

Then, you can do a basic syntax check on a BPMN file using a call like:

@Test
public void test1() throws IOException {
    validateFile("src/test/resources/diagram_1.bpmn");
}

You can use this code to make sure that syntactic errors in your BPMN diagrams are detected as early as possible.

What do you think, would it make sense to enhance this code, for example, make sure that it detects activities without the async-before flag set?

Dmitrii Pisarenko
JIT Developer

Photo by Markus Winkler on Unsplash

Weitere Blog

10.09.2021.

BPMN Error Handling Mechanism – Dealing with many errors in a process

Autor: Gerardo Manzano

Errors are inherently part of software development. They can happen because of several reasons. One common definition of a software error is a mismatch between the program and its specification. In other words, we can say, a program has an error when the program does not do what the end-user expects. In this blog post, […]

Blog lesen
12.08.2021.

Company insights – the teams behind JIT

Autor: Melissa Pürstinger

If you’re an attentive reader of our blog posts you might have already noticed that the topics and projects the people of JIT are working on are quite diverse. We are an IT company that is successfully covering more than one area and that is only possible because of the vibrant and motivated teams behind […]

Blog lesen
29.07.2021.

Ruby external task worker

Autor: Maximilian Kamenicky

“The value of a system is often defined by how well it integrates with other systems” The best software is limited if you cannot integrate it with existing frameworks. In the case of Camunda, this fact is all the more true. Imagine a workflow automation engine without a way to interact with the existing software. […]

Blog lesen