diff --git a/exercises/exercise-0.md b/exercises/exercise-0.md index 67aa0571..440e5af9 100644 --- a/exercises/exercise-0.md +++ b/exercises/exercise-0.md @@ -23,6 +23,6 @@ For this, you should follow these steps: docker compose down ``` 14. Checkout the branch `main` -15. You may proceed to [exercise 1](exercise-1.md) +15. You may proceed to [exercise 1](exercise-1.md) (Remember, the tutorial is divided into several exercises that build on each other. Skip around at your own risk!) ___ [Prerequisites](prerequisites.md) • **Exercise 0** • [Exercise 1](exercise-1.md) • [Exercise 1.1](exercise-1-1.md) • [Exercise 2](exercise-2.md) • [Exercise 3](exercise-3.md) • [Exercise 4](exercise-4.md) • [Exercise 5](exercise-5.md) • [Exercise 6](exercise-6.md) • [Exercise 7](exercise-7.md) diff --git a/exercises/exercise-1.md b/exercises/exercise-1.md index ddc4429d..49b61fa7 100644 --- a/exercises/exercise-1.md +++ b/exercises/exercise-1.md @@ -23,25 +23,60 @@ FHIR resources used in the DSF are formatted as XML. You can find them in the `t When creating your own FHIR resources for DSF process plugins you also want to put them in a fitting subdirectory of `tutorial-process/src/main/resources/fhir`. # Exercise 1 - Simple Process -In this exercise you will add functionality to a service task in the already existing process called `exampleorg_dicProcess` and learn how to start -processes in the DSF. The BPMN model for the `exampleorg_dicProcess` is located in `tutorial-process/src/main/resources/bpe`. - -Documentation topics related to this exercise are [FHIR Task](https://dsf.dev/process-development/api-v2/fhir/task.html), -[The Process Plugin Definition](https://dsf.dev/process-development/api-v2/dsf/process-plugin-definition.html), -[Spring Integration](https://dsf.dev/process-development/api-v2/dsf/spring-framework-integration.html), -[Activities](https://dsf.dev/process-development/api-v2/dsf/activities.html), -[BPMN Process Execution](https://dsf.dev/process-development/api-v2/dsf/bpmn-process-execution.html), -[BPMN Process Variables](https://dsf.dev/process-development/api-v2/dsf/bpmn-process-variables.html), -[Accessing BPMN Process Variables](https://dsf.dev/process-development/api-v2/guides/accessing-bpmn-process-variables.html), -[Versions, Placeholders and URLs](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html) -and [Starting a Process via Task Resources](https://dsf.dev/process-development/api-v2/guides/starting-a-process-via-task-resources.html). +In this exercise you will wire up a Java service task to an already existing BPMN process and start it for the first time. The goal is simple: the process runs, your Java code executes, and a log message appears. +The BPMN model for the `exampleorg_dicProcess` is located in `tutorial-process/src/main/resources/bpe/dic-process.bpmn`. +The Java service task class you will fill in is `tutorial-process/src/main/java/org/tutorial/process/tutorial/service/DicTask.java`. Solutions to this exercise are found on the branch `solutions/exercise-1`. +
+Background reading (documentation links for this exercise) + +You do not need to read all of these before starting. Use them as a reference when something is unclear: + +- [FHIR Task](https://dsf.dev/process-development/api-v2/fhir/task.html) +- [The Process Plugin Definition](https://dsf.dev/process-development/api-v2/dsf/process-plugin-definition.html) +- [Spring Integration](https://dsf.dev/process-development/api-v2/dsf/spring-framework-integration.html) +- [Activities](https://dsf.dev/process-development/api-v2/dsf/activities.html) +- [BPMN Process Execution](https://dsf.dev/process-development/api-v2/dsf/bpmn-process-execution.html) +- [BPMN Process Variables](https://dsf.dev/process-development/api-v2/dsf/bpmn-process-variables.html) +- [Accessing BPMN Process Variables](https://dsf.dev/process-development/api-v2/guides/accessing-bpmn-process-variables.html) +- [Versions, Placeholders and URLs](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html) +- [Starting a Process via Task Resources](https://dsf.dev/process-development/api-v2/guides/starting-a-process-via-task-resources.html) +
+ ## Exercise Tasks 1. Set the `DicTask` class as the service implementation of the appropriate service task within the `dic-process.bpmn` process model. -2. Register the `DicTask` class as a prototype bean in the `TutorialConfig` class. + + **What:** Open `tutorial-process/src/main/resources/bpe/dic-process.bpmn` and set the `camunda:class` attribute on the `` element to the fully qualified class name of `DicTask`. + **Why:** Camunda needs to know which Java class to instantiate and call when it reaches this task during process execution. + **How it looks in XML:** +
+ How it looks in XML? + + ```xml + + ``` + The value follows the pattern `.`, which matches the folder structure under `tutorial-process/src/main/java/`. If you use the **Camunda Modeler**, switch the task's implementation type to **"Java Class"** and enter the same fully qualified name in the field that appears. + +
+ +
+ What does the fully qualified class name look like in other processes? + + For orientation: the `CosTask` class lives at `tutorial-process/src/main/java/org/tutorial/process/tutorial/service/CosTask.java`, so its fully qualified name is `org.tutorial.process.tutorial.service.CosTask`. `DicTask` sits in the same package. +
+ +2. Register the `DicTask` class as a prototype bean in the `TutorialConfig` class located at `tutorial-process/src/main/java/org/tutorial/process/tutorial/spring/config/TutorialConfig.java`. + +
+ Why prototype scope? + + The DSF BPE engine creates a new instance of a service task class for every process execution. Spring's default scope is singleton, so we must explicitly declare the bean as `@Scope("prototype")` to prevent shared state between concurrent executions. +
+
Don't know how to register prototype beans? @@ -53,25 +88,54 @@ Solutions to this exercise are found on the branch `solutions/exercise-1`.
Don't know where to get a logger? - This project uses slf4j. So use `LoggerFactory` to get yourself a logger instance. + This project uses slf4j. Use `LoggerFactory` to get yourself a logger instance.
Can't find a way to get the start task? - The `execute` method provides a `Variables` instance. Try it through this one. + The `execute` method provides a `Variables` instance. It might provide a fitting method.
Don't know where to look for the identifier? - - Take a look at the official [FHIR Task](https://hl7.org/fhir/R4/task.html) resource, find elements that have a recipient and maneuver your way to those elements using the right getters. Then test which of them has the correct value. + + Try to navigate to the identifier value with the equivalent getters according to the following: + The FHIR Task resource has a `restriction` element that lists the allowed recipients. Its structure looks like this: + + ```xml + + + + + + + + + + + ``` + + Hint: Don't iterate over the list of all recipients. `getRecipientFirstRep()` is a HAPI convenience method that returns the first element of the recipient list. In practice a Task can have more than one recipient, but for this simple example there is always exactly one.
4. In order to start your process you need to either create a regular [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) resource or a [Draft Task Resource](https://dsf.dev/process-development/api-v2/dsf/draft-task-resources.html). Based on whether you would like to use cURL or the DSF FHIR server's web interface for starting processes you can do one of the following assignments (although we invite you to do both): + +
+ + Special DSF FHIR Task Elements + FHIR Task that starts a DSF process have the following fields with special meaning: + + | Element | Purpose | + |---|--------------------------------------------------------------------------------------------------------------------------------------------| + | `instantiatesCanonical` | Which process (and version) should be started. Points to the process URI defined in the `ActivityDefinition`. | + | `requester` / `restriction.recipient` | Who sends the request (requester) and to which organization it is addressed (recipient). Uses organization identifiers. | + | `input` (message-name) | Which BPMN Message Start Event should be triggered. The value must match the message name in the BPMN file (here: `startDicProcess`), and be defined as an expected input within the linked ActivityDefinition. | +
+ * Create a [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) resource in `tutorial-process/src/main/resources/fhir/example-task.xml` based on the [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) profile `tutorial-process/src/main/resources/fhir/StructureDefinition/task-start-dic-process.xml`. You will need it to start your process via cURL. diff --git a/exercises/exercise-2.md b/exercises/exercise-2.md index 0c228a50..ec6b61f7 100644 --- a/exercises/exercise-2.md +++ b/exercises/exercise-2.md @@ -2,20 +2,22 @@ ___ # Exercise 2 - Environment Variables and Input Parameters -BPMN processes might require additional information during execution, e.g. for configuration purposes. -We will take a look at two possibilities on how to pass additional information to a BPMN process: Environment Variables and Input Parameters. -The goal of this exercise is to enhance the `exampleorg_dicProcess` by trying them both. -In both cases the information will be available in the `execute` method of your service class. - -In order to solve this exercise, you should have solved the first exercise and read the topics on -[Environment Variables](https://dsf.dev/process-development/api-v2/dsf/environment-variables.html), -[Task Input Parameters](https://dsf.dev/process-development/api-v2/fhir/task.html#task-input-parameters), -[Accessing Task Resources During Execution](https://dsf.dev/process-development/api-v2/guides/accessing-task-resources-during-execution.html), -[Placeholders](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html) and -[Read Access Tag](https://dsf.dev/process-development/api-v2/dsf/read-access-tag.html). +In this exercise you will pass additional information into a running process in two different ways: via an **environment variable** (configured outside the process, at deployment time) and via a **FHIR Task Input Parameter** (supplied at the start of every process). + +Both values will be accessible inside the `execute` method of your `DicTask` service class. Solutions to this exercise are found on the branch `solutions/exercise-2`. +
+Background reading (documentation links for this exercise) + +- [Environment Variables](https://dsf.dev/process-development/api-v2/dsf/environment-variables.html) +- [Task Input Parameters](https://dsf.dev/process-development/api-v2/fhir/task.html#task-input-parameters) +- [Accessing Task Resources During Execution](https://dsf.dev/process-development/api-v2/guides/accessing-task-resources-during-execution.html) +- [Placeholders](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html) +- [Read Access Tag](https://dsf.dev/process-development/api-v2/dsf/read-access-tag.html) +
+ ## Exercise Tasks 1. Add a new boolean variable to the `TutorialConfig` class. It will enable/disable logging and have its value injected from an environment variable. Add the annotation and specify the default value as `false`. You may freely choose a name for your environment variable here. Just make sure you follow the naming convention explained in [Environment Variables](https://dsf.dev/process-development/api-v2/dsf/environment-variables.html). @@ -54,14 +56,16 @@ Solutions to this exercise are found on the branch `solutions/exercise-2`. `tutorial-process/src/main/resources/fhir/ValueSet`.
-7. Add a new input parameter of type `tutorial-input` with `Task.input.value[x]` as a `string` to the `task-start-dic-process.xml` [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) profile. +7. Add a new input parameter of type `tutorial-input` with `Task.input.value[x]` as a `string` to the StructureDefinition profile file `tutorial-process/src/main/resources/fhir/StructureDefinition/task-start-dic-process.xml`. + + > **Note:** The `task-start-dic-process.xml` exists both under the `StructureDefinition` and `Task` directories under `resources`. In terms of Object Oriented Programming, the StructureDefinition is the class and the Task resource is an instance.
Don't know how to add a new input parameter? Check out [this guide](https://dsf.dev/process-development/api-v2/guides/adding-task-parameters-to-task-profiles.html).
-8. `task-start-dic-process` and by extension the process `exampleorg_dicProcess` now requires additional FHIR resources. Make sure the return value for `TutorialProcessPluginDefinition#getFhirResourcesByProcessId` also includes the new [CodeSystem](https://dsf.dev/process-development/api-v2/fhir/codesystem.html) and [ValueSet](https://dsf.dev/process-development/api-v2/fhir/valueset.html) resources for the `exampleorg_dicProcess`. +8. Make sure the return value for `TutorialProcessPluginDefinition#getFhirResourcesByProcessId` also includes the new [CodeSystem](https://dsf.dev/process-development/api-v2/fhir/codesystem.html) and [ValueSet](https://dsf.dev/process-development/api-v2/fhir/valueset.html) resources for the `exampleorg_dicProcess`. The process `exampleorg_dicProcess` now requires these additional FHIR resources because `task-start-dic-process` references them — without registering them here, the DSF BPE server will reject the StructureDefinition (and thus process deployment) because it can't find the resources referenced in it. 9. Read the new input parameter in the `DicTask` class from the start [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) and add the value to the log message from exercise 1.
Don't know how to get the input parameter? diff --git a/exercises/exercise-3.md b/exercises/exercise-3.md index d4b52b7c..d0297eac 100644 --- a/exercises/exercise-3.md +++ b/exercises/exercise-3.md @@ -3,38 +3,39 @@ ___ # Exercise 3 - DSF User Role Configuration -In [Exercise 1](exercise-1.md), you added a client certificate to your browser in order to be allowed to access the DIC FHIR -server. In later exercises we will also use some of the other DSF installations like the cos.dsf.test or hrp.dsf.test. You could add a -client certificate to your browser for each one, or you could configure yourself a specific DSF user -with access to all DSF installations. -This is part of the DSF's access control using the role configuration mechanism. It allows you to specify -exact rules for accessing the FHIR REST API and starting processes for certain users. Either by providing -thumbprints of their client certificates or by using [OpenID Connect](https://openid.net/developers/how-connect-works/). -For this exercise, we will include [OpenID Connect](https://openid.net/developers/how-connect-works/) in the configuration through a [Keycloak](https://www.keycloak.org/) instance. We have already created a user for you in the `DIC` realm who has sufficient -access to the FHIR REST API and who is allowed to start our `dicProcess`. -The administration console for Keycloak is accessible under https://keycloak:8443. -Credentials for administrator access are `username: admin` and `password: admin`. There is also a DSF Role Config for the [DIC FHIR server instance](../dev-setup/docker-compose.yml) set up in accordance with the Keycloak configuration. -Your task will be to take this user and explicitly allow them to start the `dicProcess`. Optionally, you can also add -Keycloak users for the `COS` and `HRP` instances. - -Documentation topics related to this exercise are [Access Control](https://dsf.dev/operations/latest/fhir/access-control.html) -and [ActivityDefinitions](https://dsf.dev/process-development/api-v2/fhir/activitydefinition.html). +In this exercise you will learn how to control **who is allowed to start a process** in the DSF. You will configure authorization rules in an `ActivityDefinition` file and add a Keycloak-based user alongside the existing certificate-based access. + +The file you will work in is `tutorial-process/src/main/resources/fhir/ActivityDefinition/dic-process.xml`. + +
+Background reading (documentation links for this exercise) + +- [Access Control](https://dsf.dev/operations/latest/fhir/access-control.html) +- [ActivityDefinitions](https://dsf.dev/process-development/api-v2/fhir/activitydefinition.html) +- [Requester and Recipient](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html) +- [Guide: Creating ActivityDefinitions](https://dsf.dev/process-development/api-v2/guides/creating-activity-definitions.html) +
+ +A [Keycloak](https://www.keycloak.org/) instance is already running as part of the dev setup. A user has been created for you in the `DIC` realm. The Keycloak admin console is accessible at https://keycloak:8443 (`username: admin`, `password: admin`). Your task is to allow this user to start the `dicProcess`. Optionally you can also add Keycloak users for the `COS` and `HRP` instances. ## Exercise Tasks -1. Change the `requester` element in the ActivityDefinition `dic-process.xml` to allow all local clients with a practitioner role of `DSF_ADMIN` to request `dicProcess` messages. +1. Change the `requester` element in the ActivityDefinition `tutorial-process/src/main/resources/fhir/ActivityDefinition/dic-process.xml` to allow all local clients with a practitioner role of `DSF_ADMIN` to request `dicProcess` messages. There is a documentation page to help you [understand the process authorization extension](https://dsf.dev/process-development/api-v2/dsf/understanding-the-process-authorization-extension.html). +
- Don't know how to change the ActivityDefinition? + Need a ready-made example? There is a list of examples for the `requester` element [here](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html). You can also check out the [guide on creating ActivityDefinitions](https://dsf.dev/process-development/api-v2/guides/creating-activity-definitions.html).
2. We just made it so you will not be able to start the `dicProcess` using the client certificate used in earlier exercises. - Add another `requester` to the ActivityDefinition `dic-process.xml` which allows local clients from the `dic.dsf.test` organization to request `dicProcess` messages, - in case you still want to use the client certificate to start the process. + Add a **second** `` entry to the same authorization block in `dic-process.xml` which allows local clients from the `dic.dsf.test` organization to request `dicProcess` messages, in case you still want to use the client certificate to start the process. + + You need the `LOCAL_ORGANIZATION` code combined with the `extension-process-authorization-organization` nested extension pointing to `dic.dsf.test`. +
- Don't know how to change the ActivityDefinition? + Need a ready-made example? There is a list of examples for the `requester` element [here](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html). You can also check out the [guide on creating ActivityDefinitions](https://dsf.dev/process-development/api-v2/guides/creating-activity-definitions.html). diff --git a/exercises/exercise-4.md b/exercises/exercise-4.md index 61332329..d2dc8e6f 100644 --- a/exercises/exercise-4.md +++ b/exercises/exercise-4.md @@ -2,46 +2,93 @@ ___ # Exercise 4 - Messaging -Communication between organizations in BPMN processes is modeled using message flow. The fourth exercise shows how a process at one organization can trigger a process at another organization. +In this exercise you will make two organizations talk to each other: `dic.dsf.test` will automatically trigger a process at `cos.dsf.test` by sending a FHIR Task resource across organizations. You will configure both BPMN files and the required FHIR resources for this to work. -To demonstrate communication between two organizations we will configure message flow between the processes `exampleorg_dicProcess` and `exampleorg_cosProcess`. After that, the processes are to be executed at the organizations `dic.dsf.test` and `cos.dsf.test` respectively in the docker dev setup, with the former triggering execution of the latter by automatically sending a [Task](http://hl7.org/fhir/R4/task.html) resource from organization `dic.dsf.test` to organization `cos.dsf.test`. +Solutions to this exercise are found on the branch `solutions/exercise-4`. -In order to solve this exercise, you should have solved exercise 3 and read the topics on -[Messaging](https://dsf.dev/process-development/api-v2/dsf/messaging.html), -[Message Activities](https://dsf.dev/process-development/api-v2/dsf/message-activities.html), -[Version Pattern](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html#version-pattern), -[URLs](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html#urls) -and [Target and Targets](https://dsf.dev/process-development/api-v2/dsf/target-and-targets.html). +
+Background reading (documentation links for this exercise) -Solutions to this exercise are found on the branch `solutions/exercise-4`. +- [Messaging](https://dsf.dev/process-development/api-v2/dsf/messaging.html) +- [Message Activities](https://dsf.dev/process-development/api-v2/dsf/message-activities.html) +- [Version Pattern](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html#version-pattern) +- [URLs](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html#urls) +- [Target and Targets](https://dsf.dev/process-development/api-v2/dsf/target-and-targets.html) +
## Exercise Tasks -1. Replace the [End Event](https://docs.camunda.org/manual/7.17/reference/bpmn20/events/none-events/#none-end-event) of the `exampleorg_dicProcess` in the `dic-process.bpmn` file with a [Message End Event](https://dsf.dev/process-development/api-v2/dsf/messaging.html#message-end-event). Give the [Message End Event](https://dsf.dev/process-development/api-v2/dsf/messaging.html#message-end-event) a name and an ID and set its implementation to the `HelloCosMessage` class. - Configure field injections `instantiatesCanonical`, `profile` and `messageName` in the BPMN model for the [Message End Event](https://docs.camunda.org/manual/7.17/reference/bpmn20/events/message-events/#message-end-event). - Use `http://example.org/fhir/StructureDefinition/task-hello-cos|#{version}` as the profile and `helloCos` as the message name. Figure out what the appropriate `instantiatesCanonical` value is, based on the name (process definition key) of the process to be triggered. -
- Can't remember how instantiatesCanonical is built? - Read the concept [here](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html#urls) again. +1. Replace the [End Event](https://docs.camunda.org/manual/7.17/reference/bpmn20/events/none-events/#none-end-event) of the `exampleorg_dicProcess` in `dic-process.bpmn` with a [Message End Event](https://dsf.dev/process-development/api-v2/dsf/messaging.html#message-end-event). Give the event a name and an ID, then configure it as follows: + + **In Camunda Modeler:** + - Click the End Event circle → change its type to **Message End Event** (envelope icon) + - In the properties panel, set **Implementation** to **Java Class** and enter: `org.tutorial.process.tutorial.message.HelloCosMessage` + - Switch to the **Field Injections** tab and add three entries: + + | Field name | Type | Value | + |---|---|---| + | `profile` | String | `http://example.org/fhir/StructureDefinition/task-hello-cos|#{version}` | + | `messageName` | String | `helloCos` | + | `instantiatesCanonical` | String | *(see hint below)* | + + **What do these three field injections mean?** + + | Field | Purpose | + |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | `profile` | The StructureDefinition URL that the FHIR Task sent to the target organization must conform to. This links the outgoing message to a specific Task profile. | + | `messageName` | The BPMN message name that identifies which Message Start Event at the target process should be triggered. Must match exactly the message name you set in `cos-process.bpmn`. | + | `instantiatesCanonical` | The process URI + version of the *target* process to be started. | + +
+ Can't figure out the instantiatesCanonical value? + + The process definition key of the COS process is `cosProcess`. Follow the [URL pattern](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html#urls) to create the correct value. +
+2. Open `cos-process.bpmn` and configure the **Message Start Event** message name to match the `messageName` value from step 1 (`helloCos`). + +3. Create a new StructureDefinition Task profile for the `helloCos` message. Save it as a resource under `fhir/StructureDefinition/task-hello-cos.xml`. + +
+ Don't know how to get started? + + Base this Task profile on `fhir/StructureDefinition/task-start-dic-process.xml`. Key differences: the `instantiatesCanonical` must point to the COS process URI, and the `message-name` input slice value must be `helloCos`. The `correlation-key` slice should be allowed (`max` not `0`) since inter-organization messages need a correlation key. Remove the `tutorial-input` slice if it was added in exercise 2.
-2. Modify the `exampleorg_cosProcess` in the `cos-process.bpmn` file and configure the message name of the [Message Start Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-start-event) with the same value as the message name of the [Message End Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-end-event) in the `exampleorg_dicProcess`. -3. Create a new [StructureDefinition](http://hl7.org/fhir/R4/structuredefinition.html) with a [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) profile for the `helloCos` message. + +4. Create a new ActivityDefinition for the `exampleorg_cosProcess`. Save it as `fhir/ActivityDefinition/cos-process.xml`. Configure the authorization extension: + - **requester**: `dic.dsf.test` (remote organization) → use code `REMOTE_ORGANIZATION` with a nested `extension-process-authorization-organization` pointing to `dic.dsf.test` + - **recipient**: `cos.dsf.test` (local organization) → use code `LOCAL_ORGANIZATION` with a nested `extension-process-authorization-organization` pointing to `cos.dsf.test` +
- Don't know how to get started? - - You can base this [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) profile off the `StructureDefinition/task-start-dic-process.xml` resource. Then look for elements that need to be added, changed or can be omitted. + Don't know how to get started? + + Base this ActivityDefinition on `fhir/ActivityDefinition/dic-process.xml` and adapt `url`, `name`, `title`, `message-name`, `task-profile`, `requester` and `recipient`. Refer back to the [documentation on the process authorization extension](https://dsf.dev/process-development/api-v2/dsf/understanding-the-process-authorization-extension.html) for the XML patterns. + Or take a look at the [guide on creating ActivityDefinitions](https://dsf.dev/process-development/api-v2/guides/creating-activity-definitions.html).
-4. Create a new [ActivityDefinition](https://dsf.dev/process-development/api-v2/fhir/activitydefinition.html) resource for the `exampleorg_cosProcess` and configure the authorization extension to allow the `dic.dsf.test` organization as the requester and the `cos.dsf.test` organization as the recipient. The file has to be called `cos-process.xml`. -
- Don't know how to get started? - - You can base this ActivityDefinition off the `ActivityDefinition/dic-process.xml` resource. Then look for elements that need to be added, changed or can be omitted. - Or you can take a look at the [guide on creating ActivityDefinitions](https://dsf.dev/process-development/api-v2/guides/creating-activity-definitions.html). -
-5. Add the `exampleorg_cosProcess` and its resources to the `TutorialProcessPluginDefinition` class. This will require a new mapping entry with the full process name of the `cosProcess` as the key and a List of associated FHIR resources as the value. -6. Modify `DicTask` service class to set the `target` process variable for the `cos.dsf.test` organization. -7. Configure the `HelloCosMessage` class as a Spring Bean in the `TutorialConfig` class. Don't forget the right scope. -8. Again, we introduced changes that break compatibility. Older plugin versions at the COS instance won't be able to handle the Task resource type we added earlier. Increment your resource version to `1.3`. + +5. Add the `exampleorg_cosProcess` and its resources to the `TutorialProcessPluginDefinition` class (`TutorialProcessPluginDefinition.java`). Add a new entry to the Map returned by `getFhirResourcesByProcessId()` using the full process name of the `cosProcess` as the key and a list containing the new ActivityDefinition and StructureDefinition files as the value. Also add `bpe/cos-process.bpmn` to `getProcessModels()`. + +6. Modify the `DicTask` service class to set the `target` process variable for the `cos.dsf.test` organization. + + The `target` variable tells the DSF's Message End Event where to send the outgoing FHIR Task. Call `variables.createTarget(...)` with three parameters: + + | Parameter | What it identifies | Value for this exercise | + |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------|---| + | Organization identifier | The DSF organization that should receive the message. Can be found in the allow list (e.g for the cos organization in `dev-setup/cos/fhir/conf/bundle.xml`). | `"cos.dsf.test"` | + | Endpoint identifier | The DSF endpoint name registered at that organization. Can be found in the allow list (e.g for the cos organization in `dev-setup/cos/fhir/conf/bundle.xml`). | `"cos.dsf.test_Endpoint"` | + | FHIR base URL | The FHIR server URL of the target DSF instance | `"https://cos/fhir"` | + + ```java + Target target = variables.createTarget( + "cos.dsf.test", + "cos.dsf.test_Endpoint", + "https://cos/fhir" + ); + variables.setTarget(target); + ``` + +7. Configure the `HelloCosMessage` class as a Spring prototype bean in the `TutorialConfig` class, the same way you registered `DicTask`. + +8. Again, we introduced changes that break compatibility. Older plugin versions at the COS instance won't be able to handle the Task resource type we added earlier. Increment your resource version to `1.3`. ## Solution Verification ### Maven Build and Automated Tests diff --git a/exercises/exercise-5.md b/exercises/exercise-5.md index 18c35625..4b11629c 100644 --- a/exercises/exercise-5.md +++ b/exercises/exercise-5.md @@ -2,19 +2,65 @@ ___ # Exercise 5 - Exclusive Gateways -Different execution paths in a process based on the state of process variables can be achieved using Exclusive Gateways. In Exercise 5 we will examine how this can be implemented by modifying the `exampleorg_dicProcess`. +In this exercise you will add a decision point to the `exampleorg_dicProcess`: based on the value of the `tutorial-input` parameter sent with the start Task, the process will either trigger `exampleorg_cosProcess` or stop right there. -In order to solve this exercise, you should have solved Exercise 4 and read the topics on -[Exclusive Gateways](https://dsf.dev/process-development/api-v2/bpmn/gateways.html) -and [Conditions](https://dsf.dev/process-development/api-v2/bpmn/conditions.html). +The file you will primarily work in is `dic-process.bpmn` and `DicTask.java`. Solutions to this exercise are found on the branch `solutions/exercise-5`. +
+Background reading (documentation links for this exercise) + +- [Exclusive Gateways](https://dsf.dev/process-development/api-v2/bpmn/gateways.html) +- [Conditions](https://dsf.dev/process-development/api-v2/bpmn/conditions.html) +
+ ## Exercise Tasks -1. Add an exclusive gateway to the `exampleorg_dicProcess` model and two outgoing sequence flows - the first starting the process `exampleorg_cosProcess`, the second stopping the process `exampleorg_dicProcess` without starting the process `exampleorg_cosProcess`. -2. Add condition expressions to each outgoing sequence flow which decides the path that will be taken based on a boolean value. -3. In the `DicTask` class, create a boolean variable which decides whether the `exampleorg_cosProcess` should be started based on the start Task's input parameter `tutorial-input`. -4. Add the boolean variable to the process execution variables, storing the decision. It needs to have the same name as the variable used in the condition expression from `2.` + +1. Open `tutorial-process/src/main/resources/bpe/dic-process.bpmn` in **Camunda Modeler** and add an **Exclusive Gateway** between the `DicTask` service task and the current end/message-end event. + + Connect two outgoing sequence flows from the gateway: + - **Path A**: leads to the Message End Event that sends `helloCos` (from Exercise 4) → starts `exampleorg_cosProcess` + - **Path B**: leads to a plain End Event → stops `exampleorg_dicProcess` without contacting COS + +
+ Camunda Modeler instructions + + - Click the `DicTask` service task → select **Append Gateway** + - Draw a sequence flow from the gateway to the existing Message End Event (Path A) + - Draw another sequence flow from the gateway to a new plain End Event (Path B) +
+ +2. Add a **condition expression** to each outgoing sequence flow so the BPE knows which path to take at runtime. + + The BPE uses **JUEL** (Java Unified Expression Language) for conditions. Condition expressions read process variables by name. For a boolean variable called `sendToCos`, the expressions would be: + + | Sequence flow | Condition expression | Meaning | + |---|---|---| + | Path A (→ COS) | `${sendToCos}` | Take this path when `sendToCos` is `true` | + | Path B (→ End) | `${!sendToCos}` | Take this path when `sendToCos` is `false` | + + You can freely choose the variable name — just make sure it matches exactly between the condition expressions here and the process variable you set in step 4. + +
+ Camunda Modeler instructions + + - Click a sequence flow arrow → in the properties panel on the right, find **Condition Type** → select **Expression** + - Enter the expression (e.g. `${sendToCos}`) in the **Expression** field +
+ +3. In the `DicTask` class, read the `tutorial-input` string from the start Task and derive a boolean decision from it. + + **What:** Evaluate whether the input value signals that the COS process should be started. + **Why:** The gateway condition reads a process variable — that variable must be set by your Java code before the gateway is reached. This is done in the next step. + +
+ Not sure how to read the tutorial-input parameter? + + Use the `TaskHelper` together with `variables` as you did in Exercise 2. The input type code is `tutorial-input` from CodeSystem `http://example.org/fhir/CodeSystem/tutorial`. Once you have the string value, decide on a convention — for example, `"true"` means start COS, anything else means stop. +
+ +4. Store the boolean result as a **named process variable** using `variables.setBoolean(...)`. The variable name must be **identical** to the name used in the condition expressions in step 2. ## Solution Verification diff --git a/exercises/exercise-6.md b/exercises/exercise-6.md index 156cf07e..7ab65ee3 100644 --- a/exercises/exercise-6.md +++ b/exercises/exercise-6.md @@ -2,38 +2,175 @@ ___ # Exercise 6 - Event Based Gateways and Intermediate Events -In this exercise we will look at message flow between three organizations as well as how to continue a waiting process if no return message arrives. -With this exercise we will add a third process and complete a message loop from `dic.dsf.test` to `cos.dsf.test` to `hrp.dsf.test` and back to `dic.dsf.test`. - -In order to solve this exercise, you should have solved exercise 5 and read the topics on -[Managing Multiple Incoming Messages and Missing Messages](https://dsf.dev/process-development/api-v2/guides/managing-mutiple-incoming-messages-and-missing-messages.html) -and [Message Correlation](https://dsf.dev/process-development/api-v2/dsf/message-correlation.html). +In this exercise you will complete a three-organization message loop: `dic.dsf.test` → `cos.dsf.test` → `hrp.dsf.test` → back to `dic.dsf.test`. The DIC process will no longer end immediately after sending — it will wait for a reply from HRP (or time out after two minutes). Solutions to this exercise are found on the branch `solutions/exercise-6`. +
+Background reading (documentation links for this exercise) + +- [Managing Multiple Incoming Messages and Missing Messages](https://dsf.dev/process-development/api-v2/guides/managing-mutiple-incoming-messages-and-missing-messages.html) +- [Message Correlation](https://dsf.dev/process-development/api-v2/dsf/message-correlation.html) +- [Message Intermediate Throwing Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-intermediate-throwing-event) +- [Timer Intermediate Catching Event](https://dsf.dev/process-development/api-v2/bpmn/timer-intermediate-catching-events.html) +- [Event Based Gateway](https://dsf.dev/process-development/api-v2/bpmn/gateways.html#event-based-gateway) +- [BPMN Process ID and Version Placeholder](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html) +- [ActivityDefinition Process Authorization: Requester and Recipient](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html) +
+ ## Exercise Tasks -1. Modify the `exampleorg_dicProcess`: - * Change the [Message End Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-end-event) to an [Intermediate Message Throw Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-intermediate-throwing-event). This also means that `HelloCosMessage.java` needs to implement `MessageIntermediateThrowEvent` instead of `MessageEndEvent`. - * Add an [Event Based Gateway](https://dsf.dev/process-development/api-v2/bpmn/gateways.html#event-based-gateway) after the throw event - * Configure two cases for the [Event Based Gateway](https://dsf.dev/process-development/api-v2/bpmn/gateways.html#event-based-gateway): - 1. An [Intermediate Message Catch Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-intermediate-catching-event) to catch the `goodbyeDic` message from the `exampleorg_hrpProcess`. - 1. An [Intermediate Timer Catch Event](https://dsf.dev/process-development/api-v2/bpmn/timer-intermediate-catching-events.html) to end the process if no message is sent by the `exampleorg_hrpProcess` after two minutes. - Make sure both cases finish with a process End Event. -2. Modify the `exampleorg_cosProcess` to use a [Message End Event](https://dsf.dev/process-development/api-v2/bpmn/messaging.html#message-end-event) to trigger the process in file `hrp-process.bpmn`. Figure out the values for the `instantiatesCanonical`, `profile` and `messageName` input parameters of the [Message End Event](https://dsf.dev/process-development/api-v2/dsf/messaging.html#message-end-event) based on the [ActivityDefinition](https://dsf.dev/process-development/api-v2/fhir/activitydefinition.html) in file `hrp-process.xml`. Change the `Cos Task` element into a Service Task and include the `CosTask` as the implementation. -3. Modify the process in file `hrp-process.bpmn` and set the _process definition key_ and _version_. Figure out the appropriate values based on the [AcitvityDefinition](https://dsf.dev/process-development/api-v2/fhir/activitydefinition.html) in file `hrp-process.xml`. -4. Add a new process authorization extension element to the ActivityDefinition for `exampleorg_dicProcess` using the [parent organization role coding](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html) where - only remote organizations which are part of `medizininformatik-initiative.de` and have the `HRP` role are allowed to request `goodByeDic` messages and only - organizations which are part of `medizininformatik-initiative.de` and have the `DIC` role are allowed to receive `goodByeDic` messages -
- Don't know which values to choose for roles? - - Take a look at the [dsf-organization-role](https://github.com/datasharingframework/dsf/blob/main/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-organization-role-2.0.0.xml) CodeSystem. -
-5. Forward the value from the [Task.input](https://dsf.dev/process-development/api-v2/fhir/task.html) parameter of the `dicProcess` [Task](https://dsf.dev/process-development/api-v2/fhir/task.html) to the `exampleorg_cosProcess` using the `HelloCosMessage`. To do this, you need to override `HelloCosMessage#getAdditionalInputParameters`. Don't forget to also add the definition of your `tutorial-input` [Input Parameter](https://dsf.dev/process-development/api-v2/fhir/task.html#task-input-parameters) from `task-start-dic-process.xml` to `task-hello-cos.xml`. `HelloHrpMessage` already implements the forwarding of the [Input Parameter](https://dsf.dev/process-development/api-v2/fhir/task.html#task-input-parameters) to the HRP instance and might have to be adjusted e.g. if other the [Input Parameter](https://dsf.dev/process-development/api-v2/fhir/task.html#task-input-parameters) was named differently. -6. Add the process in file `hrp-process.bpmn` to the `TutorialProcessPluginDefinition` and configure the FHIR resources needed for the three processes. -7. Add the `CosTask`, `HelloHrpMessage `, `HrpTask` and `GoodbyeDicMessage` classes as Spring Beans. Don't forget the scope. -8. Again, we introduced changes that break compatibility. Older plugin versions won't execute the HRP process because the process ID in the BPMN model is still invalid and it is missing a version. Increment your resource version to `1.4`. +1. **Modify `exampleorg_dicProcess`** (`dic-process.bpmn`) + + > **Why this step is needed:** Right now the DIC process *ends* when it sends the `helloCos` message. We need it to *pause and wait* for a `goodbyeDic` reply from HRP instead. To achieve this, the Message End Event must become an Intermediate Message Throw Event — meaning the process continues rather than ending. + +
+ Change Message End Event → Intermediate Message Throw Event + + - Click the Message End Event → change its type to **Intermediate Throw Event (Message)** (the envelope in a circle, not filled) + - The field injections (`profile`, `messageName`, `instantiatesCanonical`) stay the same + - In `HelloCosMessage.java`, change the implemented interface from `MessageEndEvent` to `MessageIntermediateThrowEvent` +
+ +
+ Add an Event Based Gateway after the throw event + + - Draw a sequence flow from the Intermediate Throw Event to a new **Event Based Gateway** (pentagon symbol) + - The process will now wait at this gateway until one of the configured events fires +
+ +
+ Configure two outgoing paths from the gateway: one leading to a Message Intermediate Catch Event and one leading to a Timer Intermediate Catch Event. Both paths must end with a plain End Event. + + | Path | Event type | Purpose | + |---|-------------------------------------------------------------------|---| + | Path 1 | **Message Intermediate Catch Event** — message name: `goodbyeDic` | Process continues normally when HRP replies | + | Path 2 | **Timer Intermediate Catch Event** — duration: `PT2M` (2 minutes) | Process ends with a timeout if HRP does not reply | + + Click the Event Based Gateway → choose either **Message Intermediate Catch Event** or **Timer Intermediate Catch Event** +
+ +2. **Modify `exampleorg_cosProcess`** (`cos-process.bpmn`) + + Work through the four sub-steps in order: + +
+ Change the `Cos Task` element into a Service Task. + + - In Camunda Modeler, click the `Cos Task` element → change its type to **Service Task** (wrench icon) + - Set **Implementation** to **Java Class** and enter: `org.tutorial.process.tutorial.service.CosTask` + +
+ + > **Why:** A plain Task element in BPMN is just a label — it does not execute any Java code. A Service Task connects to a Java class and calls its `execute()` method when the process reaches it. + +
+ Change the End Event into a Message End Event. + + - Click the End Event → change its type to **Message End Event** (filled envelope) + - This event will send a Task to `hrp.dsf.test` to start `exampleorg_hrpProcess` + +
+ +
+ Set the Java class and BPMN message reference. + + - Set **Implementation** to **Java Class**: `org.tutorial.process.tutorial.message.HelloHrpMessage` + - In the **Message** tab, create or select a BPMN message and name it `helloHrp` + +
+ +
+ Set the Field Injections. + + Look at `fhir/ActivityDefinition/hrp-process.xml` to find the values: + + | Field | Where to look in hrp-process.xml | Value | + |---|---|---| + | `profile` | `` | `http://example.org/fhir/StructureDefinition/task-hello-hrp|#{version}` | + | `messageName` | `` | `helloHrp` | + | `instantiatesCanonical` | `` → append `|#{version}` | `http://example.org/bpe/Process/hrpProcess|#{version}` | + +
+ +3. **Fix the HRP process id and class names** (`hrp-process.bpmn`) + +
+ Set the process `id` (currently `change_me`) and `version tag`. + + - The id follows the [pattern](https://dsf.dev/process-development/api-v2/dsf/versions-placeholders-urls.html) `{publisher}_{processName}`. Derive it from `fhir/ActivityDefinition/hrp-process.xml`: + - The `` element reads: `http://example.org/bpe/Process/hrpProcess` + - The last segment is `hrpProcess` + - The publisher prefix in this tutorial is `exampleorg` + - Result: **`exampleorg_hrpProcess`** + +
+ + > **Note:** The field is called **`id`** on the `` element — not "key". It uniquely identifies the process within the BPE. + +
+ Fix the Java class name in the `HrpTask` service task. + + Currently the BPMN contains a wrong class name. The correct fully qualified class name is: + ``` + org.tutorial.process.tutorial.service.HrpTask + ``` + +
+ +
+ Fix the Java class name in the `GoodbyeDicMessage` end event. + + Same issue. The correct fully qualified class name is: + ``` + org.tutorial.process.tutorial.message.GoodbyeDicMessage + ``` + +
+ +4. **Add a new authorization block to the DIC `ActivityDefinition`** (`fhir/ActivityDefinition/dic-process.xml`) + + The DIC process must now also accept the `goodbyeDic` message that HRP sends back. Add a **second** `` block with the following four components: + + **4a) Message name** + **4b) Task profile** + **4c) [Requester](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html#remote-parent-organization-role)** — who may send `goodbyeDic`? Only remote organizations with the `HRP` role in `medizininformatik-initiative.de`. + **4d) [Recipient](https://dsf.dev/process-development/api-v2/dsf/requester-and-recipient.html#local-parent-organization-role-1)** — who may receive `goodbyeDic`? Only local organizations with the `DIC` role. + +
+ Not sure which role codes are available? + + Take a look at the [dsf-organization-role](https://github.com/datasharingframework/dsf/blob/main/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-organization-role-2.0.0.xml) CodeSystem. +
+ +
+ Need a refresher on process authorization in ActivityDefinitions? + + Take a look at the [guide on creating ActivityDefinitions](https://dsf.dev/process-development/api-v2/guides/creating-activity-definitions.html#_2-extension-process-authorization). +
+ +5. **Forward the `tutorial-input` parameter from DIC to COS** + +
+ Override `HelloCosMessage#getAdditionalInputParameters` + + The `HelloCosMessage` class (`HelloCosMessage.java`) currently sends no additional input parameters. Override `getAdditionalInputParameters` to read `tutorial-input` from the DIC start Task and include it in the outgoing COS Task. + You can model your implementation on `HelloHrpMessage` which already does this. +
+ +
+ Add the `tutorial-input` slice to `task-hello-cos.xml` + + The COS Task profile (`fhir/StructureDefinition/task-hello-cos.xml`) must declare the `tutorial-input` input parameter, otherwise the DSF will reject the Task as non-conformant. Copy the `tutorial-input` slice definition from `task-start-dic-process.xml` and add it to `task-hello-cos.xml`. +
+ +6. **Register all processes and resources in `TutorialProcessPluginDefinition`** + + - Add `bpe/hrp-process.bpmn` to `getProcessModels()` + - Add a new map entry for `ConstantsTutorial.PROCESS_NAME_FULL_HRP` in `getFhirResourcesByProcessId()` listing its ActivityDefinition, StructureDefinition, and any CodeSystem/ValueSet files + +7. **Add the `CosTask`, `HelloHrpMessage `, `HrpTask` and `GoodbyeDicMessage` classes as Spring Beans. Remember to use the right scope.** +8. **Again, we introduced changes that break compatibility. Older plugin versions won't execute the HRP process because the process id in the BPMN model was still invalid. Increment your resource version to `1.4`.** ## Solution Verification diff --git a/exercises/exercise-7.md b/exercises/exercise-7.md index 7fbff3c5..bcfc0601 100644 --- a/exercises/exercise-7.md +++ b/exercises/exercise-7.md @@ -3,20 +3,23 @@ ___ # Exercise 7 - User Tasks and Task Output Parameters -This exercise introduces a new scenario which will serve as an example on how [User Tasks](https://dsf.dev/process-development/api-v2/bpmn/user-tasks.html), resource download and [Task Output Parameters](https://dsf.dev/process-development/api-v2/fhir/task.html#task-output-parameters) -may be utilized. The scenario is a voting process where one DSF instances of the tutorial setup will send a binary question (yes/no) to the other instances and itself. -The question can be set when starting the voting process. The question will then be answerable through a [QuestionnaireResponse](https://dsf.dev/process-development/api-v2/fhir/questionnaire-and-questionnaireresponse.html) resource on the instance's DSF FHIR server. -The answer then gets sent back to the instance which initiated the voting process. This exercise will focus on [User Tasks](https://dsf.dev/process-development/api-v2/bpmn/user-tasks.html) and [Task Output Parameters](https://dsf.dev/process-development/api-v2/fhir/task.html#task-output-parameters). -The scenario comes with a skeleton including two BPMN models. One for orchestrating the voting process called `exampleorg_votingProcess` found in `voting-process.bpmn` and the subprocess which handles the vote itself found in `vote.bpmn`. -It also includes most of the Java implementation for both processes and the required FHIR resources. Your task will be to fill in the parts concerning the [User Task](https://dsf.dev/process-development/api-v2/bpmn/user-tasks.html) -and [Task Output Parameters](https://dsf.dev/process-development/api-v2/fhir/task.html#task-output-parameters). - -In order to solve this exercise, you should have solved exercise 6 and read the topics on -[User Tasks](https://dsf.dev/process-development/api-v2/guides/user-tasks-in-the-dsf.html#questionnaire-template), [Questionnaire and QuestionnaireResponse](https://dsf.dev/process-development/api-v2/fhir/questionnaire-and-questionnaireresponse.html) -and [adding Task Output Parameters](https://dsf.dev/process-development/api-v2/guides/adding-task-parameters-to-task-profiles.html). - -Solutions to this exercise are found on the branch `solutions/exercise-7`. The skeleton can be found on the branch `skeleton/exercise-7`. It contains an entirely new process with missing pieces that will be added as part of the exercise tasks. -You may checkout the skeleton branch and start working from there. You may also merge the skeleton branch into the branch you were working on so far, at your own risk. +In this exercise you will implement the user-interaction part of a voting process: a binary yes/no question is sent to all three DSF instances, each user answers it via a `QuestionnaireResponse` in the DSF FHIR server web UI, and the results are collected as output parameters on the original Task resource. + +The skeleton for this exercise (two BPMN models + most Java and FHIR resources) is already provided. Your task is to fill in the **User Task** and **Task Output Parameter** pieces. + +- **Skeleton branch:** `skeleton/exercise-7` — check this out and work from there (or merge it into your current branch at your own risk) +- **BPMN files:** `voting-process.bpmn` (orchestrates the voting process) and `vote.bpmn` (handles the vote itself) + +Solutions to this exercise are found on the branch `solutions/exercise-7`. + +
+Background reading (documentation links for this exercise) + +- [User Tasks in the DSF + Questionnaire Template](https://dsf.dev/process-development/api-v2/guides/user-tasks-in-the-dsf.html#questionnaire-template) +- [Questionnaire and QuestionnaireResponse](https://dsf.dev/process-development/api-v2/fhir/questionnaire-and-questionnaireresponse.html) +- [Adding Task Output Parameters](https://dsf.dev/process-development/api-v2/guides/adding-task-parameters-to-task-profiles.html) + +
## Exercise Tasks 1. The StructureDefinition `task-start-voting-process.xml` describes the Task resource which starts the voting process. It already has an input parameter called `binary-question` which stores