How to Build and run your first PAK App
1. What You Will Learn
After finishing this tutorial you will be able to write your own PAK App including a JarCommandPool which is capable of running workflows.
This guide will build up on this guide: how to build and run commands. |
2. Prerequisites
To complete this guide you need:
-
Roughly 20 minutes (gradle solution & maven solution)
-
JDK 11+ installed with JAVA_HOME configured appropriately
-
An IDE (preferably IntelliJ)
-
Gradle 7.4.2+ or Maven
3. Setup
3.1. Setup with Gradle
First open your selected IDE and create a new Gradle project with the following configuration:
Select a name for your project:
Inside the ‚build.gradle‘ the following build script is required:
plugins {
id 'java-library'
}
ext {
// dependency version
pakVersion = '1.9.16'
}
repositories {
mavenLocal()
mavenCentral()
maven {
name = 'pak-explorer-maven'
url 'https://pak.asap.de/nexus/repository/pak-explorer-maven/'
}
}
dependencies {
implementation "de.asap.pak.core:pak-engine:${pakVersion}"
implementation "de.asap.pak.core:pak-simple:${pakVersion}"
implementation "de.asap.pak.bpmn-model:bpmn-interpreter:${pakVersion}"
implementation "de.asap.pak.extra:pak-jarpool:${pakVersion}"
implementation "de.asap.pak.extra:pak-default-datatransformer:${pakVersion}"
runtimeOnly "de.asap.pak.core:pak-commandjson:${pakVersion}"
runtimeOnly "de.asap.pak.jlcint:jlcint-interpreter:${pakVersion}"
runtimeOnly "de.asap.pak.jlcint:jlcint-pakbridge:${pakVersion}"
runtimeOnly "org.example:example-commands:1.0.0"
// Optional but useful for logging the work
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'ch.qos.logback:logback-classic:1.4.0'
}
3.2. Setup with Maven
First open your selected IDE and create a new Maven project with the following configuration:
Select a name for your project:
The ‚pom.xml‘ requires the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.pak.app</groupId>
<artifactId>HowTo-PAK-App</artifactId>
<version>1.0.0</version>
<properties>
<pak-version>1.9.16</pak-version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>pak-explorer-maven</id>
<url>https://pak.asap.de/nexus/repository/pak-explorer-maven/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>de.asap.pak.core</groupId>
<artifactId>pak-engine</artifactId>
<version>${pak-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.core</groupId>
<artifactId>pak-simple</artifactId>
<version>${pak-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.bpmn-model</groupId>
<artifactId>bpmn-interpreter</artifactId>
<version>${pak-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.extra</groupId>
<artifactId>pak-jarpool</artifactId>
<version>${pak-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.extra</groupId>
<artifactId>pak-default-datatransformer</artifactId>
<version>${pak-version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.core</groupId>
<artifactId>pak-commandjson</artifactId>
<version>${pak-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.jlcint</groupId>
<artifactId>jlcint-pakbridge</artifactId>
<version>${pak-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>de.asap.pak.jlcint</groupId>
<artifactId>jlcint-interpreter</artifactId>
<version>${pak-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>example-commands</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
4. Implementation
For our example app we need to implement a few things:
-
an Engine that runs the workflow.
-
an EngineObserver which logs the events which are fired by the Engine.
-
an AppClass which starts the whole application.
4.1. The Engine Observer
At first, we will implement an EngineObserver that logs events from our Engine.
import de.asap.pak.core.engine.spi.interceptors.EngineEvent;
import de.asap.pak.core.engine.spi.interceptors.IEngineListenerEvent;
import de.asap.pak.core.engine.spi.interceptors.IEngineObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Example Observer to show the registration of an engine observer
*/
public class EngineObserver implements IEngineObserver {
// Create a logger for this class
private static final Logger LOG = LoggerFactory.getLogger(EngineObserver.class);
@Override
public void observe(IEngineListenerEvent event){
EngineEvent engineEvent = event.getEvent();
switch (engineEvent) {
case WORKFLOW_STARTED:
handleWorkflowStarted();
break;
case WORKFLOW_FINISHED:
handleWorkflowFinished();
break;
}
}
private void handleWorkflowStarted(){
LOG.info("The workflow has been started!");
}
private void handleWorkflowFinished(){
LOG.info("The workflow has finished!");
}
}
If you want to know more about Engine-Observers & Callbacks click here |
4.2. The Engine
Now we are going to implement the engine, and therefore we need some components.
import de.asap.pak.core.commandpool.spi.ICommandPool;
import de.asap.pak.core.context.api.IContext;
import de.asap.pak.core.context.impl.ContextBuilder;
import de.asap.pak.core.context.services.spi.IDataTransformer;
import de.asap.pak.core.context.services.spi.IJsonMapper;
import de.asap.pak.core.context.services.spi.IPersistenceService;
import de.asap.pak.core.engine.api.IEngine;
import de.asap.pak.core.engine.impl.EngineBuilder;
import de.asap.pak.core.model.api.IModel;
import de.asap.pak.core.model.api.services.IMappingService;
import de.asap.pak.core.simple.context.SimpleMappingService;
import de.asap.pak.core.simple.context.SimplePersistenceService;
import de.asap.pak.core.simple.context.SimpleServiceProviderFactory;
import de.asap.pak.extra.impl.datatransformer.ObjectMapperFacade;
import de.asap.pak.extra.impl.datatransformer.PakDefaultDataTransformer;
import de.asap.pak.extra.jarpool.JarCommandPool;
import de.asap.pak.modelinterpreter.bpmn.BPMNModelInterpreter;
import de.asap.pak.modelinterpreter.bpmn.model.validator.ModelException;
import java.io.InputStream;
/**
* Class for creating an engine
*/
public final class EngineCreator {
private EngineCreator() {
//hidden
}
/**
* This method configures an engine using the PAK Engine builder by using default implementations
*
* @param is Input-Stream object, which refers to a BPMN-File
*/
public static IEngine createEngine(final InputStream is) throws ModelInterpreterException {
// In our case we want to execute a Bpmn therefore we create the interpreter to parse and create a model
final BPMNModelInterpreter interpreter = new BPMNModelInterpreter();
// Creating an instance of Model which is going to be executed
final IModel model = interpreter.interpret(is, true); (1)
// In this example we use the JarCommandPool for providing the commands to the app
final ICommandPool pool = new JarCommandPool(); (2)
// Each Engine needs a "Context" which provides all needed data and services to the engine
final ContextBuilder cb = new ContextBuilder(); (3)
// In order to create a context, a serviceprovider factory needs to be passed. The factory will create a ServiceProvider which will hold all important services for the engine
final SimpleServiceProviderFactory factory = new SimpleServiceProviderFactory(); (4)
// The persistence service will save all data which is created when executing the workflow model
factory.addService(IPersistenceService.class,new SimplePersistenceService()); (5)
factory.addService(IJsonMapper.class, new ObjectMapperFacade());
factory.addService(IDataTransformer.class, new PakDefaultDataTransformer());
cb.setServiceProviderFactory(factory);
//Create the Context instance
final IContext context = cb.build();
// Now we got everything we need to build a engine
final IEngine engine = new EngineBuilder().setCommandPool(pool).setContext(context).setModel(model).build(); (6)
// We also register our EngineObserver we have written before
final EngineObserver engineObserver = new EngineObserver();
engine.registerEngineObserver(engineObserver);
return engine;
}
}
In the code above we see some PAK core components, which we want to explain a little more
(1) Is the model that is going to be executed by the engine. |
|
(2) A CommandPool provides in general a collection of commands and their needed services. In the specific case of an JarCommandPool the commands are directly provided by one or multiple jars. For a real app we would suggest the MavenCommandPool |
|
(3) The context contains all information and services for the engine |
|
(4) The ServiceProvider provides all kind of services that are needed for executing the model |
|
(5) A PersistenceService is responsible for persisting and providing all data created/read by the commands. |
|
(6) Is the central class which controls the complete execution of the given workflow model |
Click the links for more in-depth information about each topic. |
4.3. The App class
The App class will create the engine and execute the workflow.
import de.asap.pak.core.engine.api.IEngine;
import de.asap.pak.core.engine.api.WorkflowException;
import de.asap.pak.modelinterpreter.bpmn.model.validator.ModelException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
public class App {
//Create a logger for this class
private static final Logger LOG = LoggerFactory.getLogger(App.class);
public void start() {
IEngine engine = null;
try (final InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Hello_World.bpmn")) {
engine = EngineCreator.createEngine(inputStream);
} catch (IOException | ModelInterpreterException e) {
LOG.error("Unable to parse the BPMN-File to a model", e);
}
try {
//Start the engine
engine.start();
} catch (WorkflowException e) {
LOG.error("Unable to run the workflow", e);
}
}
public static void main(String[] args) {
//run the start method of the app
new App().start();
}
}
5. Run the App
The app is basically now ready, but before executing the code we need to provide the bpmn and commands properly.
5.1. Provide the BPMN
Just place the BPMN (from the ‚Create your First Command‚ guide) to the resource folder of your project or download it from here.
5.2. Provide the Commands
In order for the commands (from the ‚Create your First Command‚ guide) to be found by the Commandpool we need to add the dependency to our project
Make sure the example command is already published to maven local or a remote maven repository. Click here if you need more information for publishing commands locally. |
//this is the dependency to the example-commands jar which was published locally to mavenLocal
runtimeOnly "org.example:example-commands:1.0.0"
<dependency>
<groupId>org.example</groupId>
<artifactId>example-commands</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
5.3. Run the App
The App is now ready to run!
To do so select the created App class → Click the green play icon in the gutter and select Run ‚App.main()‘
And you can see the expected output from the Command in the logs.
In the logs our implemented simple EngineObserver provides additional information.