Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/release/0.2.0' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
hhund committed Jun 29, 2020
2 parents 314c35e + 9473dc6 commit c31ff40
Show file tree
Hide file tree
Showing 814 changed files with 43,948 additions and 9,129 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Java CI with Maven

on: [push, pull_request]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build with Maven
run: mvn -B package --file pom.xml
5 changes: 0 additions & 5 deletions .travis.yml

This file was deleted.

13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# HiGHmed Data Sharing Framework (HiGHmed DSF)

[![Build Status](https://travis-ci.org/highmed/highmed-dsf.svg?branch=master)](https://travis-ci.org/highmed/highmed-dsf)
[![Java CI with Maven status](https://github.com/highmed/highmed-dsf/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/highmed/highmed-dsf/actions?query=workflow%3A"Java+CI+with+Maven")

The HiGHmed Data Sharing Framework (HiGHmed DSF) implements a distributed process engine based on the BPMN 2.0 and FHIR R4 standards. Within the HiGHmed medical informatics consortium, the DSF is used to support biomedical research with routine data. Every participating site runs a FHIR endpoint (dsf-fhir) accessible by other sites and a business process engine (dsf-bpe) in the local secured network. Authentication between sites is handled using X.509 client/server certificates. The process engines execute BPMN processes in order to coordinate local and remote steps necessary to enable cross-site data sharing and feasibility analyses. This includes access to local data repositories, use-and-access-committee decision support, consent filtering, and privacy preserving record-linkage and pseudonymization.

## Introduction
An introduction to the HiGHmed data sharing architecture can be found on [YouTube](http://www.youtube.com/watch?v=YPcryul5occ) (German).
## Development
Branching follows the git-flow model, for the latest development version see branch [develop](https://github.com/highmed/highmed-dsf/tree/develop).

## License
All code from the HiGHmed Data Sharing Framework is published under the [Apache-2.0 License](LICENSE).

## Wiki
The Wiki with the full documentation can be found [here](https://github.com/highmed/highmed-dsf/wiki).
A full documentation can be found in the [Wiki](https://github.com/highmed/highmed-dsf/wiki).

## Manual Integration Testing (local with Docker)
Prerequisite: Java 11, Maven 3.6, Docker 18
Prerequisite: Java 11, Maven >= 3.6, Docker >= 18

* Build the entire project from the root directory of this repository
```
Expand Down
10 changes: 8 additions & 2 deletions dsf-bpe/dsf-bpe-process-base/pom.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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>

<artifactId>dsf-bpe-process-base</artifactId>

<parent>
<groupId>org.highmed.dsf</groupId>
<artifactId>dsf-bpe-pom</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</parent>

<dependencies>
Expand All @@ -28,5 +28,11 @@
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine</artifactId>
</dependency>

<dependency>
<groupId>de.hs-heilbronn.mi</groupId>
<artifactId>log4j2-utils</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,23 @@ public interface Constants
// String VARIABLE_CORRELATION_KEY = "correlationKey";
String VARIABLE_MULTI_INSTANCE_TARGET = "multiInstanceTarget";
String VARIABLE_MULTI_INSTANCE_TARGETS = "multiInstanceTargets";
String VARIABLE_BLOOM_FILTER_CONFIG = "bloomFilterConfig";
String VARIABLE_QUERY_RESULTS = "queryResults";
String VARIABLE_FINAL_QUERY_RESULTS = "finalQueryResults";
String VARIABLE_TASK = "task";
String VARIABLE_LEADING_TASK = "leadingTask";
String VARIABLE_RESEARCH_STUDY = "researchStudy";
String VARIABLE_COHORTS = "cohorts";
String VARIABLE_TTP_IDENTIFIER = "ttp";
String VARIABLE_QUERIES = "queries";
String VARIABLE_QUERY_PARAMETERS = "queryParameters";
String VARIABLE_BUNDLE_ID = "bundleId";
String VARIABLE_NEEDS_CONSENT_CHECK = "needsConsentCheck";
String VARIABLE_NEEDS_RECORD_LINKAGE = "needsRecordLinkage";

/**
* Stores a List<{@link org.highmed.dsf.bpe.variables.FinalSimpleFeasibilityResult}> of the final results of
* a multi medic simple cohort size query. Do not override, only add new entries to the list.
*/
String VARIABLE_SIMPLE_COHORT_SIZE_QUERY_FINAL_RESULT = "simpleCohortSizeQueryFinalResult";

/**
* Stores a List<{@link org.highmed.dsf.fhir.variables.Outputs}> </>of outputs that have to be written to a
* task resource after the process terminates. Do not override, only add new entries to the list.
* Stores a List<{@link org.highmed.dsf.fhir.variables.Outputs}> </>of outputs that have to be written to a task
* resource after the process terminates. Do not override, only add new entries to the list.
*/
String VARIABLE_PROCESS_OUTPUTS = "outputs";

Expand All @@ -54,13 +51,16 @@ public interface Constants
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_PARTICIPATING_MEDIC_CORRELATION_KEY = "medic-correlation-key";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_NEEDS_CONSENT_CHECK = "needs-consent-check";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_NEEDS_RECORD_LINKAGE = "needs-record-linkage";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_BLOOM_FILTER_CONFIG = "bloom-filter-configuration";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_SINGLE_MEDIC_RESULT = "single-medic-result";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_SINGLE_MEDIC_RESULT_REFERENCE = "single-medic-result-reference";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_PARTICIPATING_MEDICS_COUNT = "participating-medics";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_NOT_ENOUGH_PARTICIPATION = "not-enough-participation";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_MULTI_MEDIC_RESULT = "multi-medic-result";
String CODESYSTEM_HIGHMED_FEASIBILITY_VALUE_RESEARCH_STUDY_REFERENCE = "research-study-reference";

String CODESYSTEM_HIGHMED_UPDATE_WHITELIST = "http://highmed.org/fhir/CodeSystem/update-whitelist";
String CODESYSTEM_HIGHMED_UPDATE_WHITELIST_VALUE_WHITE_LIST = "HiGHmed_white_list";
String CODESYSTEM_HIGHMED_UPDATE_WHITELIST_VALUE_WHITE_LIST = "highmed_whitelist";

String PROCESS_URI_BASE = "http://highmed.org/bpe/Process/";

Expand All @@ -69,6 +69,7 @@ public interface Constants
String ENDPOINT_IDENTIFIER_SYSTEM = "http://highmed.org/fhir/NamingSystem/endpoint-identifier";

String EXTENSION_PARTICIPATING_MEDIC_URI = "http://highmed.org/fhir/StructureDefinition/participating-medic";
String EXTENSION_PARTICIPATING_TTP_URI = "http://highmed.org/fhir/StructureDefinition/participating-ttp";
String EXTENSION_QUERY_URI = "http://highmed.org/fhir/StructureDefinition/query";
String EXTENSION_GROUP_ID_URI = "http://highmed.org/fhir/StructureDefinition/group-id";

Expand All @@ -81,4 +82,6 @@ public interface Constants

CodeType AQL_QUERY_TYPE = new CodeType("application/x-aql-query")
.setSystem("http://highmed.org/fhir/CodeSystem/query-type");

String OPENEHR_MIMETYPE_JSON = "application/json";
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public final void execute(DelegateExecution execution) throws Exception
task = taskHelper.addOutputs(task, outputs);

task.setStatus(Task.TaskStatus.FAILED);
webserviceClient.update(task);
webserviceClient.withMinimalReturn().update(task);

execution.getProcessEngine().getRuntimeService()
.deleteProcessInstance(execution.getProcessInstanceId(), exception.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ public void doExecute(DelegateExecution execution) throws Exception
}
catch (Exception e)
{
String errorMessage =
"Error while sending Task (process: " + processDefinitionKey + ", version: " + versionTag
+ ", message-name: " + messageName + ", business-key: " + businessKey
+ ", correlation-key: " + target.getCorrelationKey() + ") to organization with identifier "
+ target.getTargetOrganizationIdentifierValue() + ": " + e.getMessage();
String errorMessage = "Error while sending Task (process: " + processDefinitionKey + ", version: "
+ versionTag + ", message-name: " + messageName + ", business-key: " + businessKey
+ ", correlation-key: " + target.getCorrelationKey() + ") to organization with identifier "
+ target.getTargetOrganizationIdentifierValue() + ": " + e.getMessage();
logger.warn(errorMessage);
logger.debug("Error while sending Task", e);

Outputs outputs = (Outputs) execution.getVariable(Constants.VARIABLE_PROCESS_OUTPUTS);
outputs.addErrorOutput(errorMessage);
Expand All @@ -96,7 +96,8 @@ public void doExecute(DelegateExecution execution) throws Exception
* Override this method to set a different multiinstance target then the one defined in the process variable
* {@link Constants#VARIABLE_MULTI_INSTANCE_TARGET}
*
* @param execution the delegate execution of this process instance
* @param execution
* the delegate execution of this process instance
* @return {@link MultiInstanceTarget} that should receive the message
*/
protected MultiInstanceTarget getMultiInstanceTarget(DelegateExecution execution)
Expand Down Expand Up @@ -136,26 +137,26 @@ protected void sendTask(String targetOrganizationIdentifierValue, String process

// http://highmed.org/bpe/Process/processDefinitionKey
// http://highmed.org/bpe/Process/processDefinitionKey/versionTag
String instantiatesUri =
Constants.PROCESS_URI_BASE + processDefinitionKey + (versionTag != null && !versionTag.isEmpty() ?
("/" + versionTag) :
"");
String instantiatesUri = Constants.PROCESS_URI_BASE + processDefinitionKey
+ (versionTag != null && !versionTag.isEmpty() ? ("/" + versionTag) : "");
task.setInstantiatesUri(instantiatesUri);

ParameterComponent messageNameInput = new ParameterComponent(new CodeableConcept(
new Coding(Constants.CODESYSTEM_HIGHMED_BPMN, Constants.CODESYSTEM_HIGHMED_BPMN_VALUE_MESSAGE_NAME,
null)), new StringType(messageName));
ParameterComponent messageNameInput = new ParameterComponent(
new CodeableConcept(new Coding(Constants.CODESYSTEM_HIGHMED_BPMN,
Constants.CODESYSTEM_HIGHMED_BPMN_VALUE_MESSAGE_NAME, null)),
new StringType(messageName));
task.getInput().add(messageNameInput);

ParameterComponent businessKeyInput = new ParameterComponent(new CodeableConcept(
new Coding(Constants.CODESYSTEM_HIGHMED_BPMN, Constants.CODESYSTEM_HIGHMED_BPMN_VALUE_BUSINESS_KEY,
null)), new StringType(businessKey));
ParameterComponent businessKeyInput = new ParameterComponent(
new CodeableConcept(new Coding(Constants.CODESYSTEM_HIGHMED_BPMN,
Constants.CODESYSTEM_HIGHMED_BPMN_VALUE_BUSINESS_KEY, null)),
new StringType(businessKey));
task.getInput().add(businessKeyInput);

if (correlationKey != null)
{
ParameterComponent correlationKeyInput = new ParameterComponent(new CodeableConcept(
new Coding(Constants.CODESYSTEM_HIGHMED_BPMN,
ParameterComponent correlationKeyInput = new ParameterComponent(
new CodeableConcept(new Coding(Constants.CODESYSTEM_HIGHMED_BPMN,
Constants.CODESYSTEM_HIGHMED_BPMN_VALUE_CORRELATION_KEY, null)),
new StringType(correlationKey));
task.getInput().add(correlationKeyInput);
Expand All @@ -170,7 +171,7 @@ protected void sendTask(String targetOrganizationIdentifierValue, String process
client.getBaseUrl());
logger.trace("Task resource to send: {}", fhirContext.newJsonParser().encodeResourceToString(task));

client.create(task);
client.withMinimalReturn().create(task);
}

private FhirWebserviceClient getFhirClient(Task task, String targetOrganizationIdentifierValue)
Expand All @@ -183,9 +184,8 @@ private FhirWebserviceClient getFhirClient(Task task, String targetOrganizationI
else
{
logger.trace("Using remote webservice client");
return getFhirWebserviceClientProvider()
.getRemoteWebserviceClient(organizationProvider.getDefaultIdentifierSystem(),
targetOrganizationIdentifierValue);
return getFhirWebserviceClientProvider().getRemoteWebserviceClient(
organizationProvider.getDefaultIdentifierSystem(), targetOrganizationIdentifierValue);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.highmed.dsf.fhir.variables.Outputs;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Task;
import org.hl7.fhir.r4.model.Task.ParameterComponent;
import org.hl7.fhir.r4.model.Task.TaskOutputComponent;
import org.hl7.fhir.r4.model.UrlType;

public interface TaskHelper
Expand All @@ -24,17 +26,27 @@ public interface TaskHelper

Optional<UrlType> getFirstInputParameterUrlValue(Task task, String system, String code);

Stream<Task.ParameterComponent> getInputParameterWithExtension(Task task, String system, String code, String url);

Stream<UrlType> getInputParameterUrlValues(Task task, String system, String code);

Task.ParameterComponent createInput(String system, String code, String value);
Optional<byte[]> getFirstInputParameterByteValue(Task task, String system, String code);

Stream<ParameterComponent> getInputParameterWithExtension(Task task, String system, String code, String url);

ParameterComponent createInput(String system, String code, String value);

ParameterComponent createInput(String system, String code, boolean value);

ParameterComponent createInput(String system, String code, Reference reference);

ParameterComponent createInput(String system, String code, byte[] bytes);

ParameterComponent createInputUnsignedInt(String system, String code, int value);

Task.ParameterComponent createInput(String system, String code, boolean value);
ParameterComponent createInput(String system, String code, int value);

Task.ParameterComponent createInput(String system, String code, Reference reference);
TaskOutputComponent createOutput(String system, String code, String value);

Task.TaskOutputComponent createOutput(String system, String code, String value);
TaskOutputComponent createOutputUnsignedInt(String system, String code, int value);

Task addOutputs(Task task, Outputs outputs);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.highmed.dsf.fhir.variables;

import java.security.Key;

import javax.crypto.spec.SecretKeySpec;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class BloomFilterConfig
{
private static final int SEED_LENGTH = 8;
private static final int HMAC_SHA2_KEY_LENGTH = 32;
private static final int HMAC_SHA3_KEY_LENGTH = 32;

private final long permutationSeed;

@JsonDeserialize(using = KeyDeserializer.class)
@JsonSerialize(using = KeySerializer.class)
private final Key hmacSha2Key;

@JsonDeserialize(using = KeyDeserializer.class)
@JsonSerialize(using = KeySerializer.class)
private final Key hmacSha3Key;

public static BloomFilterConfig fromBytes(byte[] bytes)
{
if (bytes.length != SEED_LENGTH + HMAC_SHA2_KEY_LENGTH + HMAC_SHA3_KEY_LENGTH)
throw new IllegalArgumentException(
"bytes.length = " + (SEED_LENGTH + HMAC_SHA2_KEY_LENGTH + HMAC_SHA3_KEY_LENGTH)
+ " expected, but got " + bytes.length);

byte[] seed = new byte[SEED_LENGTH];
byte[] key1 = new byte[HMAC_SHA2_KEY_LENGTH];
byte[] key2 = new byte[HMAC_SHA3_KEY_LENGTH];

System.arraycopy(bytes, 0, seed, 0, seed.length);
System.arraycopy(bytes, seed.length, key1, 0, key1.length);
System.arraycopy(bytes, seed.length + key1.length, key2, 0, key2.length);

long permutationSeed = bigEndianToLong(seed);
Key hmacSha2Key = new SecretKeySpec(key1, "HmacSHA256");
Key hmacSha3Key = new SecretKeySpec(key2, "HmacSHA3-256");

return new BloomFilterConfig(permutationSeed, hmacSha2Key, hmacSha3Key);
}

@JsonCreator
public BloomFilterConfig(@JsonProperty("permutationSeed") long permutationSeed,
@JsonProperty("hmacSha2Key") Key hmacSha2Key, @JsonProperty("hmacSha3Key") Key hmacSha3Key)
{
this.permutationSeed = permutationSeed;
this.hmacSha2Key = hmacSha2Key;
this.hmacSha3Key = hmacSha3Key;
}

public long getPermutationSeed()
{
return permutationSeed;
}

public Key getHmacSha2Key()
{
return hmacSha2Key;
}

public Key getHmacSha3Key()
{
return hmacSha3Key;
}

@JsonIgnore
public byte[] toBytes()
{
byte[] bytes = new byte[SEED_LENGTH + HMAC_SHA2_KEY_LENGTH + HMAC_SHA3_KEY_LENGTH];

byte[] seed = longToBigEndian(permutationSeed);
byte[] key1 = hmacSha2Key.getEncoded();
byte[] key2 = hmacSha3Key.getEncoded();

System.arraycopy(seed, 0, bytes, 0, seed.length);
System.arraycopy(key1, 0, bytes, SEED_LENGTH, key1.length);
System.arraycopy(key2, 0, bytes, SEED_LENGTH + HMAC_SHA2_KEY_LENGTH, key2.length);

return bytes;
}

private static byte[] longToBigEndian(long l)
{
return new byte[] { (byte) (l >>> 56), (byte) (l >>> 48), (byte) (l >>> 40), (byte) (l >>> 32),
(byte) (l >>> 24), (byte) (l >>> 16), (byte) (l >>> 8), (byte) (l >>> 0) };
}

private static long bigEndianToLong(byte[] b)
{
return (((long) b[0] << 56) + ((long) (b[1] & 255) << 48) + ((long) (b[2] & 255) << 40)
+ ((long) (b[3] & 255) << 32) + ((long) (b[4] & 255) << 24) + ((b[5] & 255) << 16) + ((b[6] & 255) << 8)
+ ((b[7] & 255) << 0));
}
}
Loading

0 comments on commit c31ff40

Please sign in to comment.