diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 7a482c8..868f86b 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -4,6 +4,7 @@ name: Java CI Publish with Maven on: + workflow_dispatch: pull_request: types: [closed] branches: [develop] diff --git a/mii-dsf-process-projectathon-data-transfer/pom.xml b/mii-dsf-process-projectathon-data-transfer/pom.xml index caabcb6..cf27e48 100644 --- a/mii-dsf-process-projectathon-data-transfer/pom.xml +++ b/mii-dsf-process-projectathon-data-transfer/pom.xml @@ -1,6 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 mii-process-projectathon-data-transfer @@ -8,7 +8,7 @@ de.medizininformatik-initiative mii-dsf-processes - 0.1.0-SNAPSHOT + 0.1.0-RC1 @@ -26,6 +26,11 @@ hapi-fhir-client provided + + org.apache.tika + tika-core + provided + de.medizininformatik-initiative @@ -67,10 +72,15 @@ -classpath - de.medizininformatik_initiative.processes.documentation.generator.DocumentationGenerator - de.medizininformatik_initiative.processes.projectathon.data_transfer.spring.config + + de.medizininformatik_initiative.processes.documentation.generator.DocumentationGenerator + + + de.medizininformatik_initiative.processes.projectathon.data_transfer + true + true compile ${project.basedir} @@ -92,12 +102,19 @@ ${project.artifactId} ${project.version} + + org.apache.tika + tika-core + ${apache.tika.version} + - ../mii-dsf-processes-docker-test-setup/dic/bpe/process + + ../mii-dsf-processes-docker-test-setup/dic/bpe/process/${project.artifactId}-${project.version} + - copy-hapi-fhir-client/dic + copy-dependencies/dic package copy @@ -126,12 +143,19 @@ ${project.artifactId} ${project.version} + + org.apache.tika + tika-core + ${apache.tika.version} + - ../mii-dsf-processes-docker-test-setup/cos/bpe/process + + ../mii-dsf-processes-docker-test-setup/cos/bpe/process/${project.artifactId}-${project.version} + - copy-hapi-fhir-client/cos + copy-dependencies/cos package copy @@ -147,6 +171,23 @@ ../mii-dsf-processes-docker-test-setup/cos/bpe/plugin + + copy-dependencies/assembly + package + + copy + + + + + org.apache.tika + tika-core + ${apache.tika.version} + + + ${project.build.directory}/lib + + @@ -155,28 +196,36 @@ - ../mii-dsf-processes-docker-test-setup/dic/bpe/process + + ../mii-dsf-processes-docker-test-setup/dic/bpe/process/${project.artifactId}-${project.version} + - ${project.artifactId}-${project.version}.jar + ** false - ../mii-dsf-processes-docker-test-setup/dic/bpe/plugin + + ../mii-dsf-processes-docker-test-setup/dic/bpe/plugin + hapi-fhir-client-${hapi.version}.jar false - ../mii-dsf-processes-docker-test-setup/cos/bpe/process + + ../mii-dsf-processes-docker-test-setup/cos/bpe/process/${project.artifactId}-${project.version} + - ${project.artifactId}-${project.version}.jar + ** false - ../mii-dsf-processes-docker-test-setup/cos/bpe/plugin + + ../mii-dsf-processes-docker-test-setup/cos/bpe/plugin + hapi-fhir-client-${hapi.version}.jar @@ -185,6 +234,25 @@ + + org.apache.maven.plugins + maven-assembly-plugin + + + zip-assembly + install + + single + + + + + false + + src/assembly/zip.xml + + + \ No newline at end of file diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/KdsClientFactory.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/KdsClientFactory.java index e779d66..e3b96b6 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/KdsClientFactory.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/KdsClientFactory.java @@ -90,8 +90,8 @@ public void onContextRefreshedEvent(ContextRefreshedEvent event) try { logger.info( - "Testing connection to projectathon FHIR server with {trustStorePath: {}, certificatePath: {}, privateKeyPath: {}, privateKeyPassword: {}," - + " basicAuthUsername {}, basicAuthPassword {}, bearerToken {}, serverBase: {}, proxyUrl {}, proxyUsername, proxyPassword {}}", + "Testing connection to KDS FHIR server with {trustStorePath: {}, certificatePath: {}, privateKeyPath: {}, privateKeyPassword: {}," + + " basicAuthUsername {}, basicAuthPassword {}, bearerToken {}, serverBase: {}, proxyUrl {}, proxyUsername {}, proxyPassword {}}", trustStorePath, certificatePath, privateKeyPath, privateKeyPassword != null ? "***" : "null", kdsServerBasicAuthUsername, kdsServerBasicAuthPassword != null ? "***" : "null", kdsServerBearerToken != null ? "***" : "null", kdsServerBase, proxyUrl, proxyUsername, diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientImpl.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientImpl.java index 3a64d47..0dd29a4 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientImpl.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientImpl.java @@ -6,15 +6,11 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.IdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import de.medizininformatik_initiative.processes.projectathon.data_transfer.client.KdsClient; public class KdsFhirClientImpl implements KdsFhirClient { - private static final Logger logger = LoggerFactory.getLogger(KdsFhirClientImpl.class); - private KdsClient kdsClient; public KdsFhirClientImpl(KdsClient kdsClient) diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientStub.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientStub.java index 1f09efb..da77ddf 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientStub.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/client/fhir/KdsFhirClientStub.java @@ -14,15 +14,11 @@ import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.ResourceType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import de.medizininformatik_initiative.processes.projectathon.data_transfer.client.KdsClient; public final class KdsFhirClientStub implements KdsFhirClient { - private static final Logger logger = LoggerFactory.getLogger(KdsFhirClientStub.class); - private final KdsClient kdsClient; public KdsFhirClientStub(KdsClient kdsClient) diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/KeyProviderImpl.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/KeyProviderImpl.java index ec0c05d..5a72db2 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/KeyProviderImpl.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/KeyProviderImpl.java @@ -37,7 +37,7 @@ import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; -import ca.uhn.fhir.context.FhirContext; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.LoggingHelper; import de.rwh.utils.crypto.io.PemIo; public class KeyProviderImpl implements KeyProvider, InitializingBean @@ -152,17 +152,16 @@ public void onContextRefreshedEvent(ContextRefreshedEvent event) else if (output.getTotal() == 0) { logger.info("Creating new PublicKey Bundle on DSF FHIR server..."); - Bundle bundleToCreate = getPublicKeyBundle(); + Bundle bundleToCreate = createPublicKeyBundle(); bundleOnServer = clientProvider.getLocalWebserviceClient().createConditionaly(bundleToCreate, "identifier=" + CODESYSTEM_MII_CRYPTOGRAPHY + "|" + CODESYSTEM_MII_CRYPTOGRAPHY_VALUE_PUBLIC_KEY); } else { - logger.warn("Exist > 1 Bundle with identifier={}|{}", CODESYSTEM_MII_CRYPTOGRAPHY, - CODESYSTEM_MII_CRYPTOGRAPHY_VALUE_PUBLIC_KEY); - throw new RuntimeException("Exist > 1 Bundle with identifier=" + CODESYSTEM_MII_CRYPTOGRAPHY + "|" - + CODESYSTEM_MII_CRYPTOGRAPHY_VALUE_PUBLIC_KEY); + throw new RuntimeException( + "Exist " + output.getTotal() + " Bundle with identifier=" + CODESYSTEM_MII_CRYPTOGRAPHY + + "|" + CODESYSTEM_MII_CRYPTOGRAPHY_VALUE_PUBLIC_KEY + ", expected only one"); } logger.info("PublicKey Bundle has id='{}'", bundleOnServer.getId()); @@ -175,7 +174,7 @@ else if (output.getTotal() == 0) } } - private Bundle getPublicKeyBundle() + private Bundle createPublicKeyBundle() { Date date = new Date(); @@ -202,8 +201,7 @@ private Bundle getPublicKeyBundle() readAccessHelper.addAll(bundle); - logger.debug("Created Bundle: {}", - FhirContext.forR4().newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle)); + LoggingHelper.logDebugBundle("Created Bundle", bundle); return bundle; } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/RsaAesGcmUtil.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/RsaAesGcmUtil.java index db7beb1..b6d0aa0 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/RsaAesGcmUtil.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/crypto/RsaAesGcmUtil.java @@ -24,19 +24,17 @@ public class RsaAesGcmUtil private static final String RSA_CIPHER = "RSA/ECB/PKCS1Padding"; private static final int RSA_KEY_LENGTH = 4096; private static final int ENCRYPTED_AES_KEY_LENGTH = 512; - /** - * AAD: some random bytes - */ - private static final byte[] AAD = "JLCbSbIk5VAvBtKs4ypnDw3AJRfSBWXFHUxl78WBJw".getBytes(StandardCharsets.UTF_8); - public static byte[] encrypt(PublicKey publicKey, byte[] data) + public static byte[] encrypt(PublicKey publicKey, byte[] data, String sendingOrganizationIdentifier, + String receivingOrganizationIdentifier) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, ShortBufferException { SecretKey aesKey = AesGcmUtil.generateAES256Key(); + byte[] aad = getAad(sendingOrganizationIdentifier, receivingOrganizationIdentifier); byte[] encryptedAesKey = encryptRsa(aesKey, publicKey); - byte[] encryptedData = AesGcmUtil.encrypt(data, AAD, aesKey); + byte[] encryptedData = AesGcmUtil.encrypt(data, aad, aesKey); if (encryptedAesKey.length != ENCRYPTED_AES_KEY_LENGTH) throw new IllegalStateException("Encrypted AES key length = " + ENCRYPTED_AES_KEY_LENGTH + " expected"); @@ -48,7 +46,8 @@ public static byte[] encrypt(PublicKey publicKey, byte[] data) return output; } - public static byte[] decrypt(PrivateKey privateKey, byte[] encrypted) + public static byte[] decrypt(PrivateKey privateKey, byte[] encrypted, String sendingOrganizationIdentifier, + String receivingOrganizationIdentifier) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { @@ -57,9 +56,10 @@ public static byte[] decrypt(PrivateKey privateKey, byte[] encrypted) System.arraycopy(encrypted, 0, encryptedAesKey, 0, ENCRYPTED_AES_KEY_LENGTH); System.arraycopy(encrypted, ENCRYPTED_AES_KEY_LENGTH, encryptedData, 0, encrypted.length - ENCRYPTED_AES_KEY_LENGTH); + byte[] aad = getAad(sendingOrganizationIdentifier, receivingOrganizationIdentifier); SecretKey key = decryptRsa(encryptedAesKey, privateKey); - return AesGcmUtil.decrypt(encryptedData, AAD, key); + return AesGcmUtil.decrypt(encryptedData, aad, key); } public static KeyPair generateRsa4096KeyPair() throws NoSuchAlgorithmException @@ -89,4 +89,9 @@ private static SecretKey decryptRsa(byte[] encryptedKey, PrivateKey privateKey) return new SecretKeySpec(decrypted, "AES"); } + + private static byte[] getAad(String sendingOrganizationIdentifier, String receivingOrganizationIdentifier) + { + return (sendingOrganizationIdentifier + "|" + receivingOrganizationIdentifier).getBytes(StandardCharsets.UTF_8); + } } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/message/StartReceiveProcess.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/message/StartReceiveProcess.java index 84e40a4..b94a8cf 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/message/StartReceiveProcess.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/message/StartReceiveProcess.java @@ -38,4 +38,4 @@ protected Stream getAdditionalInputParameters(DelegateExecut return Stream.of(parameterComponent); } -} +} \ No newline at end of file diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/CreateBundle.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/CreateBundle.java new file mode 100644 index 0000000..a19e4ee --- /dev/null +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/CreateBundle.java @@ -0,0 +1,91 @@ +package de.medizininformatik_initiative.processes.projectathon.data_transfer.service; + +import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_BINARY; +import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_DATA_SET; +import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_DOCUMENT_REFERENCE; +import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_PROJECT_IDENTIFIER; +import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.NAMINGSYSTEM_MII_PROJECT_IDENTIFIER; +import static org.highmed.dsf.bpe.ConstantsBase.NAMINGSYSTEM_HIGHMED_ORGANIZATION_IDENTIFIER; +import static org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION; +import static org.hl7.fhir.r4.model.DocumentReference.ReferredDocumentStatus.FINAL; +import static org.hl7.fhir.r4.model.Enumerations.DocumentReferenceStatus.CURRENT; + +import java.util.Objects; +import java.util.UUID; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.highmed.dsf.bpe.delegate.AbstractServiceDelegate; +import org.highmed.dsf.fhir.authorization.read.ReadAccessHelper; +import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider; +import org.highmed.dsf.fhir.organization.OrganizationProvider; +import org.highmed.dsf.fhir.task.TaskHelper; +import org.highmed.dsf.fhir.variables.FhirResourceValues; +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.ResourceType; +import org.springframework.beans.factory.InitializingBean; + +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.LoggingHelper; + +public class CreateBundle extends AbstractServiceDelegate implements InitializingBean +{ + private final OrganizationProvider organizationProvider; + + public CreateBundle(FhirWebserviceClientProvider clientProvider, TaskHelper taskHelper, + ReadAccessHelper readAccessHelper, OrganizationProvider organizationProvider) + { + super(clientProvider, taskHelper, readAccessHelper); + + this.organizationProvider = organizationProvider; + } + + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + + Objects.requireNonNull(organizationProvider, "organizationProvider"); + } + + @Override + protected void doExecute(DelegateExecution execution) + { + String projectIdentifier = (String) execution.getVariable(BPMN_EXECUTION_VARIABLE_PROJECT_IDENTIFIER); + DocumentReference documentReference = (DocumentReference) execution + .getVariable(BPMN_EXECUTION_VARIABLE_DOCUMENT_REFERENCE); + Binary binary = (Binary) execution.getVariable(BPMN_EXECUTION_VARIABLE_BINARY); + + Bundle bundle = createTransactionBundle(projectIdentifier, documentReference, binary); + + LoggingHelper.logDebugBundle("Created Bundle", bundle); + + execution.setVariable(BPMN_EXECUTION_VARIABLE_DATA_SET, FhirResourceValues.create(bundle)); + } + + private Bundle createTransactionBundle(String projectIdentifier, DocumentReference documentReference, Binary binary) + { + Binary binaryToTransmit = new Binary().setContentType(binary.getContentType()); + binaryToTransmit.setContent(binary.getContent()); + binaryToTransmit.setId(UUID.randomUUID().toString()); + + DocumentReference documentReferenceToTransmit = new DocumentReference().setStatus(CURRENT).setDocStatus(FINAL); + documentReferenceToTransmit.getMasterIdentifier().setSystem(NAMINGSYSTEM_MII_PROJECT_IDENTIFIER) + .setValue(projectIdentifier); + documentReferenceToTransmit.addAuthor().setType(ResourceType.Organization.name()).getIdentifier() + .setSystem(NAMINGSYSTEM_HIGHMED_ORGANIZATION_IDENTIFIER) + .setValue(organizationProvider.getLocalIdentifierValue()); + documentReferenceToTransmit.setDate(documentReference.getDate()); + documentReferenceToTransmit.addContent().getAttachment().setContentType(binary.getContentType()) + .setUrl("urn:uuid:" + binaryToTransmit.getId()); + + Bundle bundle = new Bundle().setType(TRANSACTION); + bundle.addEntry().setResource(documentReferenceToTransmit) + .setFullUrl("urn:uuid:" + documentReferenceToTransmit.getId()).getRequest() + .setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.DocumentReference.name()); + bundle.addEntry().setResource(binaryToTransmit).setFullUrl("urn:uuid:" + binaryToTransmit.getId()).getRequest() + .setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.Binary.name()); + + return bundle; + } +} diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DecryptData.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DecryptData.java index 605819a..9efdc7a 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DecryptData.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DecryptData.java @@ -11,28 +11,29 @@ import org.highmed.dsf.bpe.delegate.AbstractServiceDelegate; import org.highmed.dsf.fhir.authorization.read.ReadAccessHelper; import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider; +import org.highmed.dsf.fhir.organization.OrganizationProvider; import org.highmed.dsf.fhir.task.TaskHelper; import org.highmed.dsf.fhir.variables.FhirResourceValues; import org.hl7.fhir.r4.model.Bundle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.hl7.fhir.r4.model.Reference; import org.springframework.beans.factory.InitializingBean; import ca.uhn.fhir.context.FhirContext; import de.medizininformatik_initiative.processes.projectathon.data_transfer.crypto.KeyProvider; import de.medizininformatik_initiative.processes.projectathon.data_transfer.crypto.RsaAesGcmUtil; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.LoggingHelper; public class DecryptData extends AbstractServiceDelegate implements InitializingBean { - private static final Logger logger = LoggerFactory.getLogger(DecryptData.class); - + private final OrganizationProvider organizationProvider; private final KeyProvider keyProvider; public DecryptData(FhirWebserviceClientProvider clientProvider, TaskHelper taskHelper, - ReadAccessHelper readAccessHelper, KeyProvider keyProvider) + ReadAccessHelper readAccessHelper, OrganizationProvider organizationProvider, KeyProvider keyProvider) { super(clientProvider, taskHelper, readAccessHelper); + this.organizationProvider = organizationProvider; this.keyProvider = keyProvider; } @@ -41,6 +42,7 @@ public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); + Objects.requireNonNull(organizationProvider, "organizationProvider"); Objects.requireNonNull(keyProvider, "keyProvider"); } @@ -48,26 +50,40 @@ public void afterPropertiesSet() throws Exception protected void doExecute(DelegateExecution execution) { byte[] bundleEncrypted = (byte[]) execution.getVariable(BPMN_EXECUTION_VARIABLE_DATA_SET_ENCRYPTED); + String localOrganizationIdentifier = organizationProvider.getLocalIdentifierValue(); + String sendingOrganizationIdentifier = getSendingOrganizationIdentifier(); + + Bundle bundleDecrypted = decryptBundle(keyProvider.getPrivateKey(), bundleEncrypted, + sendingOrganizationIdentifier, localOrganizationIdentifier); - Bundle bundleDecrypted = decryptBundle(keyProvider.getPrivateKey(), bundleEncrypted); - logger.debug("Decrypted Bundle: {}", - FhirContext.forR4().newXmlParser().encodeResourceToString(bundleDecrypted)); + LoggingHelper.logDebugBundle("Decrypted Bundle", bundleDecrypted); execution.setVariable(BPMN_EXECUTION_VARIABLE_DATA_SET, FhirResourceValues.create(bundleDecrypted)); } - private Bundle decryptBundle(PrivateKey privateKey, byte[] bundleEncrypted) + private String getSendingOrganizationIdentifier() + { + Reference requester = getLeadingTaskFromExecutionVariables().getRequester(); + + if (requester.hasIdentifier() && requester.getIdentifier().hasValue()) + return requester.getIdentifier().getValue(); + else + throw new IllegalArgumentException("Task is missing requester identifier"); + } + + private Bundle decryptBundle(PrivateKey privateKey, byte[] bundleEncrypted, String sendingOrganizationIdentifier, + String receivingOrganizationIdentifier) { try { - byte[] bundleDecrypted = RsaAesGcmUtil.decrypt(privateKey, bundleEncrypted); + byte[] bundleDecrypted = RsaAesGcmUtil.decrypt(privateKey, bundleEncrypted, sendingOrganizationIdentifier, + receivingOrganizationIdentifier); String bundleString = new String(bundleDecrypted, StandardCharsets.UTF_8); return (Bundle) FhirContext.forR4().newXmlParser().parseResource(bundleString); } catch (Exception exception) { String taskId = getLeadingTaskFromExecutionVariables().getId(); - logger.warn("Could not decrypt received data-set for task with id='{}'", taskId); throw new RuntimeException("Could not decrypt received data-set for task with id='" + taskId + "'"); } } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DeleteData.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DeleteData.java index 0222b4e..adaf03b 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DeleteData.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DeleteData.java @@ -31,7 +31,8 @@ protected void doExecute(DelegateExecution execution) IdType binaryId = new IdType((String) execution.getVariable(BPMN_EXECUTION_VARIABLE_DATA_SET_REFERENCE)); logger.info( - "Permanently deleting Binary with id='{}' provided for project-identifier='{}' referenced in Task with id='{}'...", + "Permanently deleting encrypted Binary with id='{}' provided for project-identifier='{}' " + + "referenced in Task with id='{}'...", binaryId.getValue(), projectIdentifier, getLeadingTaskFromExecutionVariables().getId()); deletePermanently(binaryId); diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DownloadData.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DownloadData.java index 1b7fd98..db3ef78 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DownloadData.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/DownloadData.java @@ -56,7 +56,7 @@ private IdType getDataSetReference(Task task) throw new IllegalArgumentException("No data-set reference present in task with id='" + task.getId() + "'"); if (dataSetReferences.size() > 1) - logger.warn("Found > 1 data-set references ({}) in task with id='{}', using only the first", + logger.warn("Found {} data-set references in task with id='{}', using only the first", dataSetReferences.size(), task.getId()); return new IdType(dataSetReferences.get(0)); @@ -72,11 +72,10 @@ private byte[] readDataSet(IdType dataSetReference) { return binary.readAllBytes(); } - catch (Exception excpetion) + catch (Exception exception) { - logger.warn("Downloading Binary with id='{}' failed", dataSetReference.getValue()); throw new RuntimeException("Downloading Binary with id='" + dataSetReference.getValue() + "' failed.", - excpetion); + exception); } } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/EncryptData.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/EncryptData.java index ea8bc60..97ce0a5 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/EncryptData.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/EncryptData.java @@ -19,12 +19,14 @@ import java.util.Map; import java.util.Objects; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.highmed.dsf.bpe.delegate.AbstractServiceDelegate; import org.highmed.dsf.fhir.authorization.read.ReadAccessHelper; import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider; import org.highmed.dsf.fhir.organization.EndpointProvider; +import org.highmed.dsf.fhir.organization.OrganizationProvider; import org.highmed.dsf.fhir.task.TaskHelper; import org.hl7.fhir.r4.model.Attachment; import org.hl7.fhir.r4.model.Binary; @@ -42,13 +44,16 @@ public class EncryptData extends AbstractServiceDelegate implements Initializing { private static final Logger logger = LoggerFactory.getLogger(EncryptData.class); + private final OrganizationProvider organizationProvider; private final EndpointProvider endpointProvider; public EncryptData(FhirWebserviceClientProvider clientProvider, TaskHelper taskHelper, - ReadAccessHelper readAccessHelper, EndpointProvider endpointProvider) + ReadAccessHelper readAccessHelper, OrganizationProvider organizationProvider, + EndpointProvider endpointProvider) { super(clientProvider, taskHelper, readAccessHelper); + this.organizationProvider = organizationProvider; this.endpointProvider = endpointProvider; } @@ -57,6 +62,7 @@ public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); + Objects.requireNonNull(organizationProvider, "organizationProvider"); Objects.requireNonNull(endpointProvider, "endpointProvider"); } @@ -65,10 +71,12 @@ protected void doExecute(DelegateExecution execution) { String coordinatingSiteIdentifier = (String) execution .getVariable(BPMN_EXECUTION_VARIABLE_COORDINATING_SITE_IDENTIFIER); + String localOrganizationIdentifier = organizationProvider.getLocalIdentifierValue(); + Bundle toEncrypt = (Bundle) execution.getVariable(BPMN_EXECUTION_VARIABLE_DATA_SET); PublicKey publicKey = readPublicKey(coordinatingSiteIdentifier); - byte[] encrypted = encrypt(publicKey, toEncrypt); + byte[] encrypted = encrypt(publicKey, toEncrypt, localOrganizationIdentifier, coordinatingSiteIdentifier); execution.setVariable(BPMN_EXECUTION_VARIABLE_DATA_SET_ENCRYPTED, encrypted); } @@ -117,8 +125,8 @@ private DocumentReference getDocumentReference(Bundle bundle) "Could not find any DocumentReference in PublicKey Bundle with id='" + bundle.getId() + "'"); if (documentReferences.size() > 1) - logger.warn("Found > 1 DocumentReferences ({}) in PublicKey Bundle with id='{}'", documentReferences.size(), - bundle.getId()); + logger.warn("Found {} DocumentReferences in PublicKey Bundle with id='{}', using the first", + documentReferences.size(), bundle.getId()); return documentReferences.get(0); } @@ -133,7 +141,8 @@ private Binary getBinary(Bundle bundle) "Could not find any Binary in PublicKey Bundle with id='" + bundle.getId() + "'"); if (binaries.size() > 1) - logger.warn("Found > 1 Binaries ({}) in PublicKey Bundle with id='{}'", binaries.size(), bundle.getId()); + logger.warn("Found {} Binaries in PublicKey Bundle with id='{}', using the first", binaries.size(), + bundle.getId()); return binaries.get(0); } @@ -146,9 +155,8 @@ private PublicKey getPublicKey(Binary binary, String publicKeyBundleId) } catch (Exception exception) { - logger.info("Could not generate PublicKey from Binary in PublicKey Bundle with id='{}'", publicKeyBundleId); throw new RuntimeException( - "Could not generate PublicKey from Binary in PublicKey Bundle with id='" + publicKeyBundleId + "'", + "Could not read PublicKey from Binary in PublicKey Bundle with id='" + publicKeyBundleId + "'", exception); } } @@ -161,57 +169,37 @@ private void checkHash(DocumentReference documentReference, PublicKey publicKey, .map(Attachment::getHash).count(); if (numberOfHashes < 1) - { throw new RuntimeException( "Could not find any sha256-hash in DocumentReference from Bundle with id='" + bundleId + "'"); - } if (numberOfHashes > 1) - { - logger.warn("DocumentReference contains > 1 sha256-hashes ({}), using the first from Bundle with id='{}'", + logger.warn("DocumentReference contains {} sha256-hashes, using the first from Bundle with id='{}'", numberOfHashes, bundleId); - } byte[] documentReferenceHash = documentReference.getContentFirstRep().getAttachment().getHash(); byte[] publicKeyHash = DigestUtils.sha256(publicKey.getEncoded()); logger.debug("DocumentReference PublicKey sha256-hash='{}' from Bundle with id='{}'", - byteToHex(documentReferenceHash), bundleId); - logger.debug("PublicKey actual sha256-hash='{}' from Bundle with id='{}'", byteToHex(publicKeyHash), bundleId); + Hex.encodeHexString(documentReferenceHash), bundleId); + logger.debug("PublicKey actual sha256-hash='{}' from Bundle with id='{}'", Hex.encodeHexString(publicKeyHash), + bundleId); if (!Arrays.equals(documentReferenceHash, publicKeyHash)) - { throw new RuntimeException( "Sha256-hash in DocumentReference does not match computed sha256-hash of Binary in Bundle with id='" + bundleId + "'"); - } - } - - private String byteToHex(byte[] toConvert) - { - StringBuilder hexString = new StringBuilder(2 * toConvert.length); - - for (byte b : toConvert) - { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) - { - hexString.append('0'); - } - hexString.append(hex); - } - - return hexString.toString(); } - private byte[] encrypt(PublicKey publicKey, Bundle bundle) + private byte[] encrypt(PublicKey publicKey, Bundle bundle, String sendingOrganizationIdentifier, + String receivingOrganizationIdentifier) { try { byte[] toEncrypt = FhirContext.forR4().newXmlParser().encodeResourceToString(bundle) .getBytes(StandardCharsets.UTF_8); - return RsaAesGcmUtil.encrypt(publicKey, toEncrypt); + return RsaAesGcmUtil.encrypt(publicKey, toEncrypt, sendingOrganizationIdentifier, + receivingOrganizationIdentifier); } catch (Exception exception) { diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ReadData.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ReadData.java index 204eba8..2b406fa 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ReadData.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ReadData.java @@ -21,16 +21,20 @@ import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider; import org.highmed.dsf.fhir.task.TaskHelper; import org.highmed.dsf.fhir.variables.FhirResourceValues; +import org.hl7.fhir.r4.model.Attachment; import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.DocumentReference; -import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.ResourceType; import org.hl7.fhir.r4.model.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.FhirContext; import de.medizininformatik_initiative.processes.projectathon.data_transfer.client.KdsClientFactory; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.LoggingHelper; public class ReadData extends AbstractServiceDelegate { @@ -65,11 +69,10 @@ protected void doExecute(DelegateExecution execution) String coordinatingSiteIdentifier = getCoordinatingSiteIdentifier(task); DocumentReference documentReference = readDocumentReference(projectIdentifier, task.getId()); - logger.debug("Read DocumentReference: {}", - FhirContext.forR4().newXmlParser().encodeResourceToString(documentReference)); + LoggingHelper.logDebugResource("Read DocumentReference", documentReference); Binary binary = readBinary(documentReference, task.getId()); - logger.debug("Read Binary: {}", FhirContext.forR4().newXmlParser().encodeResourceToString(binary)); + LoggingHelper.logDebugBinary("Read Binary", binary); execution.setVariable(BPMN_EXECUTION_VARIABLE_PROJECT_IDENTIFIER, projectIdentifier); execution.setVariable(BPMN_EXECUTION_VARIABLE_COORDINATING_SITE_IDENTIFIER, coordinatingSiteIdentifier); @@ -79,19 +82,20 @@ protected void doExecute(DelegateExecution execution) private String getProjectIdentifier(Task task) { - List identifiers = getTaskHelper() - .getInputParameterReferenceValues(task, CODESYSTEM_MII_DATA_TRANSFER, - CODESYSTEM_MII_DATA_TRANSFER_VALUE_PROJECT_IDENTIFIER) - .filter(Reference::hasIdentifier) - .filter(i -> NAMINGSYSTEM_MII_PROJECT_IDENTIFIER.equals(i.getIdentifier().getSystem())) - .map(i -> i.getIdentifier().getValue()).collect(toList()); + List identifiers = task.getInput().stream() + .filter(i -> i.getType().getCoding().stream() + .anyMatch(c -> CODESYSTEM_MII_DATA_TRANSFER.equals(c.getSystem()) + && CODESYSTEM_MII_DATA_TRANSFER_VALUE_PROJECT_IDENTIFIER.equals(c.getCode()))) + .filter(i -> i.getValue() instanceof Identifier).map(i -> (Identifier) i.getValue()) + .filter(i -> NAMINGSYSTEM_MII_PROJECT_IDENTIFIER.equals(i.getSystem())).map(Identifier::getValue) + .collect(toList()); if (identifiers.size() < 1) throw new IllegalArgumentException("No project identifier present in task with id='" + task.getId() + "'"); if (identifiers.size() > 1) - logger.warn("Found > 1 project identifiers ({}) in task with id='{}', using only the first", - identifiers.size(), task.getId()); + logger.warn("Found {} project identifiers in task with id='{}', using only the first", identifiers.size(), + task.getId()); return identifiers.get(0); } @@ -119,28 +123,54 @@ private DocumentReference readDocumentReference(String projectIdentifier, String if (documentReferences.size() > 1) logger.warn( - "Found > 1 DocumentReferences ({}) for project-identifier='{}' referenced in task with id='{}', using only the first", - documentReferences.size(), projectIdentifier, taskId); + "Found {} DocumentReferences for project-identifier='{}' referenced in task with id='{}', using first ({})", + documentReferences.size(), projectIdentifier, taskId, + documentReferences.get(0).getIdElement().getValue()); return documentReferences.get(0); } private Binary readBinary(DocumentReference documentReference, String taskId) { - List binaries = Stream.of(documentReference).filter(DocumentReference::hasContent) - .flatMap(dr -> dr.getContent().stream()).map(c -> c.getAttachment().getUrl()).map(this::readBinary) - .collect(toList()); - - if (binaries.size() < 1) - throw new IllegalArgumentException("Could not find any Binary from DocumentReference with id='" + List urls = Stream.of(documentReference).filter(DocumentReference::hasContent) + .flatMap(dr -> dr.getContent().stream()) + .filter(DocumentReference.DocumentReferenceContentComponent::hasAttachment) + .map(DocumentReference.DocumentReferenceContentComponent::getAttachment).filter(Attachment::hasUrl) + .map(Attachment::getUrl).collect(toList()); + + if (urls.size() < 1) + throw new IllegalArgumentException("Could not find any attachment URLs in DocumentReference with id='" + documentReference.getId() + "' belonging to task with id='" + taskId + "'"); - if (binaries.size() > 1) + if (urls.size() > 1) + logger.warn( + "Found {} attachment URLs in DocumentReference with id='{}' belonging to task with id='{}', using first ({})", + urls.size(), documentReference.getId(), taskId, urls.get(0)); + + if (!validBinaryUrl(urls.get(0))) + { logger.warn( - "Found > 1 Binaries ({}) from DocumentReference with id='{}' belonging to task with id='{}', using only the first", - binaries.size(), documentReference.getId(), taskId); + "Attachment URL {} in DocumentReference with id='{}' belonging to task with id='{}', not a valid Binary reference," + + " should be a relative Binary reference or an absolute Binary reference to KDS FHIR server at {}", + urls.get(0), documentReference.getId(), taskId, kdsClientFactory.getKdsClient().getFhirBaseUrl()); + throw new IllegalArgumentException( + "Attachment URL " + urls.get(0) + " in DocumentReference with id='" + documentReference.getId() + + "' belonging to task with id='" + taskId + "' not a valid Binary reference"); + } + + return readBinary(urls.get(0)); + } + + private boolean validBinaryUrl(String url) + { + IdType idType = new IdType(url); + String fhirBaseUrl = kdsClientFactory.getKdsClient().getFhirBaseUrl(); + + // expecting no Base URL or, Base URL equal to KDS client Base URL + boolean hasValidBaseUrl = !idType.hasBaseUrl() || fhirBaseUrl.equals(idType.getBaseUrl()); + boolean isBinaryReference = ResourceType.Binary.name().equals(idType.getResourceType()); - return binaries.get(0); + return hasValidBaseUrl && isBinaryReference; } private Binary readBinary(String url) diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/StoreData.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/StoreData.java index 4e63926..99624ac 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/StoreData.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/StoreData.java @@ -29,7 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ca.uhn.fhir.context.FhirContext; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.LoggingHelper; public class StoreData extends AbstractServiceDelegate { @@ -81,8 +81,7 @@ private Binary createBinary(byte[] content, String coordinatingSiteIdentifier) Binary binary = new Binary().setContentType(MediaType.APPLICATION_OCTET_STREAM) .setSecurityContext(securityContext).setData(content); - logger.debug("Created Binary: {}", - FhirContext.forR4().newXmlParser().setPrettyPrint(true).encodeResourceToString(binary)); + LoggingHelper.logDebugBinary("Created Binary", binary); return binary; } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataCos.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataCos.java index 1f3309a..91edf38 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataCos.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataCos.java @@ -20,6 +20,8 @@ import org.hl7.fhir.r4.model.Reference; import org.springframework.beans.factory.InitializingBean; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.MimeTypeHelper; + public class ValidateDataCos extends AbstractServiceDelegate implements InitializingBean { public ValidateDataCos(FhirWebserviceClientProvider clientProvider, TaskHelper taskHelper, @@ -74,10 +76,17 @@ protected void doExecute(DelegateExecution execution) throw new RuntimeException("DocumentReference contains < 1 or > 1 of projectIdentifiers (" + countMi + ")"); } - long countB = entries.stream().filter(e -> e.getResource() instanceof Binary).count(); + List binaries = entries.stream().map(Bundle.BundleEntryComponent::getResource) + .filter(r -> r instanceof Binary).map(r -> (Binary) r).collect(toList()); + + long countB = binaries.size(); if (countB != 1) { throw new RuntimeException("Bundle contains < 1 or > 1 of Binaries (" + countB + ")"); } + + byte[] dataB = binaries.get(0).getData(); + String mimeTypeB = binaries.get(0).getContentType(); + MimeTypeHelper.validate(dataB, mimeTypeB); } } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataDic.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataDic.java index 9cb0e3b..eab4184 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataDic.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/service/ValidateDataDic.java @@ -1,17 +1,8 @@ package de.medizininformatik_initiative.processes.projectathon.data_transfer.service; import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_BINARY; -import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_DATA_SET; -import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_DOCUMENT_REFERENCE; -import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_PROJECT_IDENTIFIER; -import static de.medizininformatik_initiative.processes.projectathon.data_transfer.ConstantsDataTransfer.NAMINGSYSTEM_MII_PROJECT_IDENTIFIER; -import static org.highmed.dsf.bpe.ConstantsBase.NAMINGSYSTEM_HIGHMED_ORGANIZATION_IDENTIFIER; -import static org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION; -import static org.hl7.fhir.r4.model.DocumentReference.ReferredDocumentStatus.FINAL; -import static org.hl7.fhir.r4.model.Enumerations.DocumentReferenceStatus.CURRENT; import java.util.Objects; -import java.util.UUID; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.highmed.dsf.bpe.delegate.AbstractServiceDelegate; @@ -19,21 +10,13 @@ import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider; import org.highmed.dsf.fhir.organization.OrganizationProvider; import org.highmed.dsf.fhir.task.TaskHelper; -import org.highmed.dsf.fhir.variables.FhirResourceValues; import org.hl7.fhir.r4.model.Binary; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.DocumentReference; -import org.hl7.fhir.r4.model.ResourceType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import ca.uhn.fhir.context.FhirContext; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.util.MimeTypeHelper; public class ValidateDataDic extends AbstractServiceDelegate implements InitializingBean { - private static final Logger logger = LoggerFactory.getLogger(ValidateDataDic.class); - private final OrganizationProvider organizationProvider; public ValidateDataDic(FhirWebserviceClientProvider clientProvider, TaskHelper taskHelper, @@ -55,40 +38,11 @@ public void afterPropertiesSet() throws Exception @Override protected void doExecute(DelegateExecution execution) { - String projectIdentifier = (String) execution.getVariable(BPMN_EXECUTION_VARIABLE_PROJECT_IDENTIFIER); - DocumentReference documentReference = (DocumentReference) execution - .getVariable(BPMN_EXECUTION_VARIABLE_DOCUMENT_REFERENCE); Binary binary = (Binary) execution.getVariable(BPMN_EXECUTION_VARIABLE_BINARY); - Bundle bundle = createTransactionBundle(projectIdentifier, documentReference, binary); - logger.debug("Created Bundle: {}", FhirContext.forR4().newXmlParser().encodeResourceToString(bundle)); - - execution.setVariable(BPMN_EXECUTION_VARIABLE_DATA_SET, FhirResourceValues.create(bundle)); - } - - private Bundle createTransactionBundle(String projectIdentifier, DocumentReference documentReference, Binary binary) - { - Binary binaryToTransmit = new Binary().setContentType(binary.getContentType()); - binaryToTransmit.setContent(binary.getContent()); - binaryToTransmit.setId(UUID.randomUUID().toString()); - - DocumentReference documentReferenceToTransmit = new DocumentReference().setStatus(CURRENT).setDocStatus(FINAL); - documentReferenceToTransmit.getMasterIdentifier().setSystem(NAMINGSYSTEM_MII_PROJECT_IDENTIFIER) - .setValue(projectIdentifier); - documentReferenceToTransmit.addAuthor().setType(ResourceType.Organization.name()).getIdentifier() - .setSystem(NAMINGSYSTEM_HIGHMED_ORGANIZATION_IDENTIFIER) - .setValue(organizationProvider.getLocalIdentifierValue()); - documentReferenceToTransmit.setDate(documentReference.getDate()); - documentReferenceToTransmit.addContent().getAttachment().setContentType(binary.getContentType()) - .setUrl("urn:uuid:" + binaryToTransmit.getId()); - - Bundle bundle = new Bundle().setType(TRANSACTION); - bundle.addEntry().setResource(documentReferenceToTransmit) - .setFullUrl("urn:uuid:" + documentReferenceToTransmit.getId()).getRequest() - .setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.DocumentReference.name()); - bundle.addEntry().setResource(binaryToTransmit).setFullUrl("urn:uuid:" + binaryToTransmit.getId()).getRequest() - .setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.Binary.name()); + String mimeTypeBinary = binary.getContentType(); + byte[] dataBinary = binary.getData(); - return bundle; + MimeTypeHelper.validate(dataBinary, mimeTypeBinary); } } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/spring/config/TransferDataConfig.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/spring/config/TransferDataConfig.java index 6eb62bf..b9cf338 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/spring/config/TransferDataConfig.java +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/spring/config/TransferDataConfig.java @@ -21,6 +21,7 @@ import de.medizininformatik_initiative.processes.projectathon.data_transfer.crypto.KeyProvider; import de.medizininformatik_initiative.processes.projectathon.data_transfer.crypto.KeyProviderImpl; import de.medizininformatik_initiative.processes.projectathon.data_transfer.message.StartReceiveProcess; +import de.medizininformatik_initiative.processes.projectathon.data_transfer.service.CreateBundle; import de.medizininformatik_initiative.processes.projectathon.data_transfer.service.DecryptData; import de.medizininformatik_initiative.processes.projectathon.data_transfer.service.DeleteData; import de.medizininformatik_initiative.processes.projectathon.data_transfer.service.DownloadData; @@ -52,75 +53,77 @@ public class TransferDataConfig @Autowired private FhirContext fhirContext; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BASE_URL", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = true, description = "The base address of the KDS FHIR server to read/store FHIR resources", recommendation = "None", example = "http://foo.bar/fhir") + @Documentation(required = true, description = "The base address of the KDS FHIR server to read/store FHIR resources", recommendation = "None", example = "http://foo.bar/fhir") @Value("${de.medizininformatik.initiative.kds.fhir.server.base.url:#{null}}") private String fhirStoreBaseUrl; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_CLIENT", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Client implementation used to connect to the KDS FHIR server in order to read/store FHIR resources", recommendation = "Use default value", example = "None") + @Documentation(description = "Client implementation used to connect to the KDS FHIR server in order to read/store FHIR resources", recommendation = "Use default value", example = "None") @Value("${de.medizininformatik.initiative.kds.fhir.server.client:de.medizininformatik_initiative.processes.projectathon.data_transfer.client.fhir.KdsFhirClientImpl}") private String fhirStoreClientClass; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_TRUST_CERTIFICATES", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "PEM encoded file with one or more trusted root certificate to validate the KDS FHIR server certificate when connecting via https", recommendation = "Use docker secret file to configure", example = "/run/secrets/hospital_ca.pem") + @Documentation(description = "PEM encoded file with one or more trusted root certificate to validate the KDS FHIR server certificate when connecting via https", recommendation = "Use docker secret file to configure", example = "/run/secrets/hospital_ca.pem") @Value("${de.medizininformatik.initiative.kds.fhir.server.trust.certificates:#{null}}") private String fhirStoreTrustStore; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_CERTIFICATE", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "PEM encoded file with client-certificate, if KDS FHIR server requires mutual TLS authentication", recommendation = "Use docker secret file to configure", example = "/run/secrets/kds_server_client_certificate.pem") + @Documentation(description = "PEM encoded file with client-certificate, if KDS FHIR server requires mutual TLS authentication", recommendation = "Use docker secret file to configure", example = "/run/secrets/kds_server_client_certificate.pem") @Value("${de.medizininformatik.initiative.kds.fhir.server.certificate:#{null}}") private String fhirStoreCertificate; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Private key corresponding to the KDS FHIR server client-certificate as PEM encoded file. Use *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD* or *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD_FILE* if private key is encrypted", recommendation = "Use docker secret file to configure", example = "/run/secrets/kds_server_private_key.pem") + @Documentation(description = "Private key corresponding to the KDS FHIR server client-certificate as PEM encoded file. Use *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD* or *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD_FILE* if private key is encrypted", recommendation = "Use docker secret file to configure", example = "/run/secrets/kds_server_private_key.pem") @Value("${de.medizininformatik.initiative.kds.fhir.server.private.key:#{null}}") private String fhirStorePrivateKey; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD or DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD_FILE", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Password to decrypt the KDS FHIR server client-certificate encrypted private key", recommendation = "Use docker secret file to configure by using *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD_FILE*. **Caution!** Editors like nano will add a `LF` (hex `0A`) character at the end of the last line. Make sure that the password file does not end with the `LF` character. For example by starting nano with `nano -L file.password`. If you want to check that the file does not end with an `LF` (hex `0A`) character, use `xxd file.password` to look at a hexdump.", example = "/run/secrets/kds_server_private_key.pem.password") + @Documentation(filePropertySupported = true, description = "Password to decrypt the KDS FHIR server client-certificate encrypted private key", recommendation = "Use docker secret file to configure by using *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PRIVATE_KEY_PASSWORD_FILE*. **Caution!** Editors like nano will add a `LF` (hex `0A`) character at the end of the last line. Make sure that the password file does not end with the `LF` character. For example by starting nano with `nano -L file.password`. If you want to check that the file does not end with an `LF` (hex `0A`) character, use `xxd file.password` to look at a hexdump.", example = "/run/secrets/kds_server_private_key.pem.password") @Value("${de.medizininformatik.initiative.kds.fhir.server.private.key.password:#{null}}") private char[] fhirStorePrivateKeyPassword; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BASICAUTH_USERNAME", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Basic authentication username, set if the server containing the FHIR KDS data requests authentication using basic auth", recommendation = "None", example = "None") + @Documentation(description = "Basic authentication username, set if the server containing the FHIR KDS data requests authentication using basic auth", recommendation = "None", example = "None") @Value("${de.medizininformatik.initiative.kds.fhir.server.basicauth.username:#{null}}") private String fhirStoreUsername; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BASICAUTH_PASSWORD or DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BASICAUTH_PASSWORD_FILE", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Basic authentication password, set if the server containing the FHIR KDS data requests authentication using basic auth", recommendation = "Use docker secret file to configure by using *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BASICAUTH_PASSWORD_FILE*. **Caution!** Editors like nano will add a `LF` (hex `0A`) character at the end of the last line. Make sure that the password file does not end with the `LF` character. For example by starting nano with `nano -L file.password`. If you want to check that the file does not end with an `LF` (hex `0A`) character, use `xxd file.password` to look at a hexdump.", example = "/run/secrets/kds_server_basicauth.password") + @Documentation(filePropertySupported = true, description = "Basic authentication password, set if the server containing the FHIR KDS data requests authentication using basic auth", recommendation = "Use docker secret file to configure by using *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BASICAUTH_PASSWORD_FILE*. **Caution!** Editors like nano will add a `LF` (hex `0A`) character at the end of the last line. Make sure that the password file does not end with the `LF` character. For example by starting nano with `nano -L file.password`. If you want to check that the file does not end with an `LF` (hex `0A`) character, use `xxd file.password` to look at a hexdump.", example = "/run/secrets/kds_server_basicauth.password") @Value("${de.medizininformatik.initiative.kds.fhir.server.basicauth.password:#{null}}") private String fhirStorePassword; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_BEARER_TOKEN", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Bearer token for authentication, set if the server containing the FHIR KDS data requests authentication using a bearer token, cannot be set using docker secrets", recommendation = "None", example = "None") + @Documentation(description = "Bearer token for authentication, set if the server containing the FHIR KDS data requests authentication using a bearer token, cannot be set using docker secrets", recommendation = "None", example = "None") @Value("${de.medizininformatik.initiative.kds.fhir.server.bearer.token:#{null}}") private String fhirStoreBearerToken; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_TIMEOUT_CONNECT", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "The timeout in milliseconds until a connection is established between the KDS client and the KDS FHIR server", recommendation = "Change default value only if timeout exceptions occur", example = "See default value") + @Documentation(description = "The timeout in milliseconds until a connection is established between the KDS client and the KDS FHIR server", recommendation = "Change default value only if timeout exceptions occur", example = "See default value") @Value("${de.medizininformatik.initiative.kds.fhir.server.timeout.connect:10000}") private int fhirStoreConnectTimeout; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_TIMEOUT_CONNECTION_REQUEST", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "The timeout in milliseconds used when requesting a connection from the connection manager between the KDS client and the KDS FHIR server", recommendation = "Change default value only if timeout exceptions occur", example = "See default value") + @Documentation(description = "The timeout in milliseconds used when requesting a connection from the connection manager between the KDS client and the KDS FHIR server", recommendation = "Change default value only if timeout exceptions occur", example = "See default value") @Value("${de.medizininformatik.initiative.kds.fhir.server.timeout.connection.request:10000}") private int fhirStoreConnectionRequestTimeout; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_TIMEOUT_SOCKET", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Maximum period of inactivity in milliseconds between two consecutive data packets of the KDS client and the KDS FHIR server", recommendation = "Change default value only if timeout exceptions occur", example = "See default value") + @Documentation(description = "Maximum period of inactivity in milliseconds between two consecutive data packets of the KDS client and the KDS FHIR server", recommendation = "Change default value only if timeout exceptions occur", example = "See default value") @Value("${de.medizininformatik.initiative.kds.fhir.server.timeout.socket:10000}") private int fhirStoreSocketTimeout; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_CLIENT_VERBOSE", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "The KDS client will log additional debug output", recommendation = "Change default value only if exceptions occur", example = "See default value") + @Documentation(description = "The KDS client will log additional debug output", recommendation = "Change default value only if exceptions occur", example = "See default value") @Value("${de.medizininformatik.initiative.kds.fhir.server.client.verbose:false}") private boolean fhirStoreHapiClientVerbose; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PROXY_URL", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Proxy location, set if the server containing the FHIR KDS data can only be reached through a proxy", recommendation = "None", example = "http://proxy.foo:8080") + @Documentation(description = "Proxy location, set if the server containing the FHIR KDS data can only be reached through a proxy", recommendation = "None", example = "http://proxy.foo:8080") @Value("${de.medizininformatik.initiative.kds.fhir.server.proxy.url:#{null}}") private String fhirStoreProxyUrl; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PROXY_USERNAME", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Proxy username, set if the server containing the FHIR KDS data can only be reached through a proxy which requests authentication", recommendation = "None", example = "None") + @Documentation(description = "Proxy username, set if the server containing the FHIR KDS data can only be reached through a proxy which requests authentication", recommendation = "None", example = "None") @Value("${de.medizininformatik.initiative.kds.fhir.server.proxy.username:#{null}}") private String fhirStoreProxyUsername; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PROXY_PASSWORD or DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PROXY_PASSWORD_FILE", processNames = "medizininformatik-initiativede_dataSend/, medizininformatik-initiativede_dataReceive/", required = false, description = "Proxy password, set if the server containing the FHIR KDS data can only be reached through a proxy which requests authentication", recommendation = "Use docker secret file to configure by using *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PROXY_PASSWORD_FILE*. **Caution!** Editors like nano will add a `LF` (hex `0A`) character at the end of the last line. Make sure that the password file does not end with the `LF` character. For example by starting nano with `nano -L file.password`. If you want to check that the file does not end with an `LF` (hex `0A`) character, use `xxd file.password` to look at a hexdump.", example = "None") + @Documentation(filePropertySupported = true, description = "Proxy password, set if the server containing the FHIR KDS data can only be reached through a proxy which requests authentication", recommendation = "Use docker secret file to configure by using *DE_MEDIZININFORMATIK_INITIATIVE_KDS_FHIR_SERVER_PROXY_PASSWORD_FILE*. **Caution!** Editors like nano will add a `LF` (hex `0A`) character at the end of the last line. Make sure that the password file does not end with the `LF` character. For example by starting nano with `nano -L file.password`. If you want to check that the file does not end with an `LF` (hex `0A`) character, use `xxd file.password` to look at a hexdump.", example = "None") @Value("${de.medizininformatik.initiative.kds.fhir.server.proxy.password:#{null}}") private String fhirStoreProxyPassword; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_COS_PRIVATE_KEY", processNames = "medizininformatik-initiativede_dataReceive/", required = true, description = "Location of the COS private-key as 4096 Bit RSA PEM encoded, not encrypted file", recommendation = "Use docker secret file to configure", example = "/run/secrets/cos_private_key.pem") + @Documentation(required = true, processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Location of the COS private-key as 4096 Bit RSA PEM encoded, not encrypted file", recommendation = "Use docker secret file to configure", example = "/run/secrets/cos_private_key.pem") @Value("${de.medizininformatik.initiative.cos.private.key:#{null}}") private String cosPrivateKeyFile; - @Documentation(environmentVariables = "DE_MEDIZININFORMATIK_INITIATIVE_COS_PUBLIC_KEY", processNames = "medizininformatik-initiativede_dataReceive/", required = true, description = "Location of the COS public-key as 4096 Bit RSA PEM encoded file", recommendation = "Use docker secret file to configure", example = "/run/secrets/cos_public_key.pem") + @Documentation(required = true, processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Location of the COS public-key as 4096 Bit RSA PEM encoded file", recommendation = "Use docker secret file to configure", example = "/run/secrets/cos_public_key.pem") @Value("${de.medizininformatik.initiative.cos.public.key:#{null}}") private String cosPublicKeyFile; @@ -129,7 +132,7 @@ public class TransferDataConfig @Bean @SuppressWarnings("unchecked") - public KdsClientFactory projectathonClientFactory() + public KdsClientFactory kdsClientFactory() { Path trustStorePath = checkExists(fhirStoreTrustStore); Path certificatePath = checkExists(fhirStoreCertificate); @@ -164,12 +167,12 @@ private Path checkExists(String file) } } - // miiProjectathonDataSend + // projectathonDataSend @Bean public ReadData readData() { - return new ReadData(fhirClientProvider, taskHelper, readAccessHelper, fhirContext, projectathonClientFactory()); + return new ReadData(fhirClientProvider, taskHelper, readAccessHelper, fhirContext, kdsClientFactory()); } @Bean @@ -178,10 +181,17 @@ public ValidateDataDic validateDataDic() return new ValidateDataDic(fhirClientProvider, taskHelper, readAccessHelper, organizationProvider); } + @Bean + public CreateBundle createBundle() + { + return new CreateBundle(fhirClientProvider, taskHelper, readAccessHelper, organizationProvider); + } + @Bean public EncryptData encryptData() { - return new EncryptData(fhirClientProvider, taskHelper, readAccessHelper, endpointProvider); + return new EncryptData(fhirClientProvider, taskHelper, readAccessHelper, organizationProvider, + endpointProvider); } @Bean @@ -203,7 +213,7 @@ public DeleteData deleteData() return new DeleteData(fhirClientProvider, taskHelper, readAccessHelper); } - // miiProjectathonDataReceive + // projectathonDataReceive @Bean public DownloadData downloadData() @@ -221,7 +231,7 @@ public KeyProvider keyProvider() @Bean public DecryptData decryptData() { - return new DecryptData(fhirClientProvider, taskHelper, readAccessHelper, keyProvider()); + return new DecryptData(fhirClientProvider, taskHelper, readAccessHelper, organizationProvider, keyProvider()); } @Bean @@ -233,7 +243,6 @@ public ValidateDataCos validateDataCos() @Bean public InsertData insertData() { - return new InsertData(fhirClientProvider, taskHelper, readAccessHelper, fhirContext, - projectathonClientFactory()); + return new InsertData(fhirClientProvider, taskHelper, readAccessHelper, fhirContext, kdsClientFactory()); } } diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/util/LoggingHelper.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/util/LoggingHelper.java new file mode 100644 index 0000000..4bcca76 --- /dev/null +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/util/LoggingHelper.java @@ -0,0 +1,50 @@ +package de.medizininformatik_initiative.processes.projectathon.data_transfer.util; + +import java.nio.charset.StandardCharsets; + +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.uhn.fhir.context.FhirContext; + +public class LoggingHelper +{ + private static final Logger logger = LoggerFactory.getLogger(LoggingHelper.class); + + public static void logDebugBundle(String message, Bundle bundle) + { + Bundle copy = bundle.copy(); + + // replace actual content with content size in bytes to not leak sensitive information + copy.getEntry().stream().filter(Bundle.BundleEntryComponent::hasResource) + .map(Bundle.BundleEntryComponent::getResource).filter(r -> r instanceof Binary).map(r -> (Binary) r) + .forEach(b -> b.setContent(createReplacementContent(b))); + + logDebugResource(message, copy); + } + + public static void logDebugBinary(String message, Binary binary) + { + Binary copy = binary.copy(); + + // replace actual content with content size in bytes to not leak sensitive information + copy.setContent(createReplacementContent(copy)); + + logDebugResource(message, copy); + } + + public static void logDebugResource(String message, Resource resource) + { + logger.debug(message + ": {}", + FhirContext.forR4().newXmlParser().setPrettyPrint(true).encodeResourceToString(resource)); + } + + private static byte[] createReplacementContent(Binary binary) + { + return ("original content (" + binary.getContent().length + " bytes) replaced") + .getBytes(StandardCharsets.UTF_8); + } +} diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/util/MimeTypeHelper.java b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/util/MimeTypeHelper.java new file mode 100644 index 0000000..432568f --- /dev/null +++ b/mii-dsf-process-projectathon-data-transfer/src/main/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/util/MimeTypeHelper.java @@ -0,0 +1,58 @@ +package de.medizininformatik_initiative.processes.projectathon.data_transfer.util; + +import org.apache.tika.config.TikaConfig; +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.mime.MediaType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MimeTypeHelper +{ + private static final Logger logger = LoggerFactory.getLogger(MimeTypeHelper.class); + + /** + * Detects the mime-type of the provided data and validates if the detected mime-type equals the declared mime-type. + * Logs a warning if the full mime-types do not match, throws a {@link RuntimeException} if the base mime-types do + * not match. + * + * @param data + * of which the mime-type should be detected + * @param declared + * the declared mime-type of the data + * @throws RuntimeException + * if the detected and the declared base mime-type do not match + */ + public static void validate(byte[] data, String declared) + { + MediaType declaredMimeType = MediaType.parse(declared); + MediaType detectedMimeType = MediaType.EMPTY; + + try + { + TikaConfig tika = new TikaConfig(); + TikaInputStream input = TikaInputStream.get(data); + Metadata metadata = new Metadata(); + + // gives only a hint to the possible mime-type + // this is needed because text/csv cannot be detected without any hint and would resolve to text/plain + metadata.add(Metadata.CONTENT_TYPE, declaredMimeType.toString()); + + detectedMimeType = tika.getDetector().detect(input, metadata); + } + catch (Exception exception) + { + throw new RuntimeException("Could not detect mime-type of binary", exception); + } + + if (!declaredMimeType.equals(detectedMimeType)) + logger.warn("Declared mime-type {} does not match detected mime-type {}", declaredMimeType.toString(), + detectedMimeType.toString()); + + if (!declaredMimeType.getType().equals(detectedMimeType.getType())) + { + throw new RuntimeException("Declared base mime-type of '" + declaredMimeType.toString() + + "' does not match detected base mime-type of '" + detectedMimeType.toString() + "'"); + } + } +} diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/receive.bpmn b/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/receive.bpmn index 9d6edbd..a89793b 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/receive.bpmn +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/receive.bpmn @@ -18,8 +18,8 @@ Flow_1w6vljw - - + + Flow_0j6v09z Flow_1w6vljw @@ -28,7 +28,7 @@ Flow_1c3t0x1 Flow_0j6v09z - + @@ -71,7 +71,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/send.bpmn b/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/send.bpmn index 7e0618c..7484c77 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/send.bpmn +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/bpe/send.bpmn @@ -1,5 +1,5 @@ - + Flow_0kkjyst @@ -10,14 +10,14 @@ Flow_0yamo5r - + Flow_0yamo5r Flow_0zrvqk8 - Flow_0zrvqk8 + Flow_05qlnk4 Flow_15vmy2h @@ -62,34 +62,39 @@ Flow_1pzxejf Flow_0phc02z + + + Flow_0zrvqk8 + Flow_05qlnk4 + - - + + - - + + - - + + - - + + - - + + @@ -99,6 +104,10 @@ + + + + @@ -112,31 +121,34 @@ - + - + - + - + - + - + - + - + - + + + + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-receive.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-receive.xml index 55d01a5..fe89b80 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-receive.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-receive.xml @@ -85,7 +85,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-send.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-send.xml index 72956e5..46f707b 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-send.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ActivityDefinition/mii-projectathon-data-send.xml @@ -65,7 +65,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-cryptography.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-cryptography.xml index 913c2a2..088591d 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-cryptography.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-cryptography.xml @@ -13,7 +13,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-data-transfer.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-data-transfer.xml index cfc0686..fb228d5 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-data-transfer.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/CodeSystem/mii-data-transfer.xml @@ -13,7 +13,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/NamingSystem/mii-project-identifier.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/NamingSystem/mii-project-identifier.xml index 12f494d..91af56f 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/NamingSystem/mii-project-identifier.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/NamingSystem/mii-project-identifier.xml @@ -6,9 +6,10 @@ - + + - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-receive.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-receive.xml index 9b48772..bc58dee 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-receive.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-receive.xml @@ -6,11 +6,13 @@ + + - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-send.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-send.xml index b995cb1..00d10c5 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-send.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/StructureDefinition/mii-projectathon-task-start-data-send.xml @@ -6,11 +6,13 @@ + + - + @@ -120,17 +122,17 @@ - - + - - - + + + + - - - + + + \ No newline at end of file diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-cryptography.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-cryptography.xml index 4d38819..e71b1ab 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-cryptography.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-cryptography.xml @@ -13,7 +13,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-data-transfer.xml b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-data-transfer.xml index f4d2952..221a68f 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-data-transfer.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/main/resources/fhir/ValueSet/mii-data-transfer.xml @@ -13,7 +13,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/bpe/start/DataSendExampleStarter.java b/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/bpe/start/DataSendExampleStarter.java index 5b0ed75..3b4e7f8 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/bpe/start/DataSendExampleStarter.java +++ b/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/bpe/start/DataSendExampleStarter.java @@ -24,8 +24,6 @@ import org.hl7.fhir.r4.model.Task.TaskIntent; import org.hl7.fhir.r4.model.Task.TaskStatus; -import ca.uhn.fhir.context.FhirContext; - public class DataSendExampleStarter { public static void main(String[] args) throws Exception @@ -60,9 +58,7 @@ private static Task createTask() .setCode(CODESYSTEM_MII_DATA_TRANSFER_VALUE_COORDINATING_SITE_IDENTIFIER); task.addInput() - .setValue(new Reference().setIdentifier( - new Identifier().setSystem(NAMINGSYSTEM_MII_PROJECT_IDENTIFIER).setValue("Test_PROJECT")) - .setType(ResourceType.DocumentReference.name())) + .setValue(new Identifier().setSystem(NAMINGSYSTEM_MII_PROJECT_IDENTIFIER).setValue("Test_PROJECT")) .getType().addCoding().setSystem(CODESYSTEM_MII_DATA_TRANSFER) .setCode(CODESYSTEM_MII_DATA_TRANSFER_VALUE_PROJECT_IDENTIFIER); diff --git a/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/fhir/profile/TaskProfileTest.java b/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/fhir/profile/TaskProfileTest.java index 595a631..5f9f263 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/fhir/profile/TaskProfileTest.java +++ b/mii-dsf-process-projectathon-data-transfer/src/test/java/de/medizininformatik_initiative/processes/projectathon/data_transfer/fhir/profile/TaskProfileTest.java @@ -113,9 +113,7 @@ private Task createValidTaskStartDataSend() .setCode(CODESYSTEM_MII_DATA_TRANSFER_VALUE_COORDINATING_SITE_IDENTIFIER); task.addInput() - .setValue(new Reference().setIdentifier( - new Identifier().setSystem(NAMINGSYSTEM_MII_PROJECT_IDENTIFIER).setValue("Test_PROJECT")) - .setType(ResourceType.DocumentReference.name())) + .setValue(new Identifier().setSystem(NAMINGSYSTEM_MII_PROJECT_IDENTIFIER).setValue("Test_PROJECT")) .getType().addCoding().setSystem(CODESYSTEM_MII_DATA_TRANSFER) .setCode(CODESYSTEM_MII_DATA_TRANSFER_VALUE_PROJECT_IDENTIFIER); diff --git a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Bundle/DicFhirStore_Demo.xml b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Bundle/DicFhirStore_Demo.xml index 542296d..b744708 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Bundle/DicFhirStore_Demo.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Bundle/DicFhirStore_Demo.xml @@ -17,7 +17,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_Demo.xml b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_Demo.xml index a5c2365..d85d569 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_Demo.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_Demo.xml @@ -5,7 +5,7 @@ - + @@ -53,12 +53,9 @@ - - - + - - + \ No newline at end of file diff --git a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_NT-proBNP.xml b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_NT-proBNP.xml index 7c489c2..3c1d4ee 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_NT-proBNP.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_NT-proBNP.xml @@ -53,12 +53,9 @@ - - - + - - + \ No newline at end of file diff --git a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_WE-STORM.xml b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_WE-STORM.xml index 4b633cd..6b86da1 100644 --- a/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_WE-STORM.xml +++ b/mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_WE-STORM.xml @@ -53,12 +53,9 @@ - - - + - - + \ No newline at end of file diff --git a/mii-dsf-processes-docker-test-setup/README.md b/mii-dsf-processes-docker-test-setup/README.md index c487e6e..0359884 100644 --- a/mii-dsf-processes-docker-test-setup/README.md +++ b/mii-dsf-processes-docker-test-setup/README.md @@ -7,41 +7,51 @@ mvn clean package ``` Add entries to your hosts file + ``` 127.0.0.1 dic 127.0.0.1 cos ``` -*A total of four console windows are required. Start docker-compose commands from sub-folder:* `mii-dsf-processes/mii-dsf-processes-docker-test-setup` +*A total of five console windows are required. Start docker-compose commands for consoles 1 to 4 from +sub-folder:* `mii-dsf-processes/mii-dsf-processes-docker-test-setup` + +Console 1: Start DIC HAPI FHIR Server -Console 1: Start DIC HAPI FHIR Server ```sh docker-compose up dic-fhir-store-hapi ``` + Access at http://localhost:8080/fhir/ Console 4: Start COS HAPI FHIR Server + ```sh docker-compose up cos-fhir-store-hapi ``` + Access at http://localhost:8081/fhir/ Console 2: Start DIC DSF FHIR Server and wait till started + ```sh docker-compose up -d dic-fhir && docker-compose logs -f dic-fhir ``` -Console 2: Disconnect from log output (Ctrl-C) if Server started -Console 2: Start DIC DSF BPE Server + +Console 2: Disconnect from log output (Ctrl-C) if Server started Console 2: Start DIC DSF BPE Server + ```sh docker-compose up -d dic-bpe && docker-compose logs -f dic-fhir dic-bpe ```` Console 3: Start COS DSF FHIR Server and wait till started + ```sh docker-compose up -d cos-fhir && docker-compose logs -f cos-fhir ``` -Console 3: Disconnect from log output (Ctrl-C) if Server started -Console 3: Start COS DSF BPE Server + +Console 3: Disconnect from log output (Ctrl-C) if Server started Console 3: Start COS DSF BPE Server + ```sh docker-compose up -d cos-bpe && docker-compose logs -f cos-fhir cos-bpe ```` @@ -51,31 +61,40 @@ Webbrowser at http://localhost:8080/fhir/: Add Demo Data to DIC HAPI FHIR Server [DicFhirStore_Demo.xml](../mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Bundle/DicFhirStore_Demo.xml) --> -*Start curl commands from root-folder:* `mii-dsf-processes` +*Start curl commands in console 5 from root-folder:* `mii-dsf-processes` + +Console 5: Execute Demo Transaction-Bundle for HAPI -Console 6: Execute Demo Transaction-Bundle for HAPI ```sh curl -H "Accept: application/xml+fhir" -H "Content-Type: application/fhir+xml" \ -d @mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Bundle/DicFhirStore_Demo.xml \ http://localhost:8080/fhir ``` -Console 6: Start Data Send Process at DIC using the following command +Console 5: Start Data Send Process at DIC using the following command + +*Unfortunately this command does not work on Windows. An alternative for starting the process is using WSL or the +example starter class with name* `DataSendExampleStarter` *in* +`mii-dsf-process-projectathon-data-transfer/src/test/java/../bpe/start` + ```sh curl -H "Accept: application/xml+fhir" -H "Content-Type: application/fhir+xml" \ -d @mii-dsf-process-projectathon-data-transfer/src/test/resources/fhir/Task/TaskStartDataSend_Demo.xml \ --ssl-no-revoke --cacert mii-dsf-processes-test-data-generator/cert/ca/testca_certificate.pem \ --cert mii-dsf-processes-test-data-generator/cert/Webbrowser_Test_User/Webbrowser_Test_User_certificate.pem \ --key mii-dsf-processes-test-data-generator/cert/Webbrowser_Test_User/Webbrowser_Test_User_private-key.pem \ +--pass password \ https://dic/fhir/Task ``` -Console X: Check data transferred to COS +Console 5: Check data transferred to COS + ```sh curl http://localhost:8081/fhir/DocumentReference ``` -Console X: Stop everything +Console 5: Stop everything + ```sh docker-compose down -v ``` \ No newline at end of file diff --git a/mii-dsf-processes-documentation-generator/pom.xml b/mii-dsf-processes-documentation-generator/pom.xml index b2e68be..564f3af 100644 --- a/mii-dsf-processes-documentation-generator/pom.xml +++ b/mii-dsf-processes-documentation-generator/pom.xml @@ -9,7 +9,7 @@ de.medizininformatik-initiative mii-dsf-processes - 0.1.0-SNAPSHOT + 0.1.0-RC1 diff --git a/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/Documentation.java b/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/Documentation.java index 268f8ad..d4e8b78 100644 --- a/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/Documentation.java +++ b/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/Documentation.java @@ -9,15 +9,37 @@ @Target(ElementType.FIELD) public @interface Documentation { - String environmentVariables(); + /** + * @return true if this property is required for processes that are listed in + * {@link Documentation#processNames} + */ + boolean required() default false; - boolean required(); + /** + * @return true if a docker secret file can be used to configure this property, else + * false, which means that a docker secret file cannot be used to configure this + * property + */ + boolean filePropertySupported() default false; - String processNames(); + /** + * @return an empty array if all processes use this property or an array of length >=1 containing only specific + * processes that use this property, but not all + */ + String[] processNames() default {}; + /** + * @return description helping to configure this property + */ String description(); + /** + * @return example value helping to configure this property + */ String example(); + /** + * @return recommendation helping to configure this property + */ String recommendation(); } diff --git a/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/DocumentationGenerator.java b/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/DocumentationGenerator.java index e231877..06dc7f0 100644 --- a/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/DocumentationGenerator.java +++ b/mii-dsf-processes-documentation-generator/src/main/java/de/medizininformatik_initiative/processes/documentation/generator/DocumentationGenerator.java @@ -1,15 +1,22 @@ package de.medizininformatik_initiative.processes.documentation.generator; +import static java.util.stream.Collectors.toList; + import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; -import java.lang.annotation.Annotation; +import java.io.InputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; +import org.camunda.bpm.model.bpmn.Bpmn; +import org.camunda.bpm.model.bpmn.instance.BaseElement; +import org.camunda.bpm.model.bpmn.instance.Process; +import org.highmed.dsf.bpe.ProcessPluginDefinition; import org.reflections.Reflections; import org.reflections.scanners.Scanners; import org.reflections.util.ClasspathHelper; @@ -32,57 +39,146 @@ public void execute(String[] args) Arrays.asList(args).forEach(this::generateDocumentation); } - private void generateDocumentation(String configDirectory) + private void generateDocumentation(String workingPackage) { - String filename = "target/Documentation_" + configDirectory + ".md"; - logger.info("Generating documentation for directory: {} in file: {}", configDirectory, filename); + String filename = "target/Documentation_" + workingPackage + ".md"; + logger.info("Generating documentation for package {} in file {}", workingPackage, filename); + + Reflections reflections = createReflections(workingPackage); + + List pluginProcessNames = getPluginProcessNames(reflections, workingPackage); + List fields = getFields(reflections); - Reflections reflections = createReflections(configDirectory); - List fields = getFields(reflections, Documentation.class); - writeFields(fields, filename, configDirectory); + writeFields(fields, pluginProcessNames, filename, workingPackage); } - private Reflections createReflections(String configDirectory) + private Reflections createReflections(String workingPackage) { ConfigurationBuilder configurationBuilder = new ConfigurationBuilder() - .setUrls(ClasspathHelper.forPackage(configDirectory)).setScanners(Scanners.FieldsAnnotated); + .setUrls(ClasspathHelper.forPackage(workingPackage)) + .setScanners(Scanners.FieldsAnnotated, Scanners.SubTypes); return new Reflections(configurationBuilder); } - private List getFields(Reflections reflections, Class annotation) + private List getPluginProcessNames(Reflections reflections, String workingPackage) + { + List> pluginDefinitionClasses = new ArrayList<>( + reflections.getSubTypesOf(ProcessPluginDefinition.class)); + + if (pluginDefinitionClasses.size() < 1) + { + logger.warn("No ProcessPluginDefinitions found in package {}", workingPackage); + return Collections.emptyList(); + } + + if (pluginDefinitionClasses.size() > 1) + logger.warn("Found {} ProcessPluginDefinitions ({}) in package {}, using only the first", + pluginDefinitionClasses.size(), pluginDefinitionClasses, workingPackage); + + try + { + ProcessPluginDefinition processPluginDefinition = pluginDefinitionClasses.get(0).getConstructor() + .newInstance(); + + return processPluginDefinition.getBpmnFiles().map(this::getProcessName).filter(Optional::isPresent) + .map(Optional::get).collect(toList()); + } + catch (Exception exception) + { + logger.warn( + "Could not read process names from package {} and ProcessPluginDefinition with name {}, reason is '{}'", + workingPackage, pluginDefinitionClasses.get(0).getName(), exception.getMessage()); + return Collections.emptyList(); + } + } + + private Optional getProcessName(String bpmnFile) + { + try + { + InputStream resource = getClass().getClassLoader().getResource(bpmnFile).openStream(); + return Bpmn.readModelFromStream(resource).getModelElementsByType(Process.class).stream() + .map(BaseElement::getId).findFirst(); + } + catch (Exception exception) + { + logger.warn("Could not read process name from resource file {}, reason is '{}'", bpmnFile, + exception.getMessage()); + return Optional.empty(); + } + } + + private List getFields(Reflections reflections) { - List fields = new ArrayList<>(reflections.getFieldsAnnotatedWith(annotation)); + List fields = new ArrayList<>(reflections.getFieldsAnnotatedWith(Documentation.class)); Collections.reverse(fields); return fields; } - private void writeFields(List fields, String filename, String configDirectory) + private void writeFields(List fields, List pluginProcessNames, String filename, + String workingPackage) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) { - fields.stream().map(this::createDocumentation).forEach(d -> write(writer, filename, d)); + fields.stream().map(field -> createDocumentation(field, pluginProcessNames)) + .forEach(d -> write(writer, filename, d)); } - catch (IOException e) + catch (Exception exception) { - logger.warn("Could not generate documentation for directory: {}, reason: {}", configDirectory, - e.getMessage()); + logger.warn("Could not generate documentation for package {}, reason is '{}'", workingPackage, + exception.getMessage()); } } - private String createDocumentation(Field field) + private String createDocumentation(Field field, List pluginProcessNames) { Documentation documentation = field.getAnnotation(Documentation.class); Value value = field.getAnnotation(Value.class); String[] valueSplit = value.value().replaceAll("\\$", "").replace("#", "").replace("{", "").replace("}", "") .split(":"); + String property = valueSplit[0]; + + String environment = property.replace(".", "_").toUpperCase(); + if (documentation.filePropertySupported()) + environment = String.format("%s or %s_FILE", environment, environment); + + boolean required = documentation.required(); + + String[] documentationProcessNames = documentation.processNames(); + String processesNamesAsString = getProcessNamesAsString(documentationProcessNames, pluginProcessNames); + + String description = documentation.description(); + String example = documentation.example(); + String recommendation = documentation.recommendation(); + + String defaultValue = (valueSplit.length == 2 && !"null".equals(valueSplit[1])) ? valueSplit[1] + : "not set by default"; + + return String.format("### %s\n- **Property:** %s\n- **Required:** %s\n- **Processes:** %s\n" + + "- **Description:** %s\n- **Example:** %s\n- **Recommendation:** %s\n" + "- **Default:** %s\n\n", + environment, property, required, processesNamesAsString, description, example, recommendation, + defaultValue); + } + + private String getProcessNamesAsString(String[] documentationProcessNames, List pluginProcessNames) + { + if (pluginProcessNames.size() == 0) + return "Could not read process names from ProcessPluginDefinition"; + + if (documentationProcessNames.length == 0) + return String.join(", ", pluginProcessNames); + + for (String documentationProcessName : documentationProcessNames) + { + if (!pluginProcessNames.contains(documentationProcessName)) + logger.warn( + "Documentation contains process with name '{}' which" + + " is not part of the processes {} defined in the ProcessPluginDefinition", + documentationProcessName, pluginProcessNames); + } - return String.format( - "### %s\n- **Property:** %s\n- **Required:** %s\n- **Processes:** %s\n- **Description:** %s\n- **Example:** %s\n- **Recommendation:** %s\n- **Default:** %s\n\n", - documentation.environmentVariables(), valueSplit[0], documentation.required(), - documentation.processNames(), documentation.description(), documentation.example(), - documentation.recommendation(), - (valueSplit.length == 2 && !"null".equals(valueSplit[1])) ? valueSplit[1] : "not set by default"); + return String.join(", ", documentationProcessNames); } private void write(BufferedWriter writer, String filename, String string) @@ -93,7 +189,7 @@ private void write(BufferedWriter writer, String filename, String string) } catch (IOException e) { - logger.warn("Writing the following string to file {} failed: \n {} reason: {}", filename, string, + logger.warn("Writing the following string to file {} failed: \n {} reason is '{}'", filename, string, e.getMessage()); } } diff --git a/mii-dsf-processes-documentation-generator/src/main/resources/log4j2.xml b/mii-dsf-processes-documentation-generator/src/main/resources/log4j2.xml index 67a1731..e8e6da7 100644 --- a/mii-dsf-processes-documentation-generator/src/main/resources/log4j2.xml +++ b/mii-dsf-processes-documentation-generator/src/main/resources/log4j2.xml @@ -8,9 +8,9 @@ - - + + diff --git a/mii-dsf-processes-test-data-generator/pom.xml b/mii-dsf-processes-test-data-generator/pom.xml index 9fb926c..3724159 100644 --- a/mii-dsf-processes-test-data-generator/pom.xml +++ b/mii-dsf-processes-test-data-generator/pom.xml @@ -8,7 +8,7 @@ de.medizininformatik-initiative mii-dsf-processes - 0.1.0-SNAPSHOT + 0.1.0-RC1 diff --git a/mii-dsf-processes-test-data-generator/src/main/resources/log4j2.xml b/mii-dsf-processes-test-data-generator/src/main/resources/log4j2.xml index 67a1731..e8e6da7 100644 --- a/mii-dsf-processes-test-data-generator/src/main/resources/log4j2.xml +++ b/mii-dsf-processes-test-data-generator/src/main/resources/log4j2.xml @@ -8,9 +8,9 @@ - - + + diff --git a/pom.xml b/pom.xml index 544a723..fba3abd 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.medizininformatik-initiative mii-dsf-processes - 0.1.0-SNAPSHOT + 0.1.0-RC1 pom @@ -23,6 +23,7 @@ ${project.basedir} 5.1.0 0.5.4 + 2.3.0 mii-dsf-processes @@ -104,12 +105,6 @@ ${hapi.version} - - org.reflections - reflections - 0.10.2 - - de.hs-heilbronn.mi @@ -129,6 +124,17 @@ 1.8.0-beta4 + + + org.apache.tika + tika-core + ${apache.tika.version} + + + org.reflections + reflections + 0.10.2 + com.fasterxml.jackson.core jackson-annotations @@ -185,8 +191,13 @@ - + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + org.apache.maven.plugins maven-dependency-plugin diff --git a/src/assembly/zip.xml b/src/assembly/zip.xml new file mode 100644 index 0000000..d5ad227 --- /dev/null +++ b/src/assembly/zip.xml @@ -0,0 +1,26 @@ + + + zip + + zip + + + + + ${project.build.directory} + + + *.jar + + + + ${project.build.directory}/lib + + + *.jar + + + + \ No newline at end of file