From b30934364ef206e57136e3706ee27ef6d2d2e42e Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 17 Jul 2023 15:28:30 +0200 Subject: [PATCH 01/39] start development cycle 1.1.0 --- CITATION.cff | 2 +- dsf-bpe/dsf-bpe-process-api-v1/pom.xml | 2 +- dsf-bpe/dsf-bpe-server-jetty/pom.xml | 2 +- dsf-bpe/dsf-bpe-server/pom.xml | 2 +- dsf-bpe/pom.xml | 2 +- dsf-common/dsf-common-auth/pom.xml | 2 +- dsf-common/dsf-common-config/pom.xml | 2 +- dsf-common/dsf-common-documentation/pom.xml | 2 +- dsf-common/dsf-common-jetty/pom.xml | 2 +- dsf-common/dsf-common-status/pom.xml | 2 +- dsf-common/pom.xml | 2 +- dsf-fhir/dsf-fhir-auth/pom.xml | 2 +- dsf-fhir/dsf-fhir-rest-adapter/pom.xml | 2 +- dsf-fhir/dsf-fhir-server-jetty/pom.xml | 2 +- dsf-fhir/dsf-fhir-server/pom.xml | 2 +- dsf-fhir/dsf-fhir-validation/pom.xml | 6 +++--- dsf-fhir/dsf-fhir-webservice-client/pom.xml | 2 +- dsf-fhir/dsf-fhir-websocket-client/pom.xml | 2 +- dsf-fhir/pom.xml | 2 +- dsf-tools/dsf-tools-build-info-reader/pom.xml | 2 +- dsf-tools/dsf-tools-bundle-generator/pom.xml | 2 +- dsf-tools/dsf-tools-db-migration/pom.xml | 2 +- dsf-tools/dsf-tools-docker-secrets-reader/pom.xml | 2 +- dsf-tools/dsf-tools-documentation-generator/pom.xml | 2 +- dsf-tools/dsf-tools-proxy-test/pom.xml | 2 +- dsf-tools/dsf-tools-test-data-generator/pom.xml | 2 +- dsf-tools/pom.xml | 2 +- pom.xml | 2 +- 28 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index b7c5306c7..f94d29176 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -24,7 +24,7 @@ preferred-citation: doi: 10.3233/SHTI210060 type: proceedings title: "Data Sharing Framework (DSF)" -version: 1.0.0 +version: 1.1.0 date-released: 2023-06-28 url: https://dsf.dev repository-code: https://github.com/datasharingframework/dsf diff --git a/dsf-bpe/dsf-bpe-process-api-v1/pom.xml b/dsf-bpe/dsf-bpe-process-api-v1/pom.xml index 7ae49a29b..dd7edf878 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1/pom.xml +++ b/dsf-bpe/dsf-bpe-process-api-v1/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-bpe-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-bpe/dsf-bpe-server-jetty/pom.xml b/dsf-bpe/dsf-bpe-server-jetty/pom.xml index 2a0e62931..299d2e7d8 100755 --- a/dsf-bpe/dsf-bpe-server-jetty/pom.xml +++ b/dsf-bpe/dsf-bpe-server-jetty/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-bpe-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-bpe/dsf-bpe-server/pom.xml b/dsf-bpe/dsf-bpe-server/pom.xml index fdc087adc..900c03637 100755 --- a/dsf-bpe/dsf-bpe-server/pom.xml +++ b/dsf-bpe/dsf-bpe-server/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-bpe-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-bpe/pom.xml b/dsf-bpe/pom.xml index 3ce31c14a..8193deec6 100755 --- a/dsf-bpe/pom.xml +++ b/dsf-bpe/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-common/dsf-common-auth/pom.xml b/dsf-common/dsf-common-auth/pom.xml index 517d20d3e..a1d4a944f 100644 --- a/dsf-common/dsf-common-auth/pom.xml +++ b/dsf-common/dsf-common-auth/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-common-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-common/dsf-common-config/pom.xml b/dsf-common/dsf-common-config/pom.xml index a67c0299d..c5c11e5d8 100644 --- a/dsf-common/dsf-common-config/pom.xml +++ b/dsf-common/dsf-common-config/pom.xml @@ -8,7 +8,7 @@ dev.dsf dsf-common-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-common/dsf-common-documentation/pom.xml b/dsf-common/dsf-common-documentation/pom.xml index 852cf9516..c59ad2711 100644 --- a/dsf-common/dsf-common-documentation/pom.xml +++ b/dsf-common/dsf-common-documentation/pom.xml @@ -6,6 +6,6 @@ dev.dsf dsf-common-pom - 1.0.0 + 1.1.0-SNAPSHOT \ No newline at end of file diff --git a/dsf-common/dsf-common-jetty/pom.xml b/dsf-common/dsf-common-jetty/pom.xml index c4ca3bcb1..06cbdfd7a 100644 --- a/dsf-common/dsf-common-jetty/pom.xml +++ b/dsf-common/dsf-common-jetty/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-common-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-common/dsf-common-status/pom.xml b/dsf-common/dsf-common-status/pom.xml index ec28524c8..01d8ff2cf 100644 --- a/dsf-common/dsf-common-status/pom.xml +++ b/dsf-common/dsf-common-status/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-common-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-common/pom.xml b/dsf-common/pom.xml index 132986daa..75b5e6aef 100644 --- a/dsf-common/pom.xml +++ b/dsf-common/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-auth/pom.xml b/dsf-fhir/dsf-fhir-auth/pom.xml index e64ff594c..dac4b4e8f 100644 --- a/dsf-fhir/dsf-fhir-auth/pom.xml +++ b/dsf-fhir/dsf-fhir-auth/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml index f2705ae51..bf9bd91a6 100755 --- a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml +++ b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-server-jetty/pom.xml b/dsf-fhir/dsf-fhir-server-jetty/pom.xml index a11bd8e5e..f7a0243ce 100755 --- a/dsf-fhir/dsf-fhir-server-jetty/pom.xml +++ b/dsf-fhir/dsf-fhir-server-jetty/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-server/pom.xml b/dsf-fhir/dsf-fhir-server/pom.xml index 0c6e138ea..ca3fd9e9d 100755 --- a/dsf-fhir/dsf-fhir-server/pom.xml +++ b/dsf-fhir/dsf-fhir-server/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-validation/pom.xml b/dsf-fhir/dsf-fhir-validation/pom.xml index a51f8f83e..37e3c2185 100644 --- a/dsf-fhir/dsf-fhir-validation/pom.xml +++ b/dsf-fhir/dsf-fhir-validation/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT @@ -95,7 +95,7 @@ - @@ -105,7 +105,7 @@ org.eclipse.m2e lifecycle-mapping - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-webservice-client/pom.xml b/dsf-fhir/dsf-fhir-webservice-client/pom.xml index 1920dae9a..97c674748 100755 --- a/dsf-fhir/dsf-fhir-webservice-client/pom.xml +++ b/dsf-fhir/dsf-fhir-webservice-client/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/dsf-fhir-websocket-client/pom.xml b/dsf-fhir/dsf-fhir-websocket-client/pom.xml index 5aee3f0ae..475c35d61 100755 --- a/dsf-fhir/dsf-fhir-websocket-client/pom.xml +++ b/dsf-fhir/dsf-fhir-websocket-client/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-fhir/pom.xml b/dsf-fhir/pom.xml index 41dff18db..75b412c0f 100755 --- a/dsf-fhir/pom.xml +++ b/dsf-fhir/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-build-info-reader/pom.xml b/dsf-tools/dsf-tools-build-info-reader/pom.xml index 38d5d9f93..3a1da819a 100644 --- a/dsf-tools/dsf-tools-build-info-reader/pom.xml +++ b/dsf-tools/dsf-tools-build-info-reader/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-bundle-generator/pom.xml b/dsf-tools/dsf-tools-bundle-generator/pom.xml index 3670876b5..1a55240df 100755 --- a/dsf-tools/dsf-tools-bundle-generator/pom.xml +++ b/dsf-tools/dsf-tools-bundle-generator/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-db-migration/pom.xml b/dsf-tools/dsf-tools-db-migration/pom.xml index 400f742fe..af0e4e763 100755 --- a/dsf-tools/dsf-tools-db-migration/pom.xml +++ b/dsf-tools/dsf-tools-db-migration/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml b/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml index 511c3e3b1..493096376 100644 --- a/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml +++ b/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-documentation-generator/pom.xml b/dsf-tools/dsf-tools-documentation-generator/pom.xml index 01bfb8c08..0c5300f42 100644 --- a/dsf-tools/dsf-tools-documentation-generator/pom.xml +++ b/dsf-tools/dsf-tools-documentation-generator/pom.xml @@ -8,7 +8,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-proxy-test/pom.xml b/dsf-tools/dsf-tools-proxy-test/pom.xml index a12882b09..9593b7eb1 100755 --- a/dsf-tools/dsf-tools-proxy-test/pom.xml +++ b/dsf-tools/dsf-tools-proxy-test/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/dsf-tools-test-data-generator/pom.xml b/dsf-tools/dsf-tools-test-data-generator/pom.xml index 56683adce..cbaf9db83 100755 --- a/dsf-tools/dsf-tools-test-data-generator/pom.xml +++ b/dsf-tools/dsf-tools-test-data-generator/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/dsf-tools/pom.xml b/dsf-tools/pom.xml index 0ce004f08..4c69237db 100755 --- a/dsf-tools/pom.xml +++ b/dsf-tools/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.0.0 + 1.1.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index b8c98a322..857aded68 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ dev.dsf dsf-pom - 1.0.0 + 1.1.0-SNAPSHOT pom From f217cf48f4005304ba593e885836c2f45e0ef86a Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 17 Jul 2023 16:07:55 +0200 Subject: [PATCH 02/39] fix filtered inputs visability --- .../src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java index e26a3ff87..887948464 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java @@ -102,7 +102,7 @@ public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws .filter(isMessageName().negate().and(isBusinessKey().negate()).and(isCorrelationKey().negate())) .toList(); - if (filteredInputs.size() > 1) + if (filteredInputs.size() > 0) { out.write("
"); out.write("

Inputs

"); From 493197a4aa991bbf5c64e6ca70123b6a24386401 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 17 Jul 2023 16:08:34 +0200 Subject: [PATCH 03/39] fix background color of stopped/failed info box --- dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css index 25d3288e0..d04c0e509 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css @@ -80,7 +80,7 @@ fieldset#form-fieldset { } .info-color-stopped-failed { - background-color: #9858b; + background-color: #ffb3b3; color: #761137; } From 5e6887ac8aae962cb2bf3ea227761269de3d557a Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 31 Jul 2023 08:13:48 +0200 Subject: [PATCH 04/39] add missing auth to jetty property name --- dsf-bpe/dsf-bpe-server-jetty/conf/jetty.properties | 2 +- dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server-jetty/conf/jetty.properties b/dsf-bpe/dsf-bpe-server-jetty/conf/jetty.properties index f426c798d..c672ed4c0 100755 --- a/dsf-bpe/dsf-bpe-server-jetty/conf/jetty.properties +++ b/dsf-bpe/dsf-bpe-server-jetty/conf/jetty.properties @@ -6,4 +6,4 @@ dev.dsf.server.certificate=target/localhost_certificate.pem dev.dsf.server.certificate.key=target/localhost_private-key.pem dev.dsf.server.certificate.key.password=password -dev.dsf.server.trust.client.certificate.cas=target/testca_certificate.pem \ No newline at end of file +dev.dsf.server.auth.trust.client.certificate.cas=target/testca_certificate.pem \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties b/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties index 67f5b5018..7af2bbc07 100755 --- a/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties +++ b/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties @@ -6,4 +6,4 @@ dev.dsf.server.certificate=target/localhost_certificate.pem dev.dsf.server.certificate.key=target/localhost_private-key.pem dev.dsf.server.certificate.key.password=password -dev.dsf.server.trust.client.certificate.cas=target/testca_certificate.pem \ No newline at end of file +dev.dsf.server.auth.trust.client.certificate.cas=target/testca_certificate.pem \ No newline at end of file From d9bae646a4ddbfb7408671049e37980501eb53c3 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Wed, 2 Aug 2023 16:11:48 +0200 Subject: [PATCH 05/39] adapt form inputs based on profile --- .../dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 6 +- .../dsf/fhir/adapter/InputHtmlGenerator.java | 202 +++++---- .../QuestionnaireResponseHtmlGenerator.java | 12 +- .../dsf/fhir/adapter/TaskHtmlGenerator.java | 42 +- .../src/main/resources/static/form.css | 97 ++++- .../src/main/resources/static/form.js | 402 +++++++++++++----- 6 files changed, 512 insertions(+), 249 deletions(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java index 5e32d198b..46c1df7fb 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java @@ -160,7 +160,7 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno + uriInfo.getPath() + "\n"); out.write("\n"); out.write("\n"); + + ");checkBookmarked();" + adaptTaskFormInputs(resource) + "\">\n"); out.write("
\n"); @@ -435,10 +435,10 @@ private boolean isHtmlEnabled(Class resourceType) return htmlGeneratorsByType.containsKey(resourceType); } - private String adaptFormInputs(BaseResource resource) + private String adaptTaskFormInputs(BaseResource resource) { if (resource instanceof Task task) - return Task.TaskStatus.DRAFT.equals(task.getStatus()) ? "adaptFormInputs();" : ""; + return Task.TaskStatus.DRAFT.equals(task.getStatus()) ? "adaptTaskFormInputs();" : ""; else return ""; } diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java index e30fd49e9..9e8a29ae9 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java @@ -6,6 +6,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Supplier; import org.hl7.fhir.r4.model.BooleanType; @@ -32,179 +33,155 @@ public abstract class InputHtmlGenerator protected static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); protected static final SimpleDateFormat DATE_TIME_DISPLAY_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); - protected void writeDisplayRow(String text, String elementId, boolean display, OutputStreamWriter out) + protected void writeDisplayRow(String text, String elementName, boolean display, OutputStreamWriter out) throws IOException { - out.write("
\n"); out.write("

" + text + "\n"); out.write("

\n"); } - protected void writeInputRow(Type type, List extensions, String elementId, - Map elementIdIndexMap, String elementLabel, boolean display, boolean writable, + protected void writeInputRow(Type type, List extensions, String elementName, + Map elemenIndexMap, String elementLabel, boolean display, boolean writable, OutputStreamWriter out) throws IOException { - String elementIdWithIndex = getElementIdWithIndex(elementId, elementIdIndexMap); + int elementIndex = getElementIndex(elementName, elemenIndexMap); - out.write("
\n"); + out.write("
\n"); - writeInputLabel(type, elementIdWithIndex, elementLabel, () -> "", out); - writeInputField(type, elementIdWithIndex, writable, out); - writeInputExtensionFields(extensions, elementIdWithIndex, writable, 0, out); + writeInputLabel(elementLabel, elementIndex, () -> "", out); + writeInputField(type, elementName, elementIndex, writable, out); + writeInputExtensionFields(extensions, elementName, elementIndex, writable, 0, out); - out.write("
    \n"); + out.write("
      \n"); out.write("
    \n"); out.write("
\n"); } - protected void writeInputLabel(Type type, String elementId, String elementLabel, Supplier additionalClasses, + protected void writeInputLabel(String elementLabel, int elementIndex, Supplier additionalClasses, OutputStreamWriter out) throws IOException { - if (type instanceof Identifier || type instanceof Coding) - { - elementId = elementId + "-system"; - } - else if (type instanceof Reference reference && reference.hasIdentifier()) - { - elementId = elementId + "-system"; - } - else if (type instanceof BooleanType) - { - elementId = elementId + "-true"; - } - String forElement = type != null ? " for=\"" + elementId + "\"" : ""; - out.write("\n"); + out.write("\n"); } - protected void writeInputExtensionFields(List extensions, String elementId, boolean writable, int depth, - OutputStreamWriter out) throws IOException + protected void writeInputExtensionFields(List extensions, String elementName, int elementIndex, + boolean writable, int depth, OutputStreamWriter out) throws IOException { for (Extension extension : extensions) { - String extensionElementId = elementId + "-" + extension.getUrl(); - out.write("
\n"); + String extensionelementName = elementName + "-" + extension.getUrl(); + out.write("
\n"); String extensionElementLabel = "Extension: " + extension.getUrl(); if (extension.hasValue()) { - writeInputLabel(extension.getValue(), extensionElementId, extensionElementLabel, () -> "", out); - writeInputField(extension.getValue(), extensionElementId, writable, out); + writeInputLabel(extensionElementLabel, elementIndex, () -> "", out); + writeInputField(extension.getValue(), extensionelementName, elementIndex, writable, out); } else { - writeInputLabel(null, extensionElementId, extensionElementLabel, () -> "row-label-extension-no-value", - out); + writeInputLabel(extensionElementLabel, elementIndex, () -> "row-label-extension-no-value", out); } if (extension.hasExtension()) { - writeInputExtensionFields(extension.getExtension(), extensionElementId, writable, ++depth, out); + writeInputExtensionFields(extension.getExtension(), extensionelementName, elementIndex, writable, + ++depth, out); } out.write("
\n"); } } - protected void writeInputField(Type type, String elementId, boolean writable, OutputStreamWriter out) - throws IOException + protected void writeInputField(Type type, String elementName, int elementIndex, boolean writable, + OutputStreamWriter out) throws IOException { if (type != null) { if (type instanceof StringType) { String value = ((StringType) type).getValue(); - out.write("\n"); + writeInputFieldValueInput("text", value, elementName, elementIndex, writable, out); } else if (type instanceof IntegerType) { String value = String.valueOf(((IntegerType) type).getValue()); - out.write("\n"); + writeInputFieldValueInput("number", value, elementName, elementIndex, writable, out); } else if (type instanceof DecimalType) { String value = String.valueOf(((DecimalType) type).getValue()); - out.write("\n"); - } - else if (type instanceof BooleanType) - { - boolean valueIsTrue = ((BooleanType) type).getValue(); - - out.write("
\n"); - out.write("\n"); - out.write("\n"); - out.write("
\n"); + writeInputFieldValueInput("number", value, elementName, elementIndex, writable, out); } else if (type instanceof DateType) { Date value = ((DateType) type).getValue(); String date = DATE_FORMAT.format(value); - - out.write("\n"); + writeInputFieldValueInput("date", date, elementName, elementIndex, writable, out); } else if (type instanceof TimeType) { String value = ((TimeType) type).getValue(); - out.write("\n"); + writeInputFieldValueInput("time", value, elementName, elementIndex, writable, out); } else if (type instanceof DateTimeType) { Date value = ((DateTimeType) type).getValue(); String dateTime = DATE_TIME_FORMAT.format(value); - - out.write("\n"); + writeInputFieldValueInput("datetime-local", dateTime, elementName, elementIndex, writable, out); } else if (type instanceof InstantType) { Date value = ((InstantType) type).getValue(); String dateTime = DATE_TIME_FORMAT.format(value); - - out.write("\n"); + writeInputFieldValueInput("datetime-local", dateTime, elementName, elementIndex, writable, out); } else if (type instanceof UriType) { String value = ((UriType) type).getValue(); - out.write("\n"); + writeInputFieldValueInput("url", value, elementName, elementIndex, writable, out); } else if (type instanceof Reference reference) { if (reference.hasReference()) { - out.write("\n"); + String value = reference.getReference(); + writeInputFieldValueInput("url", value, elementName, elementIndex, writable, out); } else if (reference.hasIdentifier()) { Identifier identifier = reference.getIdentifier(); - writeInputFieldSystemValueInput(elementId, writable, identifier.getSystem(), identifier.getValue(), - out); + writeInputFieldSystemCodeInput(identifier.getSystem(), identifier.getValue(), elementName, + elementIndex, writable, out); } } else if (type instanceof Identifier identifier) { - writeInputFieldSystemValueInput(elementId, writable, identifier.getSystem(), identifier.getValue(), - out); + writeInputFieldSystemCodeInput(identifier.getSystem(), identifier.getValue(), elementName, elementIndex, + writable, out); } else if (type instanceof Coding coding) { - writeInputFieldSystemValueInput(elementId, writable, coding.getSystem(), coding.getCode(), out); + writeInputFieldSystemCodeInput(coding.getSystem(), coding.getCode(), elementName, elementIndex, + writable, out); + } + else if (type instanceof BooleanType) + { + boolean valueIsTrue = ((BooleanType) type).getValue(); + + out.write("
\n"); + out.write("\n"); + out.write("\n"); + out.write("
\n"); } else { @@ -214,28 +191,65 @@ else if (type instanceof Coding coding) } } - private void writeInputFieldSystemValueInput(String elementId, boolean writable, String system, String value, - OutputStreamWriter out) throws IOException + private void writeInputFieldValueInput(String type, String value, String elementName, int elementIndex, + boolean writable, OutputStreamWriter out) throws IOException + { + out.write("
\n"); + writeInput(type, value, elementName, elementIndex, Optional.empty(), writable, out); + writePlaceholderButton(elementName, value, writable, out); + out.write("
\n"); + } + + private void writeInputFieldSystemCodeInput(String system, String code, String elementName, int elementIndex, + boolean writable, OutputStreamWriter out) throws IOException + { + out.write("
\n"); + writeInput("url", system, elementName + "-system", elementIndex, Optional.empty(), writable, out); + writePlaceholderButton(elementName + "-system", system, writable, out); + out.write("
\n"); + + out.write("
\n"); + writeInput("text", code, elementName + "-code", elementIndex, Optional.of("identifier-coding-code"), writable, + out); + writePlaceholderButton(elementName + "-code", code, writable, out); + out.write("
\n"); + } + + private void writeInput(String type, String value, String elementName, int elementIndex, Optional classes, + boolean writable, OutputStreamWriter out) throws IOException + { + out.write(" " class=\"" + c + "\"").orElse("")) + " name=\"" + + elementName + "\" index=\"" + elementIndex + "\" " + + (writable ? "placeholder=\"" + value + "\"" : "value=\"" + value + "\"") + ">\n"); + } + + private void writePlaceholderButton(String elementName, String value, boolean writable, OutputStreamWriter out) + throws IOException { - out.write("\n"); - out.write("\n"); + if (writable) + { + out.write("\n"); + out.write("Use placeholder\n"); + out.write( + "\n"); + out.write("\n"); + } } - private String getElementIdWithIndex(String elementId, Map elementIdIndexMap) + private int getElementIndex(String elementName, Map elementIndexMap) { - if (elementIdIndexMap.containsKey(elementId)) + if (elementIndexMap.containsKey(elementName)) { - int index = elementIdIndexMap.get(elementId) + 1; - elementIdIndexMap.put(elementId, index); - return elementId + "-" + index; + int index = elementIndexMap.get(elementName) + 1; + elementIndexMap.put(elementName, index); + return index; } else { - elementIdIndexMap.put(elementId, 0); - return elementId + "-0"; + elementIndexMap.put(elementName, 0); + return 0; } } } diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java index 43e3ae3b7..c4de31090 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java @@ -71,17 +71,17 @@ public void writeHtml(String basePath, QuestionnaireResponse questionnaireRespon out.write("
\n"); - Map elementIdIndexMap = new HashMap<>(); + Map elemenIndexMap = new HashMap<>(); for (QuestionnaireResponse.QuestionnaireResponseItemComponent item : questionnaireResponse.getItem()) { - writeRow(item, elementIdIndexMap, completed, out); + writeRow(item, elemenIndexMap, completed, out); } if (QuestionnaireResponse.QuestionnaireResponseStatus.INPROGRESS.equals(questionnaireResponse.getStatus())) { - out.write("
\n"); + out.write("
\n"); out.write( - "\n"); + "\n"); out.write("
\n"); } @@ -131,7 +131,7 @@ private String getProcessInstanceId(QuestionnaireResponse questionnaireResponse) } private void writeRow(QuestionnaireResponse.QuestionnaireResponseItemComponent item, - Map elementIdIndexMap, boolean completed, OutputStreamWriter out) throws IOException + Map elemenIndexMap, boolean completed, OutputStreamWriter out) throws IOException { String linkId = item.getLinkId(); String text = item.getText(); @@ -139,7 +139,7 @@ private void writeRow(QuestionnaireResponse.QuestionnaireResponseItemComponent i boolean writable = !completed; if (item.hasAnswer()) - writeInputRow(item.getAnswerFirstRep().getValue(), Collections.emptyList(), linkId, elementIdIndexMap, text, + writeInputRow(item.getAnswerFirstRep().getValue(), Collections.emptyList(), linkId, elemenIndexMap, text, display, writable, out); else writeDisplayRow(text, linkId, display, out); diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java index 887948464..3654ab1a2 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java @@ -78,23 +78,23 @@ public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws out.write("
\n"); - out.write("
\n"); - out.write("\n"); - out.write("\n"); + out.write("\n"); + out.write("\n"); out.write("
\n"); - out.write("
\n"); - out.write("\n"); - out.write("\n"); + out.write("\n"); + out.write("\n"); out.write("
\n"); String authoredOn = DATE_TIME_FORMAT.format(task.getAuthoredOn()); - out.write("
\n"); - out.write("\n"); - out.write("\n"); + out.write("\n"); + out.write("\n"); out.write("
\n"); @@ -104,13 +104,13 @@ public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws if (filteredInputs.size() > 0) { - out.write("
"); + out.write("
"); out.write("

Inputs

"); - Map elementIdIndexMap = new HashMap<>(); + Map elemenIndexMap = new HashMap<>(); for (ParameterComponent input : filteredInputs) { - writeInput(input, elementIdIndexMap, draft, out); + writeInput(input, elemenIndexMap, draft, out); } out.write("
"); @@ -118,13 +118,13 @@ public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws if (task.hasOutput()) { - out.write("
"); + out.write("
"); out.write("

Outputs

"); - Map elementIdIndexMap = new HashMap<>(); + Map elemenIndexMap = new HashMap<>(); for (TaskOutputComponent output : task.getOutput()) { - writeOutput(output, elementIdIndexMap, out); + writeOutput(output, elemenIndexMap, out); } out.write("
"); @@ -132,8 +132,8 @@ public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws if (draft) { - out.write("
\n"); - out.write("\n"); out.write("
\n"); } @@ -236,7 +236,7 @@ else if (ELEMENT_TYPE_PATH.equals(elementType)) } } - private void writeInput(Task.ParameterComponent input, Map elementIdIndexMap, boolean draft, + private void writeInput(Task.ParameterComponent input, Map elemenIndexMap, boolean draft, OutputStreamWriter out) throws IOException { String typeCode = getTypeCode(input); @@ -244,18 +244,18 @@ private void writeInput(Task.ParameterComponent input, Map elem if (input.hasValue()) { - writeInputRow(input.getValue(), input.getExtension(), typeCode, elementIdIndexMap, typeCode, display, draft, + writeInputRow(input.getValue(), input.getExtension(), typeCode, elemenIndexMap, typeCode, display, draft, out); } } - private void writeOutput(Task.TaskOutputComponent output, Map elementIdIndexMap, + private void writeOutput(Task.TaskOutputComponent output, Map elemenIndexMap, OutputStreamWriter out) throws IOException { String typeCode = getTypeCode(output); if (output.hasValue()) { - writeInputRow(output.getValue(), output.getExtension(), typeCode, elementIdIndexMap, typeCode, true, false, + writeInputRow(output.getValue(), output.getExtension(), typeCode, elemenIndexMap, typeCode, true, false, out); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css index d04c0e509..f1a0cb4b9 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css @@ -178,13 +178,6 @@ label { font-weight: regular; } -.row-label { - padding: 12px 12px 6px 6px; - font-weight: bold; - font-size: small; -} - - label.radio { padding: 5px 12px 5px 0; font-weight: 100; @@ -196,6 +189,7 @@ input[type=radio] { input[type=text], input[type=url], select, textarea { width: 100%; + min-width: 200px; padding: 12px; border: 1px solid #ccc; border-radius: 4px; @@ -205,8 +199,8 @@ input[type=text], input[type=url], select, textarea { } input[type=date], input[type=time], input[type=datetime-local], input[type=number] { - width: auto; - min-width: 250px; + width: 100%; + min-width: 200px; padding: 12px; border: 1px solid #ccc; border-radius: 4px; @@ -215,7 +209,7 @@ input[type=date], input[type=time], input[type=datetime-local], input[type=numbe display: block; } -input.identifier-coding-value { +input.identifier-coding-code { margin-top: 6px; } @@ -291,4 +285,87 @@ button.submit[disabled] { border-radius: 50%; -webkit-animation: spin .8s linear infinite; animation: spin .8s linear infinite; +} + +.cardinalities { + font-weight: normal; + font-size: x-small; + color: gray; +} + +.row-label { + padding: 12px 0 6px 6px; + font-weight: bold; + font-size: small; + position: relative; +} + +.plus-minus-icon { + position: absolute; + bottom: 0; + right: 0; + cursor: pointer; +} + +.plus-minus-icon:hover > svg > path { + fill: #326F95; +} + +.plus-minus-icon > svg > path { + fill: #aaa; +} + +.input-group { + position: relative +} + +.input-group-svg { + position: absolute; + right: 0.45rem; + bottom: 0.65rem; + cursor: pointer; +} + +.input-group-svg > path { + fill: #aaa; +} + +.input-group-svg:hover > path { + fill: #326F95; +} + +input[type="date"]::-webkit-inner-spin-button, +input[type="date"]::-webkit-calendar-picker-indicator { + display: none; + -webkit-appearance: none; +} + +input[type="time"]::-webkit-inner-spin-button, +input[type="time"]::-webkit-calendar-picker-indicator { + display: none; + -webkit-appearance: none; +} + +input[type="dateTime-local"]::-webkit-inner-spin-button, +input[type="dateTime-local"]::-webkit-calendar-picker-indicator { + display: none; + -webkit-appearance: none; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type=number] { + -moz-appearance: textfield; +} + +::placeholder { + color: #ddd; +} + +::-ms-input-placeholder { + color: #ddd; } \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js index 0cf272150..d943d81b5 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js @@ -28,18 +28,30 @@ function readTaskInputsFromForm(task, errors) { task.status = "requested" task.authoredOn = new Date().toISOString() - const idIndexMap = new Map() + const newInputs = [] task.input.forEach((input) => { if (input.hasOwnProperty("type")) { - const id = input.type.coding[0].code - - if (id !== "message-name" && id !== "business-key" && id !== "correlation-key") { - const inputValueType = Object.keys(input).find((string) => string.startsWith("value")) - input[inputValueType] = readAndValidateValue(input, id, idIndexMap, inputValueType, errors) + const code = input.type.coding[0].code + + if (code !== "message-name" && code !== "business-key" && code !== "correlation-key") { + document.querySelectorAll("div[name='" + code + "-input-row']").forEach(rowElement => { + const newInput = JSON.parse(JSON.stringify(input)) // clone + const inputValueType = Object.keys(newInput).find((string) => string.startsWith("value")) + const inputValue = readAndValidateValue(rowElement, newInput, code, inputValueType, errors) + + if (inputValue) { + newInput[inputValueType] = inputValue + newInputs.push(newInput) + } + }) + } else { + newInputs.push(input) } } }) + + task.input = newInputs } function completeQuestionnaireResponse() { @@ -60,83 +72,89 @@ function completeQuestionnaireResponse() { function readQuestionnaireResponseAnswersFromForm(questionnaireResponse, errors) { questionnaireResponse.status = "completed" - const idIndexMap = new Map() + const newItems = [] questionnaireResponse.item.forEach((item) => { if (item.hasOwnProperty("answer")) { const id = item.linkId if (id !== "business-key" && id !== "user-task-id") { - const answer = item.answer[0] - const answerType = Object.keys(answer).find((string) => string.startsWith("value")) - - answer[answerType] = readAndValidateValue(answer, id, idIndexMap, answerType, errors) + document.querySelectorAll("div[name='" + id + "-input-row']").forEach(rowElement => { + const newItem = JSON.parse(JSON.stringify(item)) // clone + const answer = newItem.answer[0] + const answerType = Object.keys(answer).find((string) => string.startsWith("value")) + const answerValue = readAndValidateValue(rowElement, answer, id, answerType, errors) + + if (answerValue) { + answer[answerType] = answerValue + newItems.push(newItem) + } + }) + } else { + newItems.push(item) } } }) + + questionnaireResponse.item = newItems } -function readAndValidateValue(templateValue, id, idIndexMap, valueType, errors) { - const idWithIndex = getIdWithIndex(id, idIndexMap) +function readAndValidateValue(rowElement, templateValue, name, valueType, errors) { + const valueElement = rowElement.querySelector("input[name='" + name + "']") + const value = valueElement?.value + const valueBoolean = rowElement.querySelector("input[name='" + name + "']:checked")?.value + const valueValue = rowElement.querySelector("input[name='" + name + "-code']")?.value + const valueSystem = rowElement.querySelector("input[name='" + name + "-system']")?.value - const parentElement = document.getElementById(idWithIndex) + const optional = rowElement.hasAttribute("optional") + const valueExists = ((value && valueType !== "valueBoolean") || + (valueBoolean && valueType === "valueBoolean") || + valueValue || valueSystem) - const value = parentElement?.value - const valueSystem = document.getElementById(idWithIndex + "-system")?.value - const valueValue = document.getElementById(idWithIndex + "-value")?.value + if (optional && !valueExists) { + return null + } - const rowElement = document.getElementById(idWithIndex + "-input-row") - const errorListElement = document.getElementById(idWithIndex + "-error") + const errorListElement = rowElement.querySelector("ul[name='" + name + "-error']") errorListElement.replaceChildren() if (valueType === 'valueString') { - return validateString(rowElement, errorListElement, value, errors, idWithIndex) + return validateString(rowElement, errorListElement, value, errors) } else if (valueType === 'valueInteger') { - return validateInteger(rowElement, errorListElement, value, errors, idWithIndex) + return validateInteger(rowElement, errorListElement, value, errors) } else if (valueType === 'valueDecimal') { - return validateDecimal(rowElement, errorListElement, value, errors, idWithIndex) + return validateDecimal(rowElement, errorListElement, value, errors) } else if (valueType === 'valueDate') { - return validateDate(rowElement, errorListElement, value, errors, idWithIndex) + return validateDate(rowElement, errorListElement, value, errors) } else if (valueType === 'valueTime') { - return validateTime(rowElement, errorListElement, value, errors, idWithIndex) + return validateTime(rowElement, errorListElement, value, errors) } else if (valueType === 'valueDateTime') { - return validateDateTime(rowElement, errorListElement, value, errors, idWithIndex) + return validateDateTime(rowElement, errorListElement, value, errors) } else if (valueType === 'valueInstant') { - return validateInstant(rowElement, errorListElement, value, errors, idWithIndex) + return validateInstant(rowElement, errorListElement, value, errors) } else if (valueType === 'valueUri') { - return validateUrl(rowElement, errorListElement, value, errors, idWithIndex) + return validateUrl(rowElement, errorListElement, value, errors) } else if (valueType === 'valueReference') { - if (parentElement) { - return validateReference(rowElement, errorListElement, value, errors, idWithIndex) + if (valueElement) { + return validateReference(rowElement, errorListElement, value, errors) } else { - const valueIdentifier = validateIdentifier(rowElement, errorListElement, valueSystem, valueValue, errors, idWithIndex) + const valueIdentifier = validateIdentifier(rowElement, errorListElement, valueSystem, valueValue, errors) return {identifier: valueIdentifier, type: templateValue?.valueReference?.type} } } else if (valueType === 'valueBoolean') { - return document.querySelector("input[name=" + idWithIndex + "]:checked").value + return validateBoolean(rowElement, errorListElement, valueBoolean, errors) } else if (valueType === "valueIdentifier") { - return validateIdentifier(rowElement, errorListElement, valueSystem, valueValue, errors, idWithIndex) + return validateIdentifier(rowElement, errorListElement, valueSystem, valueValue, errors) } else if (valueType === "valueCoding") { - return validateCoding(rowElement, errorListElement, valueSystem, valueValue, errors, idWithIndex) + return validateCoding(rowElement, errorListElement, valueSystem, valueValue, errors) } else { return null } } -function getIdWithIndex(id, idIndexMap) { - if (idIndexMap.has(id)) { - const index = idIndexMap.get(id) + 1 - idIndexMap.set(id, index) - return id + "-" + index - } else { - idIndexMap.set(id, 0) - return id + "-0" - } -} - -function validateString(rowElement, errorListElement, value, errors, id) { +function validateString(rowElement, errorListElement, value, errors) { if (value === null || value.trim() === "") { - addError(rowElement, errorListElement, errors, id, "Value is null or empty") + addError(rowElement, errorListElement, errors, "Value is null or empty") return null } else { removeError(rowElement, errorListElement) @@ -144,11 +162,11 @@ function validateString(rowElement, errorListElement, value, errors, id) { } } -function validateInteger(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateInteger(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) if (!Number.isInteger(parseInt(value))) { - addError(rowElement, errorListElement, errors, id, "Value is not an integer") + addError(rowElement, errorListElement, errors, "Value is not an integer") return null } else { removeError(rowElement, errorListElement) @@ -156,11 +174,11 @@ function validateInteger(rowElement, errorListElement, value, errors, id) { } } -function validateDecimal(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateDecimal(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) if (isNaN(parseFloat(value))) { - addError(rowElement, errorListElement, errors, id, "Value is not a decimal") + addError(rowElement, errorListElement, errors, "Value is not a decimal") return null } else { removeError(rowElement, errorListElement) @@ -168,12 +186,12 @@ function validateDecimal(rowElement, errorListElement, value, errors, id) { } } -function validateDate(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateDate(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) const date = new Date(value) if ((date === "Invalid Date") || isNaN(date)) { - addError(rowElement, errorListElement, errors, id, "Value is not a date") + addError(rowElement, errorListElement, errors, "Value is not a date") return null } else { removeError(rowElement, errorListElement) @@ -181,97 +199,108 @@ function validateDate(rowElement, errorListElement, value, errors, id) { } } -function validateTime(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateTime(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) - if (!(new RegExp('^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$').test(value))) { - addError(rowElement, errorListElement, errors, id, "Value is not a time") + if (!(new RegExp('^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])(:[0-5]?[0-9])?$').test(value))) { + addError(rowElement, errorListElement, errors, "Value is not a time") return null } else { removeError(rowElement, errorListElement) - return value + ":00" + return (value.split(":").length - 1 === 1) ? value + ":00" : value } } -function validateDateTime(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateDateTime(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) try { const dateTime = new Date(value).toISOString() removeError(rowElement, errorListElement) return dateTime } catch (_) { - addError(rowElement, errorListElement, errors, id, "Value is not a date time") + addError(rowElement, errorListElement, errors, "Value is not a date time") return null } } -function validateInstant(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateInstant(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) try { const dateTime = new Date(value).toISOString() removeError(rowElement, errorListElement) return dateTime } catch (_) { - addError(rowElement, errorListElement, errors, id, "Value is not an instant") + addError(rowElement, errorListElement, errors, "Value is not an instant") return null } } -function validateReference(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateReference(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) try { new URL(value) removeError(rowElement, errorListElement) return {reference: value} } catch (_) { - addError(rowElement, errorListElement, errors, id, "Value is not a reference") + addError(rowElement, errorListElement, errors, "Value is not a reference") return null } } -function validateUrl(rowElement, errorListElement, value, errors, id) { - validateString(rowElement, errorListElement, value, errors, id) +function validateUrl(rowElement, errorListElement, value, errors) { + validateString(rowElement, errorListElement, value, errors) try { new URL(value) removeError(rowElement, errorListElement) return value } catch (_) { - addError(rowElement, errorListElement, errors, id, "Value is not a url") + addError(rowElement, errorListElement, errors, "Value is not a url") return null } } -function validateIdentifier(rowElement, errorListElement, valueSystem, valueValue, errors, id) { - const validatedSystem = validateUrl(rowElement, errorListElement, valueSystem, errors, id) - const validatedValue = validateString(rowElement, errorListElement, valueValue, errors, id) +function validateBoolean(rowElement, errorListElement, valueBoolean, errors) { + if (valueBoolean === "true" || valueBoolean === "false") { + removeError(rowElement, errorListElement) + return valueBoolean + } else { + addError(rowElement, errorListElement, errors, "Boolean value not selected") + return null + } +} + +function validateIdentifier(rowElement, errorListElement, valueSystem, valueValue, errors) { + const validatedSystem = validateUrl(rowElement, errorListElement, valueSystem, errors) + const validatedValue = validateString(rowElement, errorListElement, valueValue, errors) if (validatedSystem && validatedValue) { removeError(rowElement, errorListElement) return {system: valueSystem, value: valueValue} } else { - addError(rowElement, errorListElement, errors, id, "System or value not usable for identifier") + addError(rowElement, errorListElement, errors, "System or value not usable for identifier") return null } } -function validateCoding(rowElement, errorListElement, valueSystem, valueValue, errors, id) { - const validatedSystem = validateUrl(rowElement, errorListElement, valueSystem, errors, id) - const validatedCode = validateString(rowElement, errorListElement, valueValue, errors, id) +function validateCoding(rowElement, errorListElement, valueSystem, valueValue, errors) { + const validatedSystem = validateUrl(rowElement, errorListElement, valueSystem, errors) + const validatedCode = validateString(rowElement, errorListElement, valueValue, errors) if (validatedSystem && validatedCode) { removeError(rowElement, errorListElement) return {system: valueSystem, code: valueValue} } else { - addError(rowElement, errorListElement, errors, id, "System or code not usable for coding") + addError(rowElement, errorListElement, errors, "System or code not usable for coding") return null } } -function addError(rowElement, errorListElement, errors, id, message) { +function addError(rowElement, errorListElement, errors, message) { + const id = rowElement.getAttribute("name") + "-" + rowElement.getAttribute("index") errors.push({id: id, error: message}) rowElement.classList.add("error") @@ -361,40 +390,183 @@ function disableSpinner() { spinner.classList.add("spinner-disabled") } -function adaptFormInputs() { -// const resourceType = getResourceTypeForCurrentUrl(); -// -// if (resourceType !== null && resourceType[1] !== undefined && resourceType[1] === 'Task') { -// const task = getResourceAsJson() -// -// if (task.meta !== null && task.meta.profile !== null && task.meta.profile.length > 0) { -// const profile = task.meta.profile[0].split("|") -// -// if (profile.length > 0) { -// let currentUrl = window.location.origin + window.location.pathname -// let requestUrl = currentUrl.slice(0, currentUrl.indexOf("/Task")) + "/StructureDefinition?url=" + profile[0] -// -// if (profile.length > 1) { -// requestUrl = requestUrl + "&version=" + profile[1] -// } -// -// fetch(requestUrl, { -// method: "GET", -// headers: { -// 'Accept': 'application/json' -// } -// }).then(response => { -// console.log(response) -// -// if (response.ok) { -// response.json().then((json) => { -// // TODO -// }) -// } -// }) -// } -// } -// } +function adaptTaskFormInputs() { + const resourceType = getResourceTypeForCurrentUrl(); + + if (resourceType !== null && resourceType[1] !== undefined && resourceType[1] === 'Task') { + const task = getResourceAsJson() + + if (task.meta !== null && task.meta.profile !== null && task.meta.profile.length > 0) { + const profile = task.meta.profile[0].split("|") + + if (profile.length > 0) { + let currentUrl = window.location.origin + window.location.pathname + let requestUrl = currentUrl.slice(0, currentUrl.indexOf("/Task")) + "/StructureDefinition?url=" + profile[0] + + if (profile.length > 1) { + requestUrl = requestUrl + "&version=" + profile[1] + } + + loadStructureDefinition(requestUrl) + .then(bundle => parseStructureDefinition(bundle)) + } + } + } +} + +function loadStructureDefinition(url) { + return fetch(url, { + method: "GET", + headers: { + 'Accept': 'application/json' + } + }).then(response => response.json()) +} + +function parseStructureDefinition(bundle) { + console.log(bundle) + + if (bundle.entry.length > 0 && bundle.entry[0].resource != null) { + const structureDefinition = bundle.entry[0].resource + + if (structureDefinition.differential != null) { + const differentials = structureDefinition.differential.element + const slices = filterInputSlices(differentials) + const groupedSlices = groupBy(slices, d => d.id.split(".")[1]) + const definitions = getDefinitions(groupedSlices) + + console.log(definitions) + + const indices = new Map() + definitions.forEach(definition => { modifyInputRow(definition, indices) }) + } + } +} + +function filterInputSlices(differentials) { + return differentials.filter(diff => diff.id.startsWith("Task.input:") + && !(diff.id.includes("message-name") || diff.id.includes("business-key") + || diff.id.includes("correlation-key"))) +} + +function groupBy(list, keyGetter) { + const map = new Map() + + list.forEach((item) => { + const key = keyGetter(item); + const collection = map.get(key) + + if (!collection) { + map.set(key, [item]) + } else { + collection.push(item) + } + }); + + return Array.from(map.values()); +} + +function getDefinitions(groupedSlices) { + return groupedSlices.map(differentials => { + const valueType = getValueOfDifferential(differentials, "Task.input.value[x]", "type") + + return { + identifier: window.location.href, + typeSystem: getValueOfDifferential(differentials, "Task.input.type.coding.system", "fixedUri"), + typeCode: getValueOfDifferential(differentials, "Task.input.type.coding.code", "fixedCode"), + valueType: (valueType != undefined && valueType.length > 0) ? valueType[0].code : undefined, + min: getValueOfDifferential(differentials, "Task.input", "min"), + max: getValueOfDifferential(differentials, "Task.input", "max"), + } + }) +} + +function getValueOfDifferential(differentials, path, property) { + const values = differentials.filter(d => d.path !== null && d.path === path) + + if (values.length > 0) { + return values[0][property] + } else { + return undefined + } +} + +function modifyInputRow(definition, indices) { + const row = document.querySelector("[name='" + definition.typeCode + "-input-row']") + + const index = parseInt(row.getAttribute("index")) + indices.set(getDefinitionId(definition), index) + + const label = row.querySelector("label") + if (label) { + const cardinalities = htmlToElement(" [" + definition.min + ".." + definition.max + "]") + label.appendChild(cardinalities) + + if (definition.max !== "1") { + const plusIcon = htmlToElement("") + const plusIconSvg = htmlToElement("Add additional input") + + plusIconSvg.addEventListener("click", () => { + appendInputRowAfter(row, definition, indices) + }) + + plusIcon.appendChild(plusIconSvg) + label.appendChild(plusIcon) + } + } + + if (definition.min < 1 || definition.min === undefined) + row.setAttribute("optional", "") +} + +function appendInputRowAfter(inputRow, definition, indices) { + const clone = inputRow.cloneNode(true); + + const index = getIndex(getDefinitionId(definition), indices) + clone.setAttribute("index", index) + clone.querySelectorAll("[index]").forEach( e => { e.setAttribute("index", index) }) + + clone.querySelector("span[class='plus-minus-icon']").remove() + + const label = clone.querySelector("label") + if (label) { + const minusIcon = htmlToElement("") + const minusIconSvg = htmlToElement("") + + minusIconSvg.addEventListener("click", () => { clone.remove() }) + + minusIcon.appendChild(minusIconSvg) + label.appendChild(minusIcon) + } + + inputRow.after(clone) +} + +function insertPlaceholderInValue(element, name, placeholder) { + const input = element.querySelector("input[name='" + name + "']") + input.text = placeholder + input.value = placeholder +} + +function htmlToElement(html) { + const template = document.createElement('template'); + template.innerHTML = html; + return template.content.firstChild; +} + +function getDefinitionId(definition) { + return definition.typeSystem + "|" + definition.typeCode +} + +function getIndex(id, indexMap) { + if (indexMap.has(id)) { + const index = indexMap.get(id) + 1 + indexMap.set(id, index) + return index + } else { + indexMap.set(id, 0) + return 0 + } } function getResourceAsJson() { From 344c4a9c069069813e59e34ab2fee0b6a764427a Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 8 Aug 2023 13:05:36 +0200 Subject: [PATCH 06/39] improve env definition --- .../dev/dsf/tools/generator/EnvGenerator.java | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/EnvGenerator.java b/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/EnvGenerator.java index 1ee45129d..5288fa740 100644 --- a/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/EnvGenerator.java +++ b/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/EnvGenerator.java @@ -5,7 +5,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -28,16 +27,11 @@ private static final class EnvEntry { final String userThumbprintVariableName; final String userThumbprint; - final String webbrowserTestUserThumbprintVariableName; - final String webbrowserTestUserThumbprint; - EnvEntry(String userThumbprintVariableName, String userThumbprint, - String webbrowserTestUserThumbprintVariableName, String webbrowserTestUserThumbprint) + EnvEntry(String userThumbprintVariableName, String userThumbprint) { this.userThumbprintVariableName = userThumbprintVariableName; this.userThumbprint = userThumbprint; - this.webbrowserTestUserThumbprintVariableName = webbrowserTestUserThumbprintVariableName; - this.webbrowserTestUserThumbprint = webbrowserTestUserThumbprint; } } @@ -49,8 +43,8 @@ public void generateAndWriteDockerTestFhirEnvFile(Map "Webbrowser Test User").findFirst().get(); writeEnvFile(Paths.get("../../dsf-docker-test-setup/fhir/.env"), - Collections.singletonList(new EnvEntry(BUNDLE_USER_THUMBPRINT, bundleUserThumbprint, - WEBBROSER_TEST_USER_THUMBPRINT, webbroserTestUserThumbprint))); + List.of(new EnvEntry(BUNDLE_USER_THUMBPRINT, bundleUserThumbprint), + new EnvEntry(WEBBROSER_TEST_USER_THUMBPRINT, webbroserTestUserThumbprint))); } public void generateAndWriteDockerTest3DicTtpDockerFhirEnvFiles( @@ -71,12 +65,11 @@ public void generateAndWriteDockerTest3DicTtpDockerFhirEnvFiles( String bundleTtpUserThumbprint = filterAndMapToThumbprint(clientCertificateFilesByCommonName, "ttp-client") .findFirst().get(); - List entries = List.of( - new EnvEntry("DIC1_" + BUNDLE_USER_THUMBPRINT, bundleDic1UserThumbprint, WEBBROSER_TEST_USER_THUMBPRINT, - webbroserTestUserThumbprint), - new EnvEntry("DIC2_" + BUNDLE_USER_THUMBPRINT, bundleDic2UserThumbprint, null, null), - new EnvEntry("DIC3_" + BUNDLE_USER_THUMBPRINT, bundleDic3UserThumbprint, null, null), - new EnvEntry("TTP_" + BUNDLE_USER_THUMBPRINT, bundleTtpUserThumbprint, null, null)); + List entries = List.of(new EnvEntry(WEBBROSER_TEST_USER_THUMBPRINT, webbroserTestUserThumbprint), + new EnvEntry("DIC1_" + BUNDLE_USER_THUMBPRINT, bundleDic1UserThumbprint), + new EnvEntry("DIC2_" + BUNDLE_USER_THUMBPRINT, bundleDic2UserThumbprint), + new EnvEntry("DIC3_" + BUNDLE_USER_THUMBPRINT, bundleDic3UserThumbprint), + new EnvEntry("TTP_" + BUNDLE_USER_THUMBPRINT, bundleTtpUserThumbprint)); writeEnvFile(Paths.get("../../dsf-docker-test-setup-3dic-ttp/.env"), entries); } @@ -98,14 +91,6 @@ private void writeEnvFile(Path target, List entries) { EnvEntry entry = entries.get(i); - if (entry.webbrowserTestUserThumbprintVariableName != null && entry.webbrowserTestUserThumbprint != null) - { - builder.append(entry.webbrowserTestUserThumbprintVariableName); - builder.append('='); - builder.append(entry.webbrowserTestUserThumbprint); - builder.append('\n'); - } - builder.append(entry.userThumbprintVariableName); builder.append('='); builder.append(entry.userThumbprint); From 74557031e5b43213b204a71dc21280e3e70312b2 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 9 Aug 2023 11:22:21 +0200 Subject: [PATCH 07/39] reverted accidental eclipse lifecycle mapping version change --- dsf-fhir/dsf-fhir-validation/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-validation/pom.xml b/dsf-fhir/dsf-fhir-validation/pom.xml index 37e3c2185..0abaa0a61 100644 --- a/dsf-fhir/dsf-fhir-validation/pom.xml +++ b/dsf-fhir/dsf-fhir-validation/pom.xml @@ -105,7 +105,7 @@ org.eclipse.m2e lifecycle-mapping - 1.1.0-SNAPSHOT + 1.0.0 From c13ceb5eb1c4c11623dac0a0a13fdada5343459b Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 9 Aug 2023 11:23:26 +0200 Subject: [PATCH 08/39] fixes #61 --- .../java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java index 98dd66da3..c6171ef3d 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java @@ -72,7 +72,7 @@ public Optional getOrganization(Identifier organizationIdentifier) String organizationIdSp = toSearchParameter(organizationIdentifier); - Bundle resultBundle = clientProvider.getLocalWebserviceClient().searchWithStrictHandling(Endpoint.class, + Bundle resultBundle = clientProvider.getLocalWebserviceClient().searchWithStrictHandling(Organization.class, Map.of("status", Collections.singletonList("active"), "identifier", Collections.singletonList(organizationIdSp))); From 8ad42553679f995e002f5ecf603d9e61903d6fe9 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 9 Aug 2023 13:04:59 +0200 Subject: [PATCH 09/39] adds support for AND queries, adds Group.identifier search parameter also adds minimal group integration test demonstrating search with multiple identifiers --- .../AbstractAuthorizationRule.java | 4 +- .../EndpointAuthorizationRule.java | 4 +- ...anizationAffiliationAuthorizationRule.java | 2 +- .../SubscriptionAuthorizationRule.java | 4 +- .../java/dev/dsf/fhir/dao/HistoryDao.java | 6 +- .../dsf/fhir/dao/command/CreateCommand.java | 3 +- .../dsf/fhir/dao/command/DeleteCommand.java | 3 +- .../dev/dsf/fhir/dao/command/ReadCommand.java | 2 +- .../dsf/fhir/dao/command/UpdateCommand.java | 2 +- .../dao/jdbc/AbstractResourceDaoJdbc.java | 97 +++-- .../AbstractStructureDefinitionDaoJdbc.java | 35 +- .../dao/jdbc/ActivityDefinitionDaoJdbc.java | 15 +- .../dev/dsf/fhir/dao/jdbc/BinaryDaoJdbc.java | 6 +- .../dev/dsf/fhir/dao/jdbc/BundleDaoJdbc.java | 6 +- .../dsf/fhir/dao/jdbc/CodeSystemDaoJdbc.java | 15 +- .../dao/jdbc/DocumentReferenceDaoJdbc.java | 7 +- .../dsf/fhir/dao/jdbc/EndpointDaoJdbc.java | 17 +- .../dev/dsf/fhir/dao/jdbc/GroupDaoJdbc.java | 9 +- .../dao/jdbc/HealthcareServiceDaoJdbc.java | 8 +- .../dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java | 56 +-- .../dev/dsf/fhir/dao/jdbc/LibraryDaoJdbc.java | 14 +- .../dsf/fhir/dao/jdbc/LocationDaoJdbc.java | 7 +- .../dev/dsf/fhir/dao/jdbc/MeasureDaoJdbc.java | 16 +- .../fhir/dao/jdbc/MeasureReportDaoJdbc.java | 7 +- .../fhir/dao/jdbc/NamingSystemDaoJdbc.java | 9 +- .../jdbc/OrganizationAffiliationDaoJdbc.java | 26 +- .../fhir/dao/jdbc/OrganizationDaoJdbc.java | 23 +- .../dev/dsf/fhir/dao/jdbc/PatientDaoJdbc.java | 9 +- .../fhir/dao/jdbc/PractitionerDaoJdbc.java | 8 +- .../dao/jdbc/PractitionerRoleDaoJdbc.java | 16 +- .../dsf/fhir/dao/jdbc/ProvenanceDaoJdbc.java | 4 +- .../fhir/dao/jdbc/QuestionnaireDaoJdbc.java | 15 +- .../jdbc/QuestionnaireResponseDaoJdbc.java | 21 +- .../fhir/dao/jdbc/ResearchStudyDaoJdbc.java | 18 +- .../fhir/dao/jdbc/SubscriptionDaoJdbc.java | 15 +- .../dev/dsf/fhir/dao/jdbc/TaskDaoJdbc.java | 12 +- .../dsf/fhir/dao/jdbc/ValueSetDaoJdbc.java | 14 +- .../dev/dsf/fhir/history/AtParameter.java | 4 +- .../dsf/fhir/history/HistoryServiceImpl.java | 46 +- .../dev/dsf/fhir/history/SinceParameter.java | 47 +- .../java/dev/dsf/fhir/search/SearchQuery.java | 402 +++++++++++------- .../search/SearchQueryIncludeParameter.java | 55 +-- ...rchQueryIncludeParameterConfiguration.java | 57 +++ .../dsf/fhir/search/SearchQueryParameter.java | 39 +- .../search/SearchQueryParameterError.java | 37 +- .../search/SearchQueryParameterFactory.java | 103 +++++ .../SearchQueryRevIncludeParameter.java | 16 + ...SearchQueryRevIncludeParameterFactory.java | 43 +- ...earchQuerySortParameterConfiguration.java} | 4 +- .../parameters/ActivityDefinitionStatus.java | 6 +- .../parameters/ActivityDefinitionVersion.java | 2 +- .../search/parameters/BinaryContentType.java | 17 +- .../search/parameters/BundleIdentifier.java | 13 +- .../search/parameters/CodeSystemStatus.java | 6 +- .../search/parameters/CodeSystemVersion.java | 2 +- .../DocumentReferenceIdentifier.java | 38 +- .../parameters/EndpointOrganization.java | 13 +- .../search/parameters/EndpointStatus.java | 25 +- .../search/parameters/GroupIdentifier.java | 33 ++ .../fhir/search/parameters/LibraryStatus.java | 6 +- .../search/parameters/LibraryVersion.java | 2 +- .../search/parameters/MeasureDependsOn.java | 13 +- .../fhir/search/parameters/MeasureStatus.java | 6 +- .../search/parameters/MeasureVersion.java | 2 +- .../search/parameters/NamingSystemStatus.java | 6 +- .../OrganizationAffiliationEndpoint.java | 13 +- ...nAffiliationParticipatingOrganization.java | 13 +- ...izationAffiliationPrimaryOrganization.java | 13 +- .../parameters/OrganizationEndpoint.java | 13 +- .../PractitionerRoleOrganization.java | 13 +- .../PractitionerRolePractitioner.java | 13 +- .../QuestionnaireResponseQuestionnaire.java | 13 +- .../QuestionnaireResponseStatus.java | 26 +- .../QuestionnaireResponseSubject.java | 11 +- .../parameters/QuestionnaireStatus.java | 6 +- .../parameters/QuestionnaireVersion.java | 2 +- .../parameters/ResearchStudyEnrollment.java | 13 +- .../ResearchStudyPrincipalInvestigator.java | 13 +- .../fhir/search/parameters/ResourceId.java | 40 +- .../parameters/ResourceLastUpdated.java | 4 +- .../search/parameters/ResourceProfile.java | 3 +- .../parameters/SearchQuerySortParameter.java | 19 + .../parameters/StructureDefinitionStatus.java | 2 +- .../StructureDefinitionVersion.java | 2 +- .../parameters/SubscriptionPayload.java | 17 +- .../search/parameters/SubscriptionStatus.java | 25 +- .../search/parameters/SubscriptionType.java | 25 +- .../fhir/search/parameters/TaskRequester.java | 13 +- .../fhir/search/parameters/TaskStatus.java | 25 +- .../search/parameters/ValueSetStatus.java | 6 +- .../search/parameters/ValueSetVersion.java | 2 +- .../basic/AbstractBooleanParameter.java | 33 +- .../AbstractCanonicalReferenceParameter.java | 48 +-- .../basic/AbstractCanonicalUrlParameter.java | 75 ++-- .../basic/AbstractDateTimeParameter.java | 196 ++++----- .../basic/AbstractReferenceParameter.java | 335 +++++++-------- .../basic/AbstractSearchParameter.java | 97 +---- .../basic/AbstractStatusParameter.java | 20 +- .../basic/AbstractStringParameter.java | 64 +-- .../basic/AbstractTokenParameter.java | 99 ++--- .../basic/AbstractVersionParameter.java | 10 +- .../basic/TokenValueAndSearchType.java | 62 ++- .../include/AbstractRevIncludeParameter.java | 47 ++ .../AbstractRevIncludeParameterFactory.java | 104 ----- .../EndpointOrganizationRevInclude.java | 15 +- ...onParticipatingOrganizationRevInclude.java | 15 +- ...iliationPrimaryOrganizationRevInclude.java | 15 +- .../OrganizationEndpointRevInclude.java | 15 +- .../ResearchStudyEnrollmentRevInclude.java | 15 +- .../fhir/service/ReferenceResolverImpl.java | 6 +- .../impl/AbstractResourceServiceImpl.java | 5 +- .../impl/ConformanceServiceImpl.java | 7 +- .../secure/AbstractResourceServiceSecure.java | 6 +- .../java/dev/dsf/fhir/dao/HistoryDaoTest.java | 10 +- .../integration/GroupIntegrationTest.java | 104 +++++ 115 files changed, 1936 insertions(+), 1390 deletions(-) mode change 100755 => 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameter.java create mode 100755 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameterConfiguration.java create mode 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterFactory.java create mode 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameter.java rename dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/{SearchQuerySortParameter.java => SearchQuerySortParameterConfiguration.java} (87%) create mode 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/GroupIdentifier.java create mode 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SearchQuerySortParameter.java create mode 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameter.java delete mode 100644 dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameterFactory.java create mode 100644 dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/GroupIntegrationTest.java diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java index 81e3d0dd1..cc64a198c 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java @@ -182,7 +182,7 @@ protected final boolean organizationWithIdentifierExists(Connection connection, SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0) .configureParameters(queryParameters); - List uQp = query.getUnsupportedQueryParameters(queryParameters); + List uQp = query.getUnsupportedQueryParameters(); if (!uQp.isEmpty()) { logger.warn("Unsupported query parameters {} while searching for Organization", uQp); @@ -213,7 +213,7 @@ protected final boolean roleExists(Connection connection, Coding coding) SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1) .configureParameters(queryParameters); - List uQp = query.getUnsupportedQueryParameters(queryParameters); + List uQp = query.getUnsupportedQueryParameters(); if (!uQp.isEmpty()) { logger.warn("Unsupported query parameters {} while searching for CodeSystem", uQp); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java index 86d2d40e3..f8396f6b6 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java @@ -111,7 +111,7 @@ private boolean endpointWithAddressExists(Connection connection, String address) EndpointDao dao = getDao(); SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0).configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters(queryParameters).isEmpty()) + if (!query.getUnsupportedQueryParameters().isEmpty()) return false; try @@ -133,7 +133,7 @@ private boolean endpointWithIdentifierExists(Connection connection, String ident EndpointDao dao = getDao(); SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0).configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters(queryParameters).isEmpty()) + if (!query.getUnsupportedQueryParameters().isEmpty()) return false; try diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java index 0402bb4bf..615469329 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java @@ -106,7 +106,7 @@ private boolean organizationAffiliationWithParentAndMemberExists(Connection conn SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0) .configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters(queryParameters).isEmpty()) + if (!query.getUnsupportedQueryParameters().isEmpty()) return false; try diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java index a98368630..0481cfb73 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java @@ -89,7 +89,7 @@ private Optional newResourceOk(Connection connection, Identity identity, { SearchQuery searchQuery = optDao.get().createSearchQueryWithoutUserFilter(1, 1); List unsupportedQueryParameters = searchQuery - .getUnsupportedQueryParameters(cComponentes.getQueryParams()); + .getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) { @@ -136,7 +136,7 @@ protected boolean resourceExists(Connection connection, Subscription newResource SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1) .configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters(queryParameters).isEmpty()) + if (!query.getUnsupportedQueryParameters().isEmpty()) return false; try diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/HistoryDao.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/HistoryDao.java index 4e54386c1..e9abfcadc 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/HistoryDao.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/HistoryDao.java @@ -14,12 +14,12 @@ public interface HistoryDao { - History readHistory(List filters, PageAndCount pageAndCount, AtParameter atParameter, + History readHistory(List filters, PageAndCount pageAndCount, List atParameters, SinceParameter sinceParameter) throws SQLException; - History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, AtParameter atParameter, + History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, List atParameters, SinceParameter sinceParameter, Class resource) throws SQLException; - History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, AtParameter atParameter, + History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, List atParameters, SinceParameter sinceParameter, Class resource, UUID id) throws SQLException; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java index 1311a95be..ea5727c3f 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java @@ -202,8 +202,7 @@ private Optional checkAlreadyExists(Connection connection, String ifNo SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) throw new WebApplicationException( responseGenerator.badIfNoneExistHeaderValue(ifNoneExist, unsupportedQueryParameters)); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java index ff08b136a..3fcaf9f28 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java @@ -175,8 +175,7 @@ private Optional search(Connection connection, ResourceDao dao, SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) throw new WebApplicationException(responseGenerator.badConditionalDeleteRequest(index, UriComponentsBuilder.newInstance() diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java index ff7348a18..beb4aba6b 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java @@ -202,7 +202,7 @@ private void readByCondition(Connection connection, String resourceTypeName, SearchQuery query = optDao.get().createSearchQuery(identity, effectivePage, effectiveCount); query.configureParameters(cleanQueryParameters); - List errors = query.getUnsupportedQueryParameters(cleanQueryParameters); + List errors = query.getUnsupportedQueryParameters(); if (!errors.isEmpty() && PreferHandlingType.STRICT.equals(handlingType)) throw new WebApplicationException(responseGenerator.response(Status.BAD_REQUEST, diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java index d8fc7e56a..d4ed1d7b7 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java @@ -154,7 +154,7 @@ private boolean addMissingIdToTranslationTableAndCheckConditionFindsResource(Map SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1); query.configureParameters(queryParameters); - List unsupportedParams = query.getUnsupportedQueryParameters(queryParameters); + List unsupportedParams = query.getUnsupportedQueryParameters(); if (!unsupportedParams.isEmpty()) throw new WebApplicationException(responseGenerator.unsupportedConditionalUpdateQuery(index, entry.getRequest().getUrl(), unsupportedParams)); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractResourceDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractResourceDaoJdbc.java index a865c0eb9..76ba5c77f 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractResourceDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractResourceDaoJdbc.java @@ -8,7 +8,6 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -46,7 +45,10 @@ import dev.dsf.fhir.search.SearchQuery; import dev.dsf.fhir.search.SearchQuery.SearchQueryBuilder; import dev.dsf.fhir.search.SearchQueryIdentityFilter; +import dev.dsf.fhir.search.SearchQueryIncludeParameter; import dev.dsf.fhir.search.SearchQueryParameter; +import dev.dsf.fhir.search.SearchQueryParameterFactory; +import dev.dsf.fhir.search.SearchQueryRevIncludeParameter; import dev.dsf.fhir.search.SearchQueryRevIncludeParameterFactory; import dev.dsf.fhir.search.parameters.ResourceId; import dev.dsf.fhir.search.parameters.ResourceLastUpdated; @@ -56,6 +58,15 @@ abstract class AbstractResourceDaoJdbc implements ResourceDa { private static final Logger logger = LoggerFactory.getLogger(AbstractResourceDaoJdbc.class); + protected static SearchQueryParameterFactory factory(String parameterName, + Supplier> supplier) + { + Objects.requireNonNull(parameterName, "parameterName"); + Objects.requireNonNull(supplier, "supplier"); + + return new SearchQueryParameterFactory<>(parameterName, supplier, null, null, null); + } + private static final class ResourceDistinctById { private final IdType id; @@ -112,25 +123,52 @@ public Resource getResource() private final String resourceIdColumn; private final PreparedStatementFactory preparedStatementFactory; - private final Function userFilter; - private final List>> searchParameterFactories = new ArrayList<>(); - private final List> searchRevIncludeParameterFactories = new ArrayList<>(); + private final Function identityFilter; + private final List> searchParameterFactories = new ArrayList<>(); + private final List searchRevIncludeParameterFactories = new ArrayList<>(); + + private final SearchQueryParameterFactory resourceIdFactory; + private final SearchQueryParameterFactory resourceLastUpdatedFactory; + private final SearchQueryParameterFactory resourceProfileFactory; - @SafeVarargs - protected static List with(T... t) + protected static SearchQueryRevIncludeParameterFactory factory( + Supplier revIncludeSupplier, List revIncludeParameterValues) { - return Arrays.asList(t); + Objects.requireNonNull(revIncludeSupplier, "revIncludeSupplier"); + Objects.requireNonNull(revIncludeParameterValues, "revIncludeParameterValues"); + + return new SearchQueryRevIncludeParameterFactory(revIncludeSupplier, revIncludeParameterValues); + } + + protected static SearchQueryParameterFactory factory(String parameterName, + Supplier> supplier, List nameModifiers, + Supplier> includeSupplier, List includeParameterValues) + { + Objects.requireNonNull(parameterName, "parameterName"); + Objects.requireNonNull(supplier, "supplier"); + Objects.requireNonNull(nameModifiers, "nameModifiers"); + Objects.requireNonNull(includeSupplier, "includeSupplier"); + Objects.requireNonNull(includeParameterValues, "includeParameterValues"); + + return new SearchQueryParameterFactory<>(parameterName, supplier, nameModifiers, includeSupplier, + includeParameterValues); + } + + protected static SearchQueryParameterFactory factory(String parameterName, + Supplier> supplier, List nameModifiers) + { + Objects.requireNonNull(parameterName, "parameterName"); + Objects.requireNonNull(supplier, "supplier"); + Objects.requireNonNull(nameModifiers, "nameModifiers"); + + return new SearchQueryParameterFactory<>(parameterName, supplier, nameModifiers, null, null); } - /* - * Using a suppliers for SearchParameters, implementations are not thread safe and because of that they need to be - * created on a request basis - */ AbstractResourceDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext, Class resourceType, String resourceTable, String resourceColumn, String resourceIdColumn, Function userFilter, - List>> searchParameterFactories, - List> searchRevIncludeParameterFactories) + List> searchParameterFactories, + List searchRevIncludeParameterFactories) { this(dataSource, permanentDeleteDataSource, fhirContext, resourceType, resourceTable, resourceColumn, resourceIdColumn, new PreparedStatementFactoryDefault<>(fhirContext, resourceType, resourceTable, @@ -138,16 +176,12 @@ protected static List with(T... t) userFilter, searchParameterFactories, searchRevIncludeParameterFactories); } - /* - * Using a suppliers for SearchParameters, implementations are not thread safe and because of that they need to be - * created on a request basis - */ AbstractResourceDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext, Class resourceType, String resourceTable, String resourceColumn, String resourceIdColumn, PreparedStatementFactory preparedStatementFactory, Function userFilter, - List>> searchParameterFactories, - List> searchRevIncludeParameterFactories) + List> searchParameterFactories, + List searchRevIncludeParameterFactories) { this.dataSource = dataSource; this.permanentDeleteDataSource = permanentDeleteDataSource; @@ -160,12 +194,19 @@ protected static List with(T... t) this.preparedStatementFactory = preparedStatementFactory; - this.userFilter = userFilter; + this.identityFilter = userFilter; if (searchParameterFactories != null) this.searchParameterFactories.addAll(searchParameterFactories); if (searchRevIncludeParameterFactories != null) this.searchRevIncludeParameterFactories.addAll(searchRevIncludeParameterFactories); + + resourceIdFactory = new SearchQueryParameterFactory<>(ResourceId.PARAMETER_NAME, + () -> new ResourceId<>(resourceIdColumn), null, null, null); + resourceLastUpdatedFactory = new SearchQueryParameterFactory<>(ResourceLastUpdated.PARAMETER_NAME, + () -> new ResourceLastUpdated<>(resourceColumn), null, null, null); + resourceProfileFactory = new SearchQueryParameterFactory<>(ResourceProfile.PARAMETER_NAME, + () -> new ResourceProfile<>(resourceColumn), ResourceProfile.getNameModifiers(), null, null); } @Override @@ -178,7 +219,7 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(resourceColumn, "resourceColumn"); Objects.requireNonNull(resourceIdColumn, "resourceIdColumn"); Objects.requireNonNull(preparedStatementFactory, "preparedStatementFactory"); - Objects.requireNonNull(userFilter, "userFilter"); + Objects.requireNonNull(identityFilter, "userFilter"); } protected DataSource getDataSource() @@ -877,21 +918,15 @@ public SearchQuery createSearchQueryWithoutUserFilter(int page, int count) return doCreateSearchQuery(null, page, count); } - @SuppressWarnings({ "unchecked", "rawtypes" }) private SearchQuery doCreateSearchQuery(Identity identity, int page, int count) { var builder = SearchQueryBuilder.create(resourceType, getResourceTable(), getResourceColumn(), page, count); if (identity != null) - builder = builder.with(userFilter.apply(identity)); - - return builder - .with(new ResourceId(getResourceIdColumn()), new ResourceLastUpdated(getResourceColumn()), - new ResourceProfile(getResourceColumn())) - .with(searchParameterFactories.stream().map(Supplier::get).toArray(SearchQueryParameter[]::new)) - .withRevInclude(searchRevIncludeParameterFactories.stream().map(Supplier::get) - .toArray(SearchQueryRevIncludeParameterFactory[]::new)) - .build(); + builder = builder.with(identityFilter.apply(identity)); + + return builder.with(resourceIdFactory).with(resourceLastUpdatedFactory).with(resourceProfileFactory) + .with(searchParameterFactories).withRevInclude(searchRevIncludeParameterFactories).build(); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractStructureDefinitionDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractStructureDefinitionDaoJdbc.java index d8fec89c0..8c20008bc 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractStructureDefinitionDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractStructureDefinitionDaoJdbc.java @@ -2,17 +2,23 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.function.Function; import javax.sql.DataSource; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StructureDefinition; import ca.uhn.fhir.context.FhirContext; import dev.dsf.common.auth.conf.Identity; import dev.dsf.fhir.dao.StructureDefinitionDao; import dev.dsf.fhir.search.SearchQueryIdentityFilter; +import dev.dsf.fhir.search.SearchQueryParameter; +import dev.dsf.fhir.search.SearchQueryParameterFactory; import dev.dsf.fhir.search.parameters.StructureDefinitionDate; import dev.dsf.fhir.search.parameters.StructureDefinitionIdentifier; import dev.dsf.fhir.search.parameters.StructureDefinitionStatus; @@ -22,6 +28,18 @@ abstract class AbstractStructureDefinitionDaoJdbc extends AbstractResourceDaoJdbc implements StructureDefinitionDao { + private static SearchQueryParameterFactory factory(String resourceColumn, + String parameterName, Function> supplier, List nameModifiers) + { + return factory(parameterName, () -> supplier.apply(resourceColumn), nameModifiers); + } + + private static SearchQueryParameterFactory factory(String resourceColumn, + String parameterName, Function> supplier) + { + return factory(parameterName, () -> supplier.apply(resourceColumn)); + } + private final ReadByUrlDaoJdbc readByUrl; public AbstractStructureDefinitionDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, @@ -30,12 +48,17 @@ public AbstractStructureDefinitionDaoJdbc(DataSource dataSource, DataSource perm { super(dataSource, permanentDeleteDataSource, fhirContext, StructureDefinition.class, resourceTable, resourceColumn, resourceIdColumn, userFilter, - with(() -> new StructureDefinitionDate(resourceColumn), - () -> new StructureDefinitionIdentifier(resourceColumn), - () -> new StructureDefinitionStatus(resourceColumn), - () -> new StructureDefinitionUrl(resourceColumn), - () -> new StructureDefinitionVersion(resourceColumn)), - with()); + Arrays.asList( + factory(resourceColumn, StructureDefinitionDate.PARAMETER_NAME, StructureDefinitionDate::new), + factory(resourceColumn, StructureDefinitionIdentifier.PARAMETER_NAME, + StructureDefinitionIdentifier::new, StructureDefinitionIdentifier.getNameModifiers()), + factory(resourceColumn, StructureDefinitionStatus.PARAMETER_NAME, + StructureDefinitionStatus::new, StructureDefinitionStatus.getNameModifiers()), + factory(resourceColumn, StructureDefinitionUrl.PARAMETER_NAME, StructureDefinitionUrl::new, + StructureDefinitionUrl.getNameModifiers()), + factory(resourceColumn, StructureDefinitionVersion.PARAMETER_NAME, + StructureDefinitionVersion::new, StructureDefinitionVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc(this::getDataSource, this::getResource, resourceTable, resourceColumn); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ActivityDefinitionDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ActivityDefinitionDaoJdbc.java index df4d486d4..270bd9037 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ActivityDefinitionDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ActivityDefinitionDaoJdbc.java @@ -5,6 +5,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -37,9 +39,16 @@ public ActivityDefinitionDaoJdbc(DataSource dataSource, DataSource permanentDele { super(dataSource, permanentDeleteDataSource, fhirContext, ActivityDefinition.class, "activity_definitions", "activity_definition", "activity_definition_id", ActivityDefinitionIdentityFilter::new, - with(ActivityDefinitionDate::new, ActivityDefinitionIdentifier::new, ActivityDefinitionName::new, - ActivityDefinitionStatus::new, ActivityDefinitionUrl::new, ActivityDefinitionVersion::new), - with()); + Arrays.asList(factory(ActivityDefinitionDate.PARAMETER_NAME, ActivityDefinitionDate::new), + factory(ActivityDefinitionIdentifier.PARAMETER_NAME, ActivityDefinitionIdentifier::new), + factory(ActivityDefinitionName.PARAMETER_NAME, ActivityDefinitionName::new, + ActivityDefinitionName.getNameModifiers()), + factory(ActivityDefinitionStatus.PARAMETER_NAME, ActivityDefinitionStatus::new, + ActivityDefinitionStatus.getNameModifiers()), + factory(ActivityDefinitionUrl.PARAMETER_NAME, ActivityDefinitionUrl::new), + factory(ActivityDefinitionVersion.PARAMETER_NAME, ActivityDefinitionVersion::new, + ActivityDefinitionVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc(this::getDataSource, this::getResource, getResourceTable(), getResourceColumn()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BinaryDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BinaryDaoJdbc.java index 7f62d5a2f..f81ab2d7a 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BinaryDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BinaryDaoJdbc.java @@ -4,6 +4,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import javax.sql.DataSource; @@ -22,7 +24,9 @@ public BinaryDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource { super(dataSource, permanentDeleteDataSource, fhirContext, Binary.class, "binaries", "binary_json", "binary_id", new PreparedStatementFactoryBinary(fhirContext), BinaryIdentityFilter::new, - with(BinaryContentType::new), with()); + Arrays.asList(factory(BinaryContentType.PARAMETER_NAME, BinaryContentType::new, + BinaryContentType.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BundleDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BundleDaoJdbc.java index 1ee793821..e2b5f0607 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BundleDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/BundleDaoJdbc.java @@ -2,6 +2,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import javax.sql.DataSource; @@ -18,7 +20,9 @@ public class BundleDaoJdbc extends AbstractResourceDaoJdbc implements Bu public BundleDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Bundle.class, "bundles", "bundle", "bundle_id", - BundleIdentityFilter::new, with(BundleIdentifier::new), with()); + BundleIdentityFilter::new, Arrays.asList(factory(BundleIdentifier.PARAMETER_NAME, BundleIdentifier::new, + BundleIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/CodeSystemDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/CodeSystemDaoJdbc.java index b61796bbd..c9f0b8549 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/CodeSystemDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/CodeSystemDaoJdbc.java @@ -2,6 +2,8 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import java.util.Optional; import javax.sql.DataSource; @@ -24,9 +26,16 @@ public class CodeSystemDaoJdbc extends AbstractResourceDaoJdbc imple public CodeSystemDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, CodeSystem.class, "code_systems", "code_system", - "code_system_id", CodeSystemIdentityFilter::new, with(CodeSystemDate::new, CodeSystemIdentifier::new, - CodeSystemStatus::new, CodeSystemUrl::new, CodeSystemVersion::new), - with()); + "code_system_id", CodeSystemIdentityFilter::new, + Arrays.asList(factory(CodeSystemDate.PARAMETER_NAME, CodeSystemDate::new), + factory(CodeSystemIdentifier.PARAMETER_NAME, CodeSystemIdentifier::new, + CodeSystemIdentifier.getNameModifiers()), + factory(CodeSystemStatus.PARAMETER_NAME, CodeSystemStatus::new, + CodeSystemStatus.getNameModifiers()), + factory(CodeSystemUrl.PARAMETER_NAME, CodeSystemUrl::new, CodeSystemUrl.getNameModifiers()), + factory(CodeSystemVersion.PARAMETER_NAME, CodeSystemVersion::new, + CodeSystemVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(), getResourceColumn()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/DocumentReferenceDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/DocumentReferenceDaoJdbc.java index 7eaf3eef7..85a32506e 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/DocumentReferenceDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/DocumentReferenceDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.DocumentReference; @@ -16,7 +19,9 @@ public DocumentReferenceDaoJdbc(DataSource dataSource, DataSource permanentDelet { super(dataSource, permanentDeleteDataSource, fhirContext, DocumentReference.class, "document_references", "document_reference", "document_reference_id", DocumentReferenceIdentityFilter::new, - with(DocumentReferenceIdentifier::new), with()); + Arrays.asList(factory(DocumentReferenceIdentifier.PARAMETER_NAME, DocumentReferenceIdentifier::new, + DocumentReferenceIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java index 56ad1bb37..c138f6a17 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java @@ -4,6 +4,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; import javax.sql.DataSource; @@ -28,9 +29,19 @@ public class EndpointDaoJdbc extends AbstractResourceDaoJdbc implement public EndpointDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Endpoint.class, "endpoints", "endpoint", - "endpoint_id", EndpointIdentityFilter::new, with(EndpointAddress::new, EndpointIdentifier::new, - EndpointName::new, EndpointOrganization::new, EndpointStatus::new), - with(OrganizationEndpointRevInclude::new)); + "endpoint_id", EndpointIdentityFilter::new, + Arrays.asList( + factory(EndpointAddress.PARAMETER_NAME, EndpointAddress::new, + EndpointAddress.getNameModifiers()), + factory(EndpointIdentifier.PARAMETER_NAME, EndpointIdentifier::new, + EndpointIdentifier.getNameModifiers()), + factory(EndpointName.PARAMETER_NAME, EndpointName::new, EndpointName.getNameModifiers()), + factory(EndpointOrganization.PARAMETER_NAME, EndpointOrganization::new, + EndpointOrganization.getNameModifiers(), EndpointOrganization::new, + EndpointOrganization.getIncludeParameterValues()), + factory(EndpointStatus.PARAMETER_NAME, EndpointStatus::new, EndpointStatus.getNameModifiers())), + Arrays.asList(factory(OrganizationEndpointRevInclude::new, + OrganizationEndpointRevInclude.getRevIncludeParameterValues()))); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/GroupDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/GroupDaoJdbc.java index 1412ba61c..817adff0e 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/GroupDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/GroupDaoJdbc.java @@ -1,5 +1,7 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.Group; @@ -7,6 +9,7 @@ import ca.uhn.fhir.context.FhirContext; import dev.dsf.fhir.dao.GroupDao; import dev.dsf.fhir.search.filter.GroupIdentityFilter; +import dev.dsf.fhir.search.parameters.GroupIdentifier; import dev.dsf.fhir.search.parameters.rev.include.ResearchStudyEnrollmentRevInclude; public class GroupDaoJdbc extends AbstractResourceDaoJdbc implements GroupDao @@ -14,7 +17,11 @@ public class GroupDaoJdbc extends AbstractResourceDaoJdbc implements Grou public GroupDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Group.class, "groups", "group_json", "group_id", - GroupIdentityFilter::new, with(), with(ResearchStudyEnrollmentRevInclude::new)); + GroupIdentityFilter::new, + Arrays.asList(factory(GroupIdentifier.PARAMETER_NAME, GroupIdentifier::new, + GroupIdentifier.getNameModifiers())), + Arrays.asList(factory(ResearchStudyEnrollmentRevInclude::new, + ResearchStudyEnrollmentRevInclude.getRevIncludeParameterValues()))); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HealthcareServiceDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HealthcareServiceDaoJdbc.java index 65b195aad..691cda89b 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HealthcareServiceDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HealthcareServiceDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.HealthcareService; @@ -17,7 +20,10 @@ public HealthcareServiceDaoJdbc(DataSource dataSource, DataSource permanentDelet { super(dataSource, permanentDeleteDataSource, fhirContext, HealthcareService.class, "healthcare_services", "healthcare_service", "healthcare_service_id", HealthcareServiceIdentityFilter::new, - with(HealthcareServiceActive::new, HealthcareServiceIdentifier::new), with()); + Arrays.asList(factory(HealthcareServiceActive.PARAMETER_NAME, HealthcareServiceActive::new), + factory(HealthcareServiceIdentifier.PARAMETER_NAME, HealthcareServiceIdentifier::new, + HealthcareServiceIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java index a9ca54223..799912651 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java @@ -61,55 +61,56 @@ public void afterPropertiesSet() throws Exception } @Override - public History readHistory(List filters, PageAndCount pageAndCount, AtParameter atParameter, - SinceParameter sinceParameter) throws SQLException + public History readHistory(List filters, PageAndCount pageAndCount, + List atParameters, SinceParameter sinceParameter) throws SQLException { Objects.requireNonNull(filters, "filters"); Objects.requireNonNull(pageAndCount, "pageAndCount"); - Objects.requireNonNull(atParameter, "atParameter"); + Objects.requireNonNull(atParameters, "atParameters"); Objects.requireNonNull(sinceParameter, "sinceParameter"); - return readHistory(filters, pageAndCount, atParameter, sinceParameter, null, null); + return readHistory(filters, pageAndCount, atParameters, sinceParameter, null, null); } @Override - public History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, AtParameter atParameter, + public History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, List atParameters, SinceParameter sinceParameter, Class resource) throws SQLException { Objects.requireNonNull(filter, "filter"); Objects.requireNonNull(pageAndCount, "pageAndCount"); - Objects.requireNonNull(atParameter, "atParameter"); + Objects.requireNonNull(atParameters, "atParameters"); Objects.requireNonNull(sinceParameter, "sinceParameter"); Objects.requireNonNull(resource, "resource"); - return readHistory(Collections.singletonList(filter), pageAndCount, atParameter, sinceParameter, resource, + return readHistory(Collections.singletonList(filter), pageAndCount, atParameters, sinceParameter, resource, null); } @Override - public History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, AtParameter atParameter, + public History readHistory(HistoryIdentityFilter filter, PageAndCount pageAndCount, List atParameters, SinceParameter sinceParameter, Class resource, UUID id) throws SQLException { Objects.requireNonNull(filter, "filter"); Objects.requireNonNull(pageAndCount, "pageAndCount"); - Objects.requireNonNull(atParameter, "atParameter"); + Objects.requireNonNull(atParameters, "atParameters"); Objects.requireNonNull(sinceParameter, "sinceParameter"); Objects.requireNonNull(resource, "resource"); Objects.requireNonNull(id, "id"); - return readHistory(Collections.singletonList(filter), pageAndCount, atParameter, sinceParameter, resource, id); + return readHistory(Collections.singletonList(filter), pageAndCount, atParameters, sinceParameter, resource, id); } - private History readHistory(List filter, PageAndCount pageAndCount, AtParameter atParameter, - SinceParameter sinceParameter, Class resource, UUID id) throws SQLException + private History readHistory(List filter, PageAndCount pageAndCount, + List atParameters, SinceParameter sinceParameter, Class resource, UUID id) + throws SQLException { try (Connection connection = dataSource.getConnection()) { int total = 0; try (PreparedStatement statement = connection.prepareStatement( - createCountSql(id != null, resource != null, filter, atParameter, sinceParameter))) + createCountSql(id != null, resource != null, filter, atParameters, sinceParameter))) { - configureStatement(statement, id, resource, filter, atParameter, sinceParameter); + configureStatement(statement, id, resource, filter, atParameters, sinceParameter); logger.trace("Executing count query '{}'", statement); try (ResultSet result = statement.executeQuery()) @@ -122,10 +123,10 @@ private History readHistory(List filter, PageAndCount pag List entries = new ArrayList<>(); if (!pageAndCount.isCountOnly(total)) { - try (PreparedStatement statement = connection.prepareStatement( - createReadSql(id != null, resource != null, filter, atParameter, sinceParameter, pageAndCount))) + try (PreparedStatement statement = connection.prepareStatement(createReadSql(id != null, + resource != null, filter, atParameters, sinceParameter, pageAndCount))) { - configureStatement(statement, id, resource, filter, atParameter, sinceParameter); + configureStatement(statement, id, resource, filter, atParameters, sinceParameter); logger.trace("Executing read query '{}'", statement); try (ResultSet result = statement.executeQuery()) @@ -207,7 +208,7 @@ private Resource jsonToResource(String json, Class resourceT } private String createCountSql(boolean forId, boolean forResource, List filter, - AtParameter atParameter, SinceParameter sinceParameter) + List atParameter, SinceParameter sinceParameter) { String selectSql = "SELECT count(*) FROM history WHERE "; @@ -215,7 +216,7 @@ private String createCountSql(boolean forId, boolean forResource, List filter, - AtParameter atParameter, SinceParameter sinceParameter, PageAndCount pageAndCount) + List atParameter, SinceParameter sinceParameter, PageAndCount pageAndCount) { String selectSql = "SELECT id, version, type, method, last_updated, resource FROM history WHERE "; @@ -223,7 +224,7 @@ private String createReadSql(boolean forId, boolean forResource, List filter, - AtParameter atParameter, SinceParameter sinceParameter, String selectSql, String limitOffsetSql) + List atParameters, SinceParameter sinceParameter, String selectSql, String limitOffsetSql) { String idSql = forId ? "id = ?" : null; String typeSql = forResource ? "type = ?" : null; @@ -231,15 +232,15 @@ private String createSql(boolean forId, boolean forResource, List params = Stream.of(atParameter, sinceParameter).filter(SearchQueryParameter::isDefined) - .map(SearchQueryParameter::getFilterQuery); + Stream params = Stream.concat(atParameters.stream(), Stream.of(sinceParameter)) + .filter(SearchQueryParameter::isDefined).map(SearchQueryParameter::getFilterQuery); return Stream.concat(Stream.of(idSql, typeSql, filterSql).filter(s -> s != null), params) .collect(Collectors.joining(" AND ", selectSql, limitOffsetSql)); } private void configureStatement(PreparedStatement statement, UUID id, Class resource, - List filter, AtParameter atParameter, SinceParameter sinceParameter) + List filter, List atParameters, SinceParameter sinceParameter) throws SQLException { int parameterIndex = 1; @@ -257,10 +258,13 @@ private void configureStatement(PreparedStatement statement, UUID id, Class implements LibraryDao { @@ -24,9 +27,14 @@ public class LibraryDaoJdbc extends AbstractResourceDaoJdbc implements public LibraryDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Library.class, "libraries", "library", "library_id", - LibraryIdentityFilter::new, with(LibraryDate::new, LibraryIdentifier::new, LibraryStatus::new, - LibraryUrl::new, LibraryVersion::new), - with()); + LibraryIdentityFilter::new, + Arrays.asList(factory(LibraryDate.PARAMETER_NAME, LibraryDate::new), + factory(LibraryIdentifier.PARAMETER_NAME, LibraryIdentifier::new, + LocationIdentifier.getNameModifiers()), + factory(LibraryStatus.PARAMETER_NAME, LibraryStatus::new, LibraryStatus.getNameModifiers()), + factory(LibraryUrl.PARAMETER_NAME, LibraryUrl::new, LibraryUrl.getNameModifiers()), + factory(LibraryVersion.PARAMETER_NAME, LibraryVersion::new, LibraryVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(), getResourceColumn()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/LocationDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/LocationDaoJdbc.java index 8c5da23d1..a71237990 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/LocationDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/LocationDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.Location; @@ -14,7 +17,9 @@ public class LocationDaoJdbc extends AbstractResourceDaoJdbc implement public LocationDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Location.class, "locations", "location", - "location_id", LocationIdentityFilter::new, with(LocationIdentifier::new), with()); + "location_id", LocationIdentityFilter::new, Arrays.asList(factory(LocationIdentifier.PARAMETER_NAME, + LocationIdentifier::new, LocationIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureDaoJdbc.java index 21f9d9e1a..6bf40c814 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureDaoJdbc.java @@ -2,6 +2,8 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import java.util.Optional; import javax.sql.DataSource; @@ -25,9 +27,17 @@ public class MeasureDaoJdbc extends AbstractResourceDaoJdbc implements public MeasureDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Measure.class, "measures", "measure", "measure_id", - MeasureIdentityFilter::new, with(MeasureDate::new, MeasureDependsOn::new, MeasureIdentifier::new, - MeasureStatus::new, MeasureUrl::new, MeasureVersion::new), - with()); + MeasureIdentityFilter::new, + Arrays.asList(factory(MeasureDate.PARAMETER_NAME, MeasureDate::new), + factory(MeasureDependsOn.PARAMETER_NAME, MeasureDependsOn::new, + MeasureDependsOn.getNameModifiers(), MeasureDependsOn::new, + MeasureDependsOn.getIncludeParameterValues()), + factory(MeasureIdentifier.PARAMETER_NAME, MeasureIdentifier::new, + MeasureIdentifier.getNameModifiers()), + factory(MeasureStatus.PARAMETER_NAME, MeasureStatus::new, MeasureStatus.getNameModifiers()), + factory(MeasureUrl.PARAMETER_NAME, MeasureUrl::new, MeasureUrl.getNameModifiers()), + factory(MeasureVersion.PARAMETER_NAME, MeasureVersion::new, MeasureVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(), getResourceColumn()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureReportDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureReportDaoJdbc.java index 421f6e06d..3689851c0 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureReportDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/MeasureReportDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.MeasureReport; @@ -15,7 +18,9 @@ public MeasureReportDaoJdbc(DataSource dataSource, DataSource permanentDeleteDat { super(dataSource, permanentDeleteDataSource, fhirContext, MeasureReport.class, "measure_reports", "measure_report", "measure_report_id", MeasureReportIdentityFilter::new, - with(MeasureReportIdentifier::new), with()); + Arrays.asList(factory(MeasureReportIdentifier.PARAMETER_NAME, MeasureReportIdentifier::new, + MeasureReportIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/NamingSystemDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/NamingSystemDaoJdbc.java index ac6de30b2..83cba7d74 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/NamingSystemDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/NamingSystemDaoJdbc.java @@ -4,6 +4,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import java.util.Objects; import java.util.Optional; @@ -28,7 +30,12 @@ public NamingSystemDaoJdbc(DataSource dataSource, DataSource permanentDeleteData { super(dataSource, permanentDeleteDataSource, fhirContext, NamingSystem.class, "naming_systems", "naming_system", "naming_system_id", NamingSystemIdentityFilter::new, - with(NamingSystemDate::new, NamingSystemName::new, NamingSystemStatus::new), with()); + Arrays.asList(factory(NamingSystemDate.PARAMETER_NAME, NamingSystemDate::new), + factory(NamingSystemName.PARAMETER_NAME, NamingSystemName::new, + NamingSystemName.getNameModifiers()), + factory(NamingSystemStatus.PARAMETER_NAME, NamingSystemStatus::new, + NamingSystemStatus.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java index e7bf9d16e..199b57f74 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java @@ -5,6 +5,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -36,10 +37,27 @@ public OrganizationAffiliationDaoJdbc(DataSource dataSource, DataSource permanen super(dataSource, permanentDeleteDataSource, fhirContext, OrganizationAffiliation.class, "organization_affiliations", "organization_affiliation", "organization_affiliation_id", OrganizationAffiliationIdentityFilter::new, - with(OrganizationAffiliationActive::new, OrganizationAffiliationEndpoint::new, - OrganizationAffiliationIdentifier::new, OrganizationAffiliationParticipatingOrganization::new, - OrganizationAffiliationPrimaryOrganization::new, OrganizationAffiliationRole::new), - with()); + Arrays.asList(factory(OrganizationAffiliationActive.PARAMETER_NAME, OrganizationAffiliationActive::new), + factory(OrganizationAffiliationEndpoint.PARAMETER_NAME, OrganizationAffiliationEndpoint::new, + OrganizationAffiliationEndpoint.getNameModifiers(), + OrganizationAffiliationEndpoint::new, + OrganizationAffiliationEndpoint.getIncludeParameterValues()), + factory(OrganizationAffiliationIdentifier.PARAMETER_NAME, + OrganizationAffiliationIdentifier::new, + OrganizationAffiliationIdentifier.getNameModifiers()), + factory(OrganizationAffiliationParticipatingOrganization.PARAMETER_NAME, + OrganizationAffiliationParticipatingOrganization::new, + OrganizationAffiliationParticipatingOrganization.getNameModifiers(), + OrganizationAffiliationParticipatingOrganization::new, + OrganizationAffiliationParticipatingOrganization.getIncludeParameterValues()), + factory(OrganizationAffiliationPrimaryOrganization.PARAMETER_NAME, + OrganizationAffiliationPrimaryOrganization::new, + OrganizationAffiliationPrimaryOrganization.getNameModifiers(), + OrganizationAffiliationPrimaryOrganization::new, + OrganizationAffiliationPrimaryOrganization.getIncludeParameterValues()), + factory(OrganizationAffiliationRole.PARAMETER_NAME, OrganizationAffiliationRole::new, + OrganizationAffiliationRole.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java index f643d5471..4b58cf4cd 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java @@ -4,6 +4,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; import java.util.Optional; import javax.sql.DataSource; @@ -32,10 +33,24 @@ public OrganizationDaoJdbc(DataSource dataSource, DataSource permanentDeleteData { super(dataSource, permanentDeleteDataSource, fhirContext, Organization.class, "organizations", "organization", "organization_id", OrganizationIdentityFilter::new, - with(OrganizationActive::new, OrganizationEndpoint::new, OrganizationIdentifier::new, - OrganizationName::new, OrganizationType::new), - with(EndpointOrganizationRevInclude::new, OrganizationAffiliationPrimaryOrganizationRevInclude::new, - OrganizationAffiliationParticipatingOrganizationRevInclude::new)); + Arrays.asList(factory(OrganizationActive.PARAMETER_NAME, OrganizationActive::new), + factory(OrganizationEndpoint.PARAMETER_NAME, OrganizationEndpoint::new, + OrganizationEndpoint.getNameModifiers(), OrganizationEndpoint::new, + OrganizationEndpoint.getIncludeParameterValues()), + factory(OrganizationIdentifier.PARAMETER_NAME, OrganizationIdentifier::new, + OrganizationIdentifier.getNameModifiers()), + factory(OrganizationName.PARAMETER_NAME, OrganizationName::new, + OrganizationName.getNameModifiers()), + factory(OrganizationType.PARAMETER_NAME, OrganizationType::new, + OrganizationType.getNameModifiers())), + Arrays.asList( + factory(EndpointOrganizationRevInclude::new, + EndpointOrganizationRevInclude.getRevIncludeParameterValues()), + factory(OrganizationAffiliationPrimaryOrganizationRevInclude::new, + OrganizationAffiliationPrimaryOrganizationRevInclude.getRevIncludeParameterValues()), + factory(OrganizationAffiliationParticipatingOrganizationRevInclude::new, + OrganizationAffiliationParticipatingOrganizationRevInclude + .getRevIncludeParameterValues()))); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PatientDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PatientDaoJdbc.java index 97dc55ee9..c673ac4b3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PatientDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PatientDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.Patient; @@ -15,7 +18,11 @@ public class PatientDaoJdbc extends AbstractResourceDaoJdbc implements public PatientDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Patient.class, "patients", "patient", "patient_id", - PatientIdentityFilter::new, with(PatientActive::new, PatientIdentifier::new), with()); + PatientIdentityFilter::new, + Arrays.asList(factory(PatientActive.PARAMETER_NAME, PatientActive::new), + factory(PatientIdentifier.PARAMETER_NAME, PatientIdentifier::new, + PatientIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerDaoJdbc.java index eb313ec5e..479e6182c 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.Practitioner; @@ -16,7 +19,10 @@ public PractitionerDaoJdbc(DataSource dataSource, DataSource permanentDeleteData { super(dataSource, permanentDeleteDataSource, fhirContext, Practitioner.class, "practitioners", "practitioner", "practitioner_id", PractitionerIdentityFilter::new, - with(PractitionerActive::new, PractitionerIdentifier::new), with()); + Arrays.asList(factory(PractitionerActive.PARAMETER_NAME, PractitionerActive::new), + factory(PractitionerIdentifier.PARAMETER_NAME, PractitionerIdentifier::new, + PractitionerIdentifier.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerRoleDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerRoleDaoJdbc.java index 1c66f4969..e9e1590b6 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerRoleDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/PractitionerRoleDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.PractitionerRole; @@ -18,9 +21,16 @@ public PractitionerRoleDaoJdbc(DataSource dataSource, DataSource permanentDelete { super(dataSource, permanentDeleteDataSource, fhirContext, PractitionerRole.class, "practitioner_roles", "practitioner_role", "practitioner_role_id", PractitionerRoleIdentityFilter::new, - with(PractitionerRoleActive::new, PractitionerRoleIdentifier::new, PractitionerRoleOrganization::new, - PractitionerRolePractitioner::new), - with()); + Arrays.asList(factory(PractitionerRoleActive.PARAMETER_NAME, PractitionerRoleActive::new), + factory(PractitionerRoleIdentifier.PARAMETER_NAME, PractitionerRoleIdentifier::new, + PractitionerRoleIdentifier.getNameModifiers()), + factory(PractitionerRoleOrganization.PARAMETER_NAME, PractitionerRoleOrganization::new, + PractitionerRoleOrganization.getNameModifiers(), PractitionerRoleOrganization::new, + PractitionerRoleOrganization.getIncludeParameterValues()), + factory(PractitionerRolePractitioner.PARAMETER_NAME, PractitionerRolePractitioner::new, + PractitionerRolePractitioner.getNameModifiers(), PractitionerRolePractitioner::new, + PractitionerRolePractitioner.getIncludeParameterValues())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ProvenanceDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ProvenanceDaoJdbc.java index 2810a4bb8..09e60bd07 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ProvenanceDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ProvenanceDaoJdbc.java @@ -1,5 +1,7 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.Provenance; @@ -13,7 +15,7 @@ public class ProvenanceDaoJdbc extends AbstractResourceDaoJdbc imple public ProvenanceDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Provenance.class, "provenances", "provenance", - "provenance_id", ProvenanceIdentityFilter::new, with(), with()); + "provenance_id", ProvenanceIdentityFilter::new, Collections.emptyList(), Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireDaoJdbc.java index 3b844f3fd..2f7af9950 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireDaoJdbc.java @@ -2,6 +2,8 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import java.util.Optional; import javax.sql.DataSource; @@ -25,9 +27,16 @@ public QuestionnaireDaoJdbc(DataSource dataSource, DataSource permanentDeleteDat { super(dataSource, permanentDeleteDataSource, fhirContext, Questionnaire.class, "questionnaires", "questionnaire", "questionnaire_id", QuestionnaireIdentityFilter::new, - with(QuestionnaireDate::new, QuestionnaireIdentifier::new, QuestionnaireStatus::new, - QuestionnaireUrl::new, QuestionnaireVersion::new), - with()); + Arrays.asList(factory(QuestionnaireDate.PARAMETER_NAME, QuestionnaireDate::new), + factory(QuestionnaireIdentifier.PARAMETER_NAME, QuestionnaireIdentifier::new, + QuestionnaireIdentifier.getNameModifiers()), + factory(QuestionnaireStatus.PARAMETER_NAME, QuestionnaireStatus::new, + QuestionnaireStatus.getNameModifiers()), + factory(QuestionnaireUrl.PARAMETER_NAME, QuestionnaireUrl::new, + QuestionnaireUrl.getNameModifiers()), + factory(QuestionnaireVersion.PARAMETER_NAME, QuestionnaireVersion::new, + QuestionnaireVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(), getResourceColumn()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireResponseDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireResponseDaoJdbc.java index 4731ea374..424214e03 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireResponseDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/QuestionnaireResponseDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.QuestionnaireResponse; @@ -22,10 +25,20 @@ public QuestionnaireResponseDaoJdbc(DataSource dataSource, DataSource permanentD super(dataSource, permanentDeleteDataSource, fhirContext, QuestionnaireResponse.class, "questionnaire_responses", "questionnaire_response", "questionnaire_response_id", QuestionnaireResponseIdentityFilter::new, - with(QuestionnaireResponseAuthored::new, QuestionnaireResponseIdentifier::new, - QuestionnaireResponseQuestionnaire::new, QuestionnaireResponseStatus::new, - QuestionnaireResponseSubject::new), - with()); + Arrays.asList(factory(QuestionnaireResponseAuthored.PARAMETER_NAME, QuestionnaireResponseAuthored::new), + factory(QuestionnaireResponseIdentifier.PARAMETER_NAME, QuestionnaireResponseIdentifier::new, + QuestionnaireResponseIdentifier.getNameModifiers()), + factory(QuestionnaireResponseQuestionnaire.PARAMETER_NAME, + QuestionnaireResponseQuestionnaire::new, + QuestionnaireResponseQuestionnaire.getNameModifiers(), + QuestionnaireResponseQuestionnaire::new, + QuestionnaireResponseQuestionnaire.getIncludeParameterValues()), + factory(QuestionnaireResponseStatus.PARAMETER_NAME, QuestionnaireResponseStatus::new, + QuestionnaireResponseStatus.getNameModifiers()), + factory(QuestionnaireResponseSubject.PARAMETER_NAME, QuestionnaireResponseSubject::new, + QuestionnaireResponseSubject.getNameModifiers(), QuestionnaireResponseSubject::new, + QuestionnaireResponseSubject.getIncludeParameterValues())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ResearchStudyDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ResearchStudyDaoJdbc.java index a4f896568..7de4257b8 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ResearchStudyDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ResearchStudyDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.ResearchStudy; @@ -17,9 +20,18 @@ public ResearchStudyDaoJdbc(DataSource dataSource, DataSource permanentDeleteDat { super(dataSource, permanentDeleteDataSource, fhirContext, ResearchStudy.class, "research_studies", "research_study", "research_study_id", ResearchStudyIdentityFilter::new, - with(ResearchStudyEnrollment::new, ResearchStudyIdentifier::new, - ResearchStudyPrincipalInvestigator::new), - with()); + Arrays.asList( + factory(ResearchStudyEnrollment.PARAMETER_NAME, ResearchStudyEnrollment::new, + ResearchStudyEnrollment.getNameModifiers(), ResearchStudyEnrollment::new, + ResearchStudyEnrollment.getIncludeParameterValues()), + factory(ResearchStudyIdentifier.PARAMETER_NAME, ResearchStudyIdentifier::new, + ResearchStudyIdentifier.getNameModifiers()), + factory(ResearchStudyPrincipalInvestigator.PARAMETER_NAME, + ResearchStudyPrincipalInvestigator::new, + ResearchStudyPrincipalInvestigator.getNameModifiers(), + ResearchStudyPrincipalInvestigator::new, + ResearchStudyPrincipalInvestigator.getIncludeParameterValues())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/SubscriptionDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/SubscriptionDaoJdbc.java index bfca0549e..438d0e7ad 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/SubscriptionDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/SubscriptionDaoJdbc.java @@ -5,6 +5,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -29,9 +30,17 @@ public class SubscriptionDaoJdbc extends AbstractResourceDaoJdbc i public SubscriptionDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, Subscription.class, "subscriptions", "subscription", - "subscription_id", SubscriptionIdentityFilter::new, with(SubscriptionCriteria::new, - SubscriptionPayload::new, SubscriptionStatus::new, SubscriptionType::new), - with()); + "subscription_id", SubscriptionIdentityFilter::new, + Arrays.asList( + factory(SubscriptionCriteria.PARAMETER_NAME, SubscriptionCriteria::new, + SubscriptionCriteria.getNameModifiers()), + factory(SubscriptionPayload.PARAMETER_NAME, SubscriptionPayload::new, + SubscriptionPayload.getNameModifiers()), + factory(SubscriptionStatus.PARAMETER_NAME, SubscriptionStatus::new, + SubscriptionStatus.getNameModifiers()), + factory(SubscriptionType.PARAMETER_NAME, SubscriptionType::new, + SubscriptionType.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/TaskDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/TaskDaoJdbc.java index f56bde838..4ce05e630 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/TaskDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/TaskDaoJdbc.java @@ -1,5 +1,8 @@ package dev.dsf.fhir.dao.jdbc; +import java.util.Arrays; +import java.util.Collections; + import javax.sql.DataSource; import org.hl7.fhir.r4.model.Task; @@ -19,8 +22,13 @@ public TaskDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, { super(dataSource, permanentDeleteDataSource, fhirContext, Task.class, "tasks", "task", "task_id", TaskIdentityFilter::new, - with(TaskAuthoredOn::new, TaskIdentifier::new, TaskModified::new, TaskRequester::new, TaskStatus::new), - with()); + Arrays.asList(factory(TaskAuthoredOn.PARAMETER_NAME, TaskAuthoredOn::new), + factory(TaskIdentifier.PARAMETER_NAME, TaskIdentifier::new, TaskIdentifier.getNameModifiers()), + factory(TaskModified.PARAMETER_NAME, TaskModified::new), + factory(TaskRequester.PARAMETER_NAME, TaskRequester::new, TaskRequester.getNameModifiers(), + TaskRequester::new, TaskRequester.getIncludeParameterValues()), + factory(TaskStatus.PARAMETER_NAME, TaskStatus::new, TaskStatus.getNameModifiers())), + Collections.emptyList()); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ValueSetDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ValueSetDaoJdbc.java index f0255475c..f61ba8721 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ValueSetDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ValueSetDaoJdbc.java @@ -2,6 +2,8 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; import java.util.Optional; import javax.sql.DataSource; @@ -24,9 +26,15 @@ public class ValueSetDaoJdbc extends AbstractResourceDaoJdbc implement public ValueSetDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext) { super(dataSource, permanentDeleteDataSource, fhirContext, ValueSet.class, "value_sets", "value_set", - "value_set_id", ValueSetIdentityFilter::new, with(ValueSetDate::new, ValueSetIdentifier::new, - ValueSetStatus::new, ValueSetUrl::new, ValueSetVersion::new), - with()); + "value_set_id", ValueSetIdentityFilter::new, + Arrays.asList(factory(ValueSetDate.PARAMETER_NAME, ValueSetDate::new), + factory(ValueSetIdentifier.PARAMETER_NAME, ValueSetIdentifier::new, + ValueSetIdentifier.getNameModifiers()), + factory(ValueSetStatus.PARAMETER_NAME, ValueSetStatus::new, ValueSetStatus.getNameModifiers()), + factory(ValueSetUrl.PARAMETER_NAME, ValueSetUrl::new, ValueSetUrl.getNameModifiers()), + factory(ValueSetVersion.PARAMETER_NAME, ValueSetVersion::new, + ValueSetVersion.getNameModifiers())), + Collections.emptyList()); readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(), getResourceColumn()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/AtParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/AtParameter.java index 1b19137b3..b3fe110c9 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/AtParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/AtParameter.java @@ -7,9 +7,11 @@ public class AtParameter extends AbstractDateTimeParameter { + public static final String PARAMETER_NAME = "_at"; + public AtParameter() { - super("_at", "last_updated"); + super(PARAMETER_NAME, "last_updated"); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/HistoryServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/HistoryServiceImpl.java index de46ef527..11d1c06f4 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/HistoryServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/HistoryServiceImpl.java @@ -1,8 +1,10 @@ package dev.dsf.fhir.history; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; @@ -20,6 +22,7 @@ import dev.dsf.fhir.prefer.PreferHandlingType; import dev.dsf.fhir.search.PageAndCount; import dev.dsf.fhir.search.SearchQuery; +import dev.dsf.fhir.search.SearchQueryParameter; import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.service.ReferenceCleaner; import jakarta.ws.rs.WebApplicationException; @@ -92,20 +95,32 @@ public Bundle getHistory(Identity identity, UriInfo uri, HttpHeaders headers, Cl PageAndCount pageAndCount = new PageAndCount(effectivePage, effectiveCount); - AtParameter atParameter = new AtParameter(); - atParameter.configure(queryParameters); + List atParameters = new ArrayList<>(); SinceParameter sinceParameter = new SinceParameter(); - sinceParameter.configure(queryParameters); + + List errors = new ArrayList<>(); + + List atValues = queryParameters.getOrDefault(AtParameter.PARAMETER_NAME, Collections.emptyList()); + atValues.stream().filter(v -> v != null && !v.isBlank()).forEach(atValue -> + { + AtParameter atParameter = new AtParameter(); + atParameters.add(atParameter); + atParameter.configure(errors, AtParameter.PARAMETER_NAME, atValue); + }); + + String sinceValue = queryParameters.getFirst(SinceParameter.PARAMETER_NAME); + if (sinceValue != null && !sinceValue.isBlank()) + sinceParameter.configure(errors, SinceParameter.PARAMETER_NAME, sinceValue); String path = null; History history; if (resource == null && id == null) history = exceptionHandler.handleSqlException(() -> historyDao.readHistory( - historyUserFilterFactory.getIdentityFilters(identity), pageAndCount, atParameter, sinceParameter)); + historyUserFilterFactory.getIdentityFilters(identity), pageAndCount, atParameters, sinceParameter)); else if (resource != null && id != null) { history = exceptionHandler.handleSqlException(() -> historyDao.readHistory( - historyUserFilterFactory.getIdentityFilter(identity, resource), pageAndCount, atParameter, + historyUserFilterFactory.getIdentityFilter(identity, resource), pageAndCount, atParameters, sinceParameter, resource, parameterConverter.toUuid(getResourceTypeName(resource), id))); path = resource.getAnnotation(ResourceDef.class).name(); } @@ -113,16 +128,12 @@ else if (resource != null) { history = exceptionHandler.handleSqlException( () -> historyDao.readHistory(historyUserFilterFactory.getIdentityFilter(identity, resource), - pageAndCount, atParameter, sinceParameter, resource)); + pageAndCount, atParameters, sinceParameter, resource)); path = resource.getAnnotation(ResourceDef.class).name(); } else throw new WebApplicationException(); - List errors = new ArrayList<>(); - errors.addAll(atParameter.getErrors()); - errors.addAll(sinceParameter.getErrors()); - if (!errors.isEmpty() && PreferHandlingType.STRICT.equals(parameterConverter.getPreferHandling(headers))) throw new WebApplicationException( responseGenerator.response(Status.BAD_REQUEST, responseGenerator.toOperationOutcomeError(errors), @@ -139,8 +150,7 @@ else if (resource != null) bundleUri = bundleUri.path(id); bundleUri = bundleUri.path("_history"); - atParameter.modifyBundleUri(bundleUri); - sinceParameter.modifyBundleUri(bundleUri); + bundleUri = configureBundleUri(bundleUri, atParameters, sinceParameter); Bundle bundle = responseGenerator.createHistoryBundle(history, errors, bundleUri, format, pretty, summaryMode); // clean literal references from bundle entries @@ -149,6 +159,18 @@ else if (resource != null) return bundle; } + private UriBuilder configureBundleUri(UriBuilder bundleUri, List atParameters, + SinceParameter sinceParameter) + { + Objects.requireNonNull(bundleUri, "bundleUri"); + + Stream.concat(atParameters.stream(), Stream.of(sinceParameter)).filter(SearchQueryParameter::isDefined) + .forEach(p -> bundleUri.replaceQueryParam(p.getBundleUriQueryParameterName(), + p.getBundleUriQueryParameterValue())); + + return bundleUri; + } + private String getResourceTypeName(Class resource) { if (resource == null) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/SinceParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/SinceParameter.java index 22779259c..c2bdb75ce 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/SinceParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/history/SinceParameter.java @@ -1,6 +1,5 @@ package dev.dsf.fhir.history; -import java.util.Collections; import java.util.List; import org.hl7.fhir.r4.model.DomainResource; @@ -12,43 +11,31 @@ public class SinceParameter extends AbstractDateTimeParameter { + public static final String PARAMETER_NAME = "_since"; + public SinceParameter() { - super("_since", "last_updated"); + super(PARAMETER_NAME, "last_updated"); } @Override - protected void checkParameters(List parameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - List superValuesAndTypes = super.getValuesAndTypes(); - - if (superValuesAndTypes.size() > 1) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, parameters, "More than one " + parameterName + " values")); + super.doConfigure(errors, queryParameterName, queryParameterValue); - if (superValuesAndTypes.size() == 1) + if (!DateTimeSearchType.EQ.equals(valueAndType.searchType) + || !DateTimeType.ZONED_DATE_TIME.equals(valueAndType.type)) { - DateTimeValueAndTypeAndSearchType vT = superValuesAndTypes.get(0); - if (!DateTimeSearchType.EQ.equals(vT.searchType) || !DateTimeType.ZONED_DATE_TIME.equals(vT.type)) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, - parameters, "Not instant")); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + queryParameterValue, "Not instant")); + valueAndType = null; } - } - - @Override - public List getValuesAndTypes() - { - List superValuesAndTypes = super.getValuesAndTypes(); - - if (superValuesAndTypes.size() == 1) + else { - DateTimeValueAndTypeAndSearchType vT = superValuesAndTypes.get(0); - if (DateTimeSearchType.EQ.equals(vT.searchType) && DateTimeType.ZONED_DATE_TIME.equals(vT.type)) - return Collections - .singletonList(new DateTimeValueAndTypeAndSearchType(vT.value, vT.type, DateTimeSearchType.GE)); + valueAndType = new DateTimeValueAndTypeAndSearchType(valueAndType.value, valueAndType.type, + DateTimeSearchType.GE); } - - return superValuesAndTypes; } @Override @@ -64,4 +51,10 @@ protected String getSortSql(String sortDirectionWithSpacePrefix) // Not implemented for history throw new UnsupportedOperationException(); } + + @Override + public String getBundleUriQueryParameterValue() + { + return toUrlValue(valueAndType); + } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java index 5c1d97276..8204695a5 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java @@ -7,11 +7,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -26,9 +29,10 @@ public class SearchQuery implements DbSearchQuery, Matcher { - public static final String PARAMETER_SORT = "_sort"; public static final String PARAMETER_INCLUDE = "_include"; public static final String PARAMETER_REVINCLUDE = "_revinclude"; + + public static final String PARAMETER_SORT = "_sort"; public static final String PARAMETER_PAGE = "_page"; public static final String PARAMETER_COUNT = "_count"; public static final String PARAMETER_FORMAT = "_format"; @@ -38,6 +42,9 @@ public class SearchQuery implements DbSearchQuery, Matcher public static final String[] STANDARD_PARAMETERS = { PARAMETER_SORT, PARAMETER_INCLUDE, PARAMETER_REVINCLUDE, PARAMETER_PAGE, PARAMETER_COUNT, PARAMETER_FORMAT, PARAMETER_PRETTY, PARAMETER_SUMMARY }; + public static final String[] SINGLE_VALUE_PARAMETERS = { PARAMETER_SORT, PARAMETER_PAGE, PARAMETER_COUNT, + PARAMETER_FORMAT, PARAMETER_PRETTY, PARAMETER_SUMMARY }; + public static class SearchQueryBuilder { public static SearchQueryBuilder create(Class resourceType, String resourceTable, @@ -53,10 +60,10 @@ public static SearchQueryBuilder create(Class resourc private final int page; private final int count; - private final List> searchParameters = new ArrayList>(); + private final List> searchParameters = new ArrayList<>(); private final List revIncludeParameters = new ArrayList<>(); - private SearchQueryIdentityFilter userFilter; // may be null + private SearchQueryIdentityFilter identityFilter; // may be null private SearchQueryBuilder(Class resourceType, String resourceTable, String resourceColumn, int page, int count) @@ -69,49 +76,50 @@ private SearchQueryBuilder(Class resourceType, String resourceTable, String r this.count = count; } - public SearchQueryBuilder with(SearchQueryIdentityFilter userFilter) + public SearchQueryBuilder with(SearchQueryIdentityFilter identityFilter) { - this.userFilter = userFilter; + this.identityFilter = identityFilter; return this; } - public SearchQueryBuilder with(SearchQueryParameter searchParameters) + public SearchQueryBuilder with(SearchQueryParameterFactory searchParameters) { this.searchParameters.add(searchParameters); return this; } - public SearchQueryBuilder with(@SuppressWarnings("unchecked") SearchQueryParameter... searchParameters) + public SearchQueryBuilder with( + @SuppressWarnings("unchecked") SearchQueryParameterFactory... searchParameters) { return with(Arrays.asList(searchParameters)); } - public SearchQueryBuilder with(List> searchParameters) + public SearchQueryBuilder with(List> searchParameters) { this.searchParameters.addAll(searchParameters); return this; } - public SearchQueryBuilder withRevInclude(SearchQueryRevIncludeParameterFactory searchParameters) + public SearchQueryBuilder withRevInclude(SearchQueryRevIncludeParameterFactory revIncludeParameter) { - this.revIncludeParameters.add(searchParameters); + this.revIncludeParameters.add(revIncludeParameter); return this; } - public SearchQueryBuilder withRevInclude(SearchQueryRevIncludeParameterFactory... searchParameters) + public SearchQueryBuilder withRevInclude(SearchQueryRevIncludeParameterFactory... revIncludeParameters) { - return withRevInclude(Arrays.asList(searchParameters)); + return withRevInclude(Arrays.asList(revIncludeParameters)); } - public SearchQueryBuilder withRevInclude(List searchParameters) + public SearchQueryBuilder withRevInclude(List revIncludeParameters) { - this.revIncludeParameters.addAll(searchParameters); + this.revIncludeParameters.addAll(revIncludeParameters); return this; } public SearchQuery build() { - return new SearchQuery(resourceType, resourceTable, resourceColumn, userFilter, page, count, + return new SearchQuery(resourceType, resourceTable, resourceColumn, identityFilter, page, count, searchParameters, revIncludeParameters); } } @@ -122,176 +130,274 @@ public SearchQuery build() private final String resourceColumn; private final String resourceTable; - private final SearchQueryIdentityFilter userFilter; + private final SearchQueryIdentityFilter identityFilter; private final PageAndCount pageAndCount; + private final Map> searchParameterFactoriesByParameterName = new HashMap<>(); + private final Map> searchParameterFactoriesBySortParameterName = new HashMap<>(); + private final Map> includeParameterFactoriesByValue = new HashMap<>(); + private final Map revIncludeParameterFactoriesByValue = new HashMap<>(); + private final List> searchParameters = new ArrayList<>(); - private final List revIncludeParameterFactories = new ArrayList<>(); + private final List sortParameters = new ArrayList<>(); + private final List includeParameters = new ArrayList<>(); + private final List revIncludeParameters = new ArrayList<>(); + private final List errors = new ArrayList<>(); private String filterQuery; private String sortSql; private String includeSql; private String revIncludeSql; - private List> sortParameters = Collections.emptyList(); - private List includeParameters = Collections.emptyList(); - private List revIncludeParameters = Collections.emptyList(); SearchQuery(Class resourceType, String resourceTable, String resourceColumn, - SearchQueryIdentityFilter userFilter, int page, int count, - List> searchParameters, - List revIncludeParameters) + SearchQueryIdentityFilter identityFilter, int page, int count, + List> searchParameterFactories, + List searchRevIncludeParameterFactories) { this.resourceType = resourceType; this.resourceTable = resourceTable; this.resourceColumn = resourceColumn; - this.userFilter = userFilter; + this.identityFilter = identityFilter; this.pageAndCount = new PageAndCount(page, count); - this.searchParameters.addAll(searchParameters); - this.revIncludeParameterFactories.addAll(revIncludeParameters); - } - - public SearchQuery configureParameters(Map> queryParameters) - { - searchParameters.forEach(p -> p.configure(queryParameters)); - - List revIncludeParameterValues = queryParameters.getOrDefault(PARAMETER_REVINCLUDE, - Collections.emptyList()); - revIncludeParameterFactories.forEach(p -> p.configure(revIncludeParameterValues)); - - includeSql = createIncludeSql(queryParameters.get(PARAMETER_INCLUDE)); - revIncludeSql = createRevIncludeSql(); + if (searchParameterFactories != null) + { + searchParameterFactories.forEach(f -> + { + f.getNameAndModifiedNames().forEach(name -> + { + SearchQueryParameterFactory existingMapping = searchParameterFactoriesByParameterName + .putIfAbsent(name, f); - filterQuery = createFilterQuery(); + if (existingMapping != null) + throw new RuntimeException("More than one " + SearchQueryParameter.class.getName() + + " configured for parameter name " + name); + }); - sortSql = createSortSql(getFirst(queryParameters, PARAMETER_SORT)); + f.getSortNames().forEach(name -> + { + SearchQueryParameterFactory existingMapping = searchParameterFactoriesBySortParameterName + .putIfAbsent(name, f); - return this; - } + if (existingMapping != null) + throw new RuntimeException("More than one " + SearchQueryParameter.class.getName() + + " configured for sort parameter name " + name); + }); - private String createFilterQuery() - { - Stream elements = searchParameters.stream().filter(SearchQueryParameter::isDefined) - .map(SearchQueryParameter::getFilterQuery); + if (f.isIncludeParameter()) + { + f.getIncludeParameterValues().forEach(value -> + { + SearchQueryParameterFactory existingMapping = includeParameterFactoriesByValue + .putIfAbsent(value, f); + + if (existingMapping != null) + throw new RuntimeException("More than one " + SearchQueryParameter.class.getName() + + " configured for include parameter value " + value); + }); + } + }); + } - if (userFilter != null && !userFilter.getFilterQuery().isEmpty()) - elements = Stream.concat(Stream.of(userFilter.getFilterQuery()), elements); + if (searchRevIncludeParameterFactories != null) + { + searchRevIncludeParameterFactories.forEach(f -> f.getRevIncludeParameterValues().forEach(value -> + { + SearchQueryRevIncludeParameterFactory existingMapping = revIncludeParameterFactoriesByValue + .putIfAbsent(value, f); - return elements.collect(Collectors.joining(" AND ")); + if (existingMapping != null) + throw new RuntimeException("More than one " + SearchQueryRevIncludeParameter.class.getName() + + " configured for revinclude parameter value " + value); + })); + } } - public List getUnsupportedQueryParameters(Map> queryParameters) + public SearchQuery configureParameters(Map> queryParameters) { - Map> parameters = new HashMap>(queryParameters); - searchParameters.stream().flatMap(p -> p.getBaseAndModifiedParameterNames()).forEach(parameters::remove); - Arrays.asList(STANDARD_PARAMETERS).forEach(parameters::remove); - - List errors = new ArrayList<>(getDuplicateStandardParameters(queryParameters)); + checkSingleValueParameters(queryParameters); - parameters.keySet().stream().map( - name -> new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_PARAMETER, name, null)) - .forEach(errors::add); + filterQuery = createFilterQuery(queryParameters); - searchParameters.stream().flatMap(p -> p.getErrors().stream()).forEach(errors::add); - revIncludeParameterFactories.stream().flatMap(p -> p.getErrors().stream()).forEach(errors::add); - - List includeParameterValues = queryParameters.getOrDefault(PARAMETER_INCLUDE, Collections.emptyList()); - includeParameters.stream().map(SearchQueryIncludeParameter::getBundleUriQueryParameterValues) - .forEach(v -> includeParameterValues.remove(v)); - if (!includeParameterValues.isEmpty()) - errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_PARAMETER, - PARAMETER_INCLUDE, includeParameterValues)); - - List revIncludeParameterValues = new ArrayList<>( + includeSql = createIncludeSql(queryParameters.getOrDefault(PARAMETER_INCLUDE, Collections.emptyList())); + revIncludeSql = createRevIncludeSql( queryParameters.getOrDefault(PARAMETER_REVINCLUDE, Collections.emptyList())); - revIncludeParameters.stream().map(SearchQueryIncludeParameter::getBundleUriQueryParameterValues) - .forEach(v -> revIncludeParameterValues.remove(v)); - if (!revIncludeParameterValues.isEmpty()) - errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_PARAMETER, - PARAMETER_REVINCLUDE, revIncludeParameterValues)); - if (!errors.isEmpty()) - logger.warn("Query parameters with error: {}", errors); + sortSql = createSortSql(queryParameters.getOrDefault(PARAMETER_SORT, Collections.emptyList())); - return errors; + return this; } - private List getDuplicateStandardParameters(Map> queryParameters) + private void checkSingleValueParameters(Map> queryParameters) { - List errors = new ArrayList<>(); - for (String parameter : STANDARD_PARAMETERS) + Arrays.stream(SINGLE_VALUE_PARAMETERS).forEach(parameter -> { List values = queryParameters.get(parameter); if (values != null && values.size() > 1) { - if ((!PARAMETER_INCLUDE.equals(parameter) && !PARAMETER_REVINCLUDE.equals(parameter)) - || hasDuplicates(values)) - errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameter, values)); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, + parameter, null, "More than one query parameter `" + parameter + "`")); } - } - return errors; + }); } - private boolean hasDuplicates(List values) + private String createFilterQuery(Map> queryParameters) { - return values.size() != new HashSet<>(values).size(); + queryParameters.entrySet().stream() + .filter(e -> Arrays.stream(STANDARD_PARAMETERS).noneMatch(p -> p.equals(e.getKey()))).forEach(e -> + { + SearchQueryParameterFactory queryParameterFactory = searchParameterFactoriesByParameterName + .get(e.getKey()); + if (queryParameterFactory != null) + { + e.getValue().stream().filter(v -> v != null && !v.isBlank()) + .forEach(value -> searchParameters.add(queryParameterFactory.createQueryParameter() + .configure(errors, e.getKey(), value))); + } + else + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_PARAMETER, + e.getKey(), null, "Query parameter `" + e.getKey() + "` not supported")); + } + }); + + Stream elements = searchParameters.stream().filter(SearchQueryParameter::isDefined) + .map(SearchQueryParameter::getFilterQuery); + + if (identityFilter != null && !identityFilter.getFilterQuery().isEmpty()) + elements = Stream.concat(Stream.of(identityFilter.getFilterQuery()), elements); + + return elements.collect(Collectors.joining(" AND ")); } - private String getFirst(Map> queryParameters, String key) + // TODO rename ? + public List getUnsupportedQueryParameters() { - if (queryParameters.containsKey(key) && !queryParameters.get(key).isEmpty()) - return queryParameters.get(key).get(0); - else - return null; + return errors; } - private String createSortSql(String sortParameterValue) + private String createSortSql(List sortParameterValues) { - if (sortParameterValue == null) + if (sortParameterValues.size() <= 0) return ""; - sortParameters = searchParameters.stream().filter(sp -> sp.getSortParameter().isPresent()) - .collect(Collectors.toList()); + final String sortParameterValue = sortParameterValues.get(0); - if (sortParameters.isEmpty()) + if (sortParameterValue == null || sortParameterValue.isBlank()) return ""; - return sortParameters.stream().map(sp -> sp.getSortParameter().get().getSql()) - .collect(Collectors.joining(", ", " ORDER BY ", "")); + Set supportedSortValues = new HashSet<>(); + for (String value : sortParameterValue.split(",")) + { + if (value != null && !value.isBlank()) + { + SearchQueryParameterFactory sortParameterFactory = searchParameterFactoriesByParameterName + .get(value); + if (sortParameterFactory != null) + { + if (!supportedSortValues.contains(sortParameterFactory.getName())) + { + supportedSortValues.add(sortParameterFactory.getName()); + sortParameters + .add(sortParameterFactory.createQuerySortParameter().configureSort(errors, value)); + } + else + { + errors.add(new SearchQueryParameterError( + SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, PARAMETER_SORT, null, + "More than one " + PARAMETER_SORT + " query parameter valus `" + value + "`")); + } + } + else + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + PARAMETER_SORT, null, + PARAMETER_SORT + " query parameter value `" + value + "` not supported")); + } + } + } + + return sortParameters.isEmpty() ? "" + : sortParameters.stream().map(SearchQuerySortParameterConfiguration::getSql) + .collect(Collectors.joining(", ", " ORDER BY ", "")); } private String createIncludeSql(List includeParameterValues) { - if (includeParameterValues == null || includeParameterValues.isEmpty()) - return ""; - - includeParameters = searchParameters.stream().flatMap(sp -> sp.getIncludeParameters().stream()) - .collect(Collectors.toList()); - - if (includeParameters.isEmpty()) - return ""; + Set supportedIncludeValues = new HashSet<>(); + for (String value : includeParameterValues) + { + if (value != null && !value.isBlank()) + { + SearchQueryParameterFactory includeParameterFactory = includeParameterFactoriesByValue.get(value); + if (includeParameterFactory != null) + { + if (!supportedIncludeValues.contains(value)) + { + supportedIncludeValues.add(value); + includeParameters.add( + includeParameterFactory.createQueryIncludeParameter().configureInclude(errors, value)); + } + else + { + errors.add(new SearchQueryParameterError( + SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, PARAMETER_INCLUDE, null, + "More than one " + PARAMETER_INCLUDE + " query parameter value " + value)); + } + } + else + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + PARAMETER_INCLUDE, null, + PARAMETER_INCLUDE + " query parameter value " + value + " not supported")); + } + } + } - return includeParameters.stream().map(SearchQueryIncludeParameter::getSql) - .collect(Collectors.joining(", ", ", ", "")); + return includeParameters.isEmpty() ? "" + : includeParameters.stream().map(SearchQueryIncludeParameterConfiguration::getSql) + .collect(Collectors.joining(", ", ", ", "")); } - private String createRevIncludeSql() + private String createRevIncludeSql(List revIncludeParameterValues) { - if (revIncludeParameterFactories == null || revIncludeParameterFactories.isEmpty()) - return ""; - - revIncludeParameters = revIncludeParameterFactories.stream().flatMap(f -> f.getRevIncludeParameters().stream()) - .collect(Collectors.toList()); - - if (revIncludeParameters.isEmpty()) - return ""; + Set supportedRevIncludeValues = new HashSet<>(); + for (String value : revIncludeParameterValues) + { + if (value != null && !value.isBlank()) + { + SearchQueryRevIncludeParameterFactory revIncludeParameterFactory = revIncludeParameterFactoriesByValue + .get(value); + if (revIncludeParameterFactory != null) + { + if (!supportedRevIncludeValues.contains(value)) + { + supportedRevIncludeValues.add(value); + revIncludeParameters.add(revIncludeParameterFactory.createQueryRevIncludeParameter() + .configureRevInclude(errors, value)); + } + else + { + errors.add(new SearchQueryParameterError( + SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, PARAMETER_REVINCLUDE, null, + "More than one " + PARAMETER_REVINCLUDE + " query parameter value " + value)); + } + } + else + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + PARAMETER_REVINCLUDE, null, + PARAMETER_REVINCLUDE + " query parameter value " + value + " not supported")); + } + } + } - return revIncludeParameters.stream().map(SearchQueryIncludeParameter::getSql) - .collect(Collectors.joining(", ", ", ", "")); + return revIncludeParameters.isEmpty() ? "" + : revIncludeParameters.stream().map(SearchQueryIncludeParameterConfiguration::getSql) + .collect(Collectors.joining(", ", ", ", "")); } @Override @@ -322,12 +428,12 @@ public void modifyStatement(PreparedStatement statement, .collect(Collectors.toList()); int index = 0; - if (userFilter != null) + if (identityFilter != null) { - while (index < userFilter.getSqlParameterCount()) + while (index < identityFilter.getSqlParameterCount()) { int i = ++index; - userFilter.modifyStatement(i, i, statement); + identityFilter.modifyStatement(i, i, statement); } } @@ -352,35 +458,39 @@ public UriBuilder configureBundleUri(UriBuilder bundleUri) { Objects.requireNonNull(bundleUri, "bundleUri"); - searchParameters.stream().filter(SearchQueryParameter::isDefined).forEach(p -> p.modifyBundleUri(bundleUri)); + searchParameters.stream().filter(SearchQueryParameter::isDefined) + .collect(Collectors.toMap(SearchQueryParameter::getBundleUriQueryParameterName, + p -> Collections.singletonList(p.getBundleUriQueryParameterValue()), (v1, v2) -> + { + List list = new ArrayList<>(v1); + list.addAll(v2); + return list; + })) + .entrySet().stream().sorted(Comparator.comparing(Entry::getKey)) + .forEach(e -> bundleUri.replaceQueryParam(e.getKey(), e.getValue().toArray())); if (!sortParameters.isEmpty()) - bundleUri.replaceQueryParam(PARAMETER_SORT, sortParameter()); + { + String values = sortParameters.stream().map(p -> p.getBundleUriQueryParameterValuePart()) + .collect(Collectors.joining(",")); + bundleUri.replaceQueryParam(PARAMETER_SORT, values); + } if (!includeParameters.isEmpty()) - bundleUri.replaceQueryParam(PARAMETER_INCLUDE, includeParameters()); - if (!revIncludeParameterFactories.isEmpty()) - bundleUri.replaceQueryParam(PARAMETER_REVINCLUDE, revIncludeParameters()); + { + Object[] values = includeParameters.stream() + .map(SearchQueryIncludeParameterConfiguration::getBundleUriQueryParameterValues).toArray(); + bundleUri.replaceQueryParam(PARAMETER_INCLUDE, values); + } + if (!revIncludeParameters.isEmpty()) + { + Object[] values = revIncludeParameters.stream() + .map(SearchQueryIncludeParameterConfiguration::getBundleUriQueryParameterValues).toArray(); + bundleUri.replaceQueryParam(PARAMETER_REVINCLUDE, values); + } return bundleUri; } - private String sortParameter() - { - return sortParameters.stream().map(p -> p.getSortParameter().get().getBundleUriQueryParameterValuePart()) - .collect(Collectors.joining(",")); - } - - private Object[] includeParameters() - { - return includeParameters.stream().map(SearchQueryIncludeParameter::getBundleUriQueryParameterValues).toArray(); - } - - private Object[] revIncludeParameters() - { - return revIncludeParameters.stream().map(SearchQueryIncludeParameter::getBundleUriQueryParameterValues) - .toArray(); - } - public Class getResourceType() { return resourceType; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameter.java old mode 100755 new mode 100644 index 1a3a430b9..4a27e4444 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameter.java @@ -1,57 +1,18 @@ package dev.dsf.fhir.search; -import java.sql.Connection; -import java.sql.SQLException; +import java.util.List; -import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Resource; -import dev.dsf.fhir.function.BiConsumerWithSqlException; - -public class SearchQueryIncludeParameter +public interface SearchQueryIncludeParameter { - private final String sql; - private final IncludeParts includeParts; - - private final BiConsumerWithSqlException includeResourceModifier; - - public SearchQueryIncludeParameter(String sql, IncludeParts includeParts) - { - this(sql, includeParts, null); - } - /** - * @param sql + * @param errors * not null - * @param includeParts - * not null - * @param includeResourceModifier - * Use this {@link BiConsumerWithSqlException} to modify the include resources. This consumer can be used - * if the resources returned by the include SQL are not complete and additional content needs to be - * retrieved from a not included column. For example the content of a {@link Binary} resource might not - * be stored in the json column. + * @param queryParameterIncludeValue + * not null, not blank + * @return */ - public SearchQueryIncludeParameter(String sql, IncludeParts includeParts, - BiConsumerWithSqlException includeResourceModifier) - { - this.sql = sql; - this.includeParts = includeParts; - this.includeResourceModifier = includeResourceModifier; - } - - public String getBundleUriQueryParameterValues() - { - return includeParts.toBundleUriQueryParameterValue(); - } - - public String getSql() - { - return sql; - } - - public void modifyIncludeResource(Resource resource, Connection connection) throws SQLException - { - if (includeResourceModifier != null) - includeResourceModifier.accept(resource, connection); - } + SearchQueryIncludeParameterConfiguration configureInclude(List errors, + String queryParameterIncludeValue); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameterConfiguration.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameterConfiguration.java new file mode 100755 index 000000000..72a431772 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryIncludeParameterConfiguration.java @@ -0,0 +1,57 @@ +package dev.dsf.fhir.search; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.Resource; + +import dev.dsf.fhir.function.BiConsumerWithSqlException; + +public class SearchQueryIncludeParameterConfiguration +{ + private final String sql; + private final IncludeParts includeParts; + + private final BiConsumerWithSqlException includeResourceModifier; + + public SearchQueryIncludeParameterConfiguration(String sql, IncludeParts includeParts) + { + this(sql, includeParts, null); + } + + /** + * @param sql + * not null + * @param includeParts + * not null + * @param includeResourceModifier + * Use this {@link BiConsumerWithSqlException} to modify the include resources. This consumer can be used + * if the resources returned by the include SQL are not complete and additional content needs to be + * retrieved from a not included column. For example the content of a {@link Binary} resource might not + * be stored in the json column. + */ + public SearchQueryIncludeParameterConfiguration(String sql, IncludeParts includeParts, + BiConsumerWithSqlException includeResourceModifier) + { + this.sql = sql; + this.includeParts = includeParts; + this.includeResourceModifier = includeResourceModifier; + } + + public String getBundleUriQueryParameterValues() + { + return includeParts.toBundleUriQueryParameterValue(); + } + + public String getSql() + { + return sql; + } + + public void modifyIncludeResource(Resource resource, Connection connection) throws SQLException + { + if (includeResourceModifier != null) + includeResourceModifier.accept(resource, connection); + } +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameter.java index 598e19588..e1d5e5683 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameter.java @@ -9,17 +9,14 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Stream; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; import org.hl7.fhir.r4.model.Resource; import dev.dsf.fhir.function.BiFunctionWithSqlException; -import jakarta.ws.rs.core.UriBuilder; +import dev.dsf.fhir.search.parameters.SearchQuerySortParameter; -public interface SearchQueryParameter extends MatcherParameter +public interface SearchQueryParameter extends MatcherParameter, SearchQuerySortParameter { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -35,9 +32,17 @@ public interface SearchQueryParameter extends MatcherParamet String documentation(); } - void configure(Map> queryParameters); - - List getErrors(); + /** + * @param errors + * not null + * @param queryParameterName + * not null and not blank + * @param queryParameterValue + * not null and not blank + * @return the current instance + */ + SearchQueryParameter configure(List errors, String queryParameterName, + String queryParameterValue); boolean isDefined(); @@ -49,18 +54,18 @@ void modifyStatement(int parameterIndex, int subqueryParameterIndex, PreparedSta BiFunctionWithSqlException arrayCreator) throws SQLException; /** - * Will not be called if {@link #isDefined()} returns false + * Only called if {@link #isDefined()} returns true * - * @param bundleUri - * never null + * @return not null, not blank */ - void modifyBundleUri(UriBuilder bundleUri); - - Optional getSortParameter(); + String getBundleUriQueryParameterName(); - List getIncludeParameters(); + /** + * Only called if {@link #isDefined()} returns true + * + * @return not null, not blank + */ + String getBundleUriQueryParameterValue(); String getParameterName(); - - Stream getBaseAndModifiedParameterNames(); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterError.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterError.java index aa76f6200..51d5ff8c8 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterError.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterError.java @@ -1,7 +1,5 @@ package dev.dsf.fhir.search; -import java.util.List; - public class SearchQueryParameterError { public static enum SearchQueryParameterErrorType @@ -11,34 +9,33 @@ public static enum SearchQueryParameterErrorType private final SearchQueryParameterErrorType type; private final String parameterName; - private final List parameterValues; + private final String parameterValue; private final Exception exception; private final String message; - public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, - List parameterValues) + public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, String parameterValue) { - this(type, parameterName, parameterValues, null, null); + this(type, parameterName, parameterValue, null, null); } - public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, - List parameterValues, String message) + public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, String parameterValue, + String message) { - this(type, parameterName, parameterValues, null, message); + this(type, parameterName, parameterValue, null, message); } - public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, - List parameterValues, Exception exception) + public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, String parameterValue, + Exception exception) { - this(type, parameterName, parameterValues, exception, null); + this(type, parameterName, parameterValue, exception, null); } - public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, - List parameterValues, Exception exception, String message) + public SearchQueryParameterError(SearchQueryParameterErrorType type, String parameterName, String parameterValue, + Exception exception, String message) { this.type = type; this.parameterName = parameterName; - this.parameterValues = parameterValues; + this.parameterValue = parameterValue; this.exception = exception; this.message = message; } @@ -53,9 +50,9 @@ public String getParameterName() return parameterName; } - public List getParameterValues() + public String getParameterValue() { - return parameterValues; + return parameterValue; } public Exception getException() @@ -92,10 +89,10 @@ else if (message != null) b.append("'"); } } - if (parameterValues != null) + if (parameterValue != null) { - b.append(", values: "); - b.append(parameterValues); + b.append(", value: "); + b.append(parameterValue); } return b.toString(); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterFactory.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterFactory.java new file mode 100644 index 000000000..71fc5ee0a --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryParameterFactory.java @@ -0,0 +1,103 @@ +package dev.dsf.fhir.search; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.hl7.fhir.r4.model.Resource; + +import dev.dsf.fhir.search.parameters.SearchQuerySortParameter; + +public final class SearchQueryParameterFactory +{ + private final String name; + private final List nameModifiers = new ArrayList<>(); + private final Supplier> supplier; + private final Supplier> includeSupplier; + private final List includeParameterValues = new ArrayList<>(); + + /** + * @param name + * not null + * @param supplier + * not null + */ + public SearchQueryParameterFactory(String name, Supplier> supplier) + { + this(name, supplier, null, null, null); + } + + /** + * @param name + * not null + * @param supplier + * not null + * @param nameModifiers + * may be null + * @param includeSupplier + * may be null, not null if param includeParameterValues not + * null + * @param includeParameterValues + * may be null, not null if param includeSupplier not null + */ + public SearchQueryParameterFactory(String name, Supplier> supplier, + List nameModifiers, Supplier> includeSupplier, + List includeParameterValues) + { + this.name = Objects.requireNonNull(name, "name"); + this.supplier = Objects.requireNonNull(supplier, "supplier"); + + if (nameModifiers != null) + this.nameModifiers.addAll(nameModifiers); + + this.includeSupplier = includeSupplier; + if (includeParameterValues != null) + this.includeParameterValues.addAll(includeParameterValues); + + if (includeSupplier != null ^ includeParameterValues != null) + throw new IllegalArgumentException( + "includeSupplier and includeParameterValues must both be null or not null"); + } + + public String getName() + { + return name; + } + + public Stream getNameAndModifiedNames() + { + return Stream.concat(Stream.of(name), nameModifiers.stream().map(m -> name + m)); + } + + public Stream getSortNames() + { + return Stream.of(name, "+" + name, "-" + name); + } + + public Stream getIncludeParameterValues() + { + return includeParameterValues.stream(); + } + + public SearchQueryParameter createQueryParameter() + { + return supplier.get(); + } + + public SearchQuerySortParameter createQuerySortParameter() + { + return supplier.get(); + } + + public boolean isIncludeParameter() + { + return includeSupplier != null && includeParameterValues != null; + } + + public SearchQueryIncludeParameter createQueryIncludeParameter() + { + return includeSupplier.get(); + } +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameter.java new file mode 100644 index 000000000..45cfdae97 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameter.java @@ -0,0 +1,16 @@ +package dev.dsf.fhir.search; + +import java.util.List; + +public interface SearchQueryRevIncludeParameter +{ + /** + * @param errors + * not null + * @param queryParameterRevIncludeValue + * not null, not blank + * @return + */ + SearchQueryIncludeParameterConfiguration configureRevInclude(List errors, + String queryParameterRevIncludeValue); +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameterFactory.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameterFactory.java index 131a4fb7e..5dae3158b 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameterFactory.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQueryRevIncludeParameterFactory.java @@ -1,12 +1,47 @@ package dev.dsf.fhir.search; +import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; -public interface SearchQueryRevIncludeParameterFactory +public final class SearchQueryRevIncludeParameterFactory { - void configure(List revIncludeParameterValues); + private final Supplier revIncludeSupplier; + private final List revIncludeParameterValues = new ArrayList<>(); - List getErrors(); + /** + * @param revIncludeSupplier + * may be null, not null if param revIncludeParameterValues not + * null + * @param revIncludeParameterValues + * may be null, not null if param revIncludeSupplier not + * null + */ + public SearchQueryRevIncludeParameterFactory(Supplier revIncludeSupplier, + List revIncludeParameterValues) + { + this.revIncludeSupplier = revIncludeSupplier; + if (revIncludeParameterValues != null) + this.revIncludeParameterValues.addAll(revIncludeParameterValues); - List getRevIncludeParameters(); + if (revIncludeSupplier != null ^ revIncludeParameterValues != null) + throw new IllegalArgumentException( + "includeSupplier and includeParameterValues must both be null or not null"); + } + + public Stream getRevIncludeParameterValues() + { + return revIncludeParameterValues.stream(); + } + + public boolean isIncludeParameter() + { + return revIncludeSupplier != null && revIncludeParameterValues != null; + } + + public SearchQueryRevIncludeParameter createQueryRevIncludeParameter() + { + return revIncludeSupplier.get(); + } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuerySortParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuerySortParameterConfiguration.java similarity index 87% rename from dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuerySortParameter.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuerySortParameterConfiguration.java index 7d8058299..2e8dd884a 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuerySortParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuerySortParameterConfiguration.java @@ -1,6 +1,6 @@ package dev.dsf.fhir.search; -public class SearchQuerySortParameter +public class SearchQuerySortParameterConfiguration { public static enum SortDirection { @@ -38,7 +38,7 @@ public static SortDirection fromString(String sortParameter) private final String parameterName; private final SortDirection direction; - public SearchQuerySortParameter(String sql, String parameterName, SortDirection direction) + public SearchQuerySortParameterConfiguration(String sql, String parameterName, SortDirection direction) { this.sql = sql; this.parameterName = parameterName; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionStatus.java index 16aa02160..c43bf757c 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionStatus.java @@ -6,11 +6,13 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = ActivityDefinitionStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ActivityDefinition-status", type = SearchParamType.TOKEN, documentation = "The current status of the activity definition") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ActivityDefinition-status", type = SearchParamType.TOKEN, documentation = "The current status of the activity definition") public class ActivityDefinitionStatus extends AbstractStatusParameter { + public static final String RESOURCE_COLUMN = "activity_definition"; + public ActivityDefinitionStatus() { - super("activity_definition", ActivityDefinition.class); + super(RESOURCE_COLUMN, ActivityDefinition.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionVersion.java index 3979ae438..9516d2398 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ActivityDefinitionVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = ActivityDefinitionVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ActivityDefinition-version", type = SearchParamType.TOKEN, documentation = "The business version of the activity definition") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ActivityDefinition-version", type = SearchParamType.TOKEN, documentation = "The business version of the activity definition") public class ActivityDefinitionVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "activity_definition"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BinaryContentType.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BinaryContentType.java index d2b37720b..08007a611 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BinaryContentType.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BinaryContentType.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.CodeType; @@ -13,14 +12,15 @@ import dev.dsf.fhir.function.BiFunctionWithSqlException; import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; +import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = BinaryContentType.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Binary-contentType", type = SearchParamType.TOKEN, documentation = "The MIME type of the actual binary content") public class BinaryContentType extends AbstractTokenParameter { public static final String PARAMETER_NAME = "contentType"; + public static final String RESOURCE_COLUMN = "binary_json"; private CodeType contentType; @@ -30,9 +30,10 @@ public BinaryContentType() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) contentType = toContentType(valueAndType.codeValue); @@ -55,7 +56,7 @@ public boolean isDefined() @Override public String getFilterQuery() { - return "binary_json->>'contentType' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->>'contentType' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -72,9 +73,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), contentType.getValue()); + return contentType.getValue(); } @Override @@ -95,6 +96,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "binary_json->>'contentType'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->>'contentType'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BundleIdentifier.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BundleIdentifier.java index 9a872b49b..cec3fd969 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BundleIdentifier.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/BundleIdentifier.java @@ -18,6 +18,7 @@ public class BundleIdentifier extends AbstractTokenParameter { public static final String PARAMETER_NAME = "identifier"; + public static final String RESOURCE_COLUMN = "bundle"; public BundleIdentifier() { @@ -32,12 +33,14 @@ public String getFilterQuery() case CODE: case CODE_AND_SYSTEM: case SYSTEM: - return "bundle->'identifier' " + (valueAndType.negated ? "<>" : "=") + " ?::jsonb"; + return RESOURCE_COLUMN + "->'identifier' " + (valueAndType.negated ? "<>" : "=") + " ?::jsonb"; case CODE_AND_NO_SYSTEM_PROPERTY: if (valueAndType.negated) - return "bundle->'identifier'->>'value' <> ? OR (bundle->'identifier' ?? 'system')"; + return RESOURCE_COLUMN + "->'identifier'->>'value' <> ? OR (" + RESOURCE_COLUMN + + "->'identifier' ?? 'system')"; else - return "bundle->'identifier'->>'value' = ? AND NOT (bundle->'identifier' ?? 'system')"; + return RESOURCE_COLUMN + "->'identifier'->>'value' = ? AND NOT (" + RESOURCE_COLUMN + + "->'identifier' ?? 'system')"; default: return ""; } @@ -82,8 +85,8 @@ private boolean identifierMatches(Identifier identifier) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "(bundle->'identifier'->>'system')::text || (bundle->'identifier'->>'value')::text" - + sortDirectionWithSpacePrefix; + return "(" + RESOURCE_COLUMN + "->'identifier'->>'system')::text || (" + RESOURCE_COLUMN + + "->'identifier'->>'value')::text" + sortDirectionWithSpacePrefix; } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemStatus.java index 357f4328c..b5d171ae8 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemStatus.java @@ -6,11 +6,13 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = CodeSystemStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/CodeSystem-status", type = SearchParamType.TOKEN, documentation = "The current status of the code system") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/CodeSystem-status", type = SearchParamType.TOKEN, documentation = "The current status of the code system") public class CodeSystemStatus extends AbstractStatusParameter { + public static final String RESOURCE_COLUMN = "code_system"; + public CodeSystemStatus() { - super("code_system", CodeSystem.class); + super(RESOURCE_COLUMN, CodeSystem.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemVersion.java index fbf24603e..259d0cb79 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/CodeSystemVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = CodeSystemVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/CodeSystem-version", type = SearchParamType.TOKEN, documentation = "The business version of the code system") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/CodeSystem-version", type = SearchParamType.TOKEN, documentation = "The business version of the code system") public class CodeSystemVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "code_system"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/DocumentReferenceIdentifier.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/DocumentReferenceIdentifier.java index 2861477ff..349a4e1eb 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/DocumentReferenceIdentifier.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/DocumentReferenceIdentifier.java @@ -20,8 +20,8 @@ @SearchParameterDefinition(name = AbstractIdentifierParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/clinical-identifier", type = SearchParamType.TOKEN, documentation = "Identifies this document reference across multiple systems") public class DocumentReferenceIdentifier extends AbstractTokenParameter { - public static final String RESOURCE_COLUMN = "document_reference"; public static final String PARAMETER_NAME = "identifier"; + public static final String RESOURCE_COLUMN = "document_reference"; public DocumentReferenceIdentifier() { @@ -35,27 +35,37 @@ public String getFilterQuery() { case CODE: if (valueAndType.negated) - return "NOT (document_reference->'identifier' @> ?::jsonb OR document_reference->'masterIdentifier'->>'value' = ?)"; + return "NOT (" + RESOURCE_COLUMN + "->'identifier' @> ?::jsonb OR " + RESOURCE_COLUMN + + "->'masterIdentifier'->>'value' = ?)"; else - return "(document_reference->'identifier' @> ?::jsonb OR document_reference->'masterIdentifier'->>'value' = ?)"; + return "(" + RESOURCE_COLUMN + "->'identifier' @> ?::jsonb OR " + RESOURCE_COLUMN + + "->'masterIdentifier'->>'value' = ?)"; case CODE_AND_SYSTEM: if (valueAndType.negated) - return "NOT (document_reference->'identifier' @> ?::jsonb OR (document_reference->'masterIdentifier'->>'value' = ? AND document_reference->'masterIdentifier'->>'system' = ?))"; + return "NOT (" + RESOURCE_COLUMN + "->'identifier' @> ?::jsonb OR (" + RESOURCE_COLUMN + + "->'masterIdentifier'->>'value' = ? AND " + RESOURCE_COLUMN + + "->'masterIdentifier'->>'system' = ?))"; else - return "(document_reference->'identifier' @> ?::jsonb OR (document_reference->'masterIdentifier'->>'value' = ? AND document_reference->'masterIdentifier'->>'system' = ?))"; + return "(" + RESOURCE_COLUMN + "->'identifier' @> ?::jsonb OR (" + RESOURCE_COLUMN + + "->'masterIdentifier'->>'value' = ? AND " + RESOURCE_COLUMN + + "->'masterIdentifier'->>'system' = ?))"; case SYSTEM: if (valueAndType.negated) - return "NOT (document_reference->'identifier' @> ?::jsonb OR document_reference->'masterIdentifier'->>'system' = ?)"; + return "NOT (" + RESOURCE_COLUMN + "->'identifier' @> ?::jsonb OR " + RESOURCE_COLUMN + + "->'masterIdentifier'->>'system' = ?)"; else - return "(document_reference->'identifier' @> ?::jsonb OR document_reference->'masterIdentifier'->>'system' = ?)"; + return "(" + RESOURCE_COLUMN + "->'identifier' @> ?::jsonb OR " + RESOURCE_COLUMN + + "->'masterIdentifier'->>'system' = ?)"; case CODE_AND_NO_SYSTEM_PROPERTY: if (valueAndType.negated) - return "(SELECT count(*) FROM (" - + "SELECT identifier FROM jsonb_array_elements(document_reference->'identifier') AS identifier UNION SELECT document_reference->'masterIdentifier') AS document_reference_identifiers " + return "(SELECT count(*) FROM (" + "SELECT identifier FROM jsonb_array_elements(" + RESOURCE_COLUMN + + "->'identifier') AS identifier UNION SELECT " + RESOURCE_COLUMN + + "->'masterIdentifier') AS document_reference_identifiers " + "WHERE identifier->>'value' <> ? OR (identifier ?? 'system')" + ") > 0"; else - return "(SELECT count(*) FROM (" - + "SELECT identifier FROM jsonb_array_elements(document_reference->'identifier') AS identifier UNION SELECT document_reference->'masterIdentifier') AS document_reference_identifiers " + return "(SELECT count(*) FROM (" + "SELECT identifier FROM jsonb_array_elements(" + RESOURCE_COLUMN + + "->'identifier') AS identifier UNION SELECT " + RESOURCE_COLUMN + + "->'masterIdentifier') AS document_reference_identifiers " + "WHERE identifier->>'value' = ? AND NOT (identifier ?? 'system')" + ") > 0"; default: return ""; @@ -157,8 +167,8 @@ private boolean identifierMatches(TokenValueAndSearchType valueAndType, Identifi @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "(SELECT string_agg((identifier->>'system')::text || (identifier->>'value')::text, ' ') FROM (SELECT identifier FROM jsonb_array_elements(document_reference->'identifier') identifier " - + "UNION SELECT document_reference->'masterIdentifier') AS document_reference_identifier)" - + sortDirectionWithSpacePrefix; + return "(SELECT string_agg((identifier->>'system')::text || (identifier->>'value')::text, ' ') FROM (SELECT identifier FROM jsonb_array_elements(" + + RESOURCE_COLUMN + "->'identifier') identifier " + "UNION SELECT " + RESOURCE_COLUMN + + "->'masterIdentifier') AS document_reference_identifier)" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointOrganization.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointOrganization.java index 0bafe4833..fbd117f72 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointOrganization.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointOrganization.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,16 +28,22 @@ @SearchParameterDefinition(name = EndpointOrganization.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Endpoint-organization", type = SearchParamType.REFERENCE, documentation = "The organization that is managing the endpoint") public class EndpointOrganization extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "Endpoint"; + public static final String RESOURCE_TYPE_NAME = "Endpoint"; public static final String PARAMETER_NAME = "organization"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } private static final String IDENTIFIERS_SUBQUERY = "(SELECT organization->'identifier' FROM current_organizations" + " WHERE concat('Organization/', organization->>'id') = endpoint->'managingOrganization'->>'reference')"; public EndpointOrganization() { - super(Endpoint.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(Endpoint.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java index 6fb78a2a1..75674f82e 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.exceptions.FHIRException; @@ -18,12 +17,12 @@ import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = EndpointStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Endpoint-status", type = SearchParamType.TOKEN, documentation = "The current status of the Endpoint (usually expected to be active)") public class EndpointStatus extends AbstractTokenParameter { public static final String PARAMETER_NAME = "status"; + public static final String RESOURCE_COLUMN = "endpoint"; private org.hl7.fhir.r4.model.Endpoint.EndpointStatus status; @@ -33,15 +32,15 @@ public EndpointStatus() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); - if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) - status = toStatus(valueAndType.codeValue, queryParameters.get(parameterName)); + status = toStatus(errors, valueAndType.codeValue, queryParameterValue); } - private org.hl7.fhir.r4.model.Endpoint.EndpointStatus toStatus(String status, List parameterValues) + private org.hl7.fhir.r4.model.Endpoint.EndpointStatus toStatus(List errors, + String status, String queryParameterValue) { if (status == null || status.isBlank()) return null; @@ -52,8 +51,8 @@ private org.hl7.fhir.r4.model.Endpoint.EndpointStatus toStatus(String status, Li } catch (FHIRException e) { - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, - parameterValues, e)); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + queryParameterValue, e)); return null; } } @@ -67,7 +66,7 @@ public boolean isDefined() @Override public String getFilterQuery() { - return "endpoint->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -84,9 +83,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), status.toCode()); + return status.toCode(); } @Override @@ -107,6 +106,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "endpoint->>'status'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->>'status'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/GroupIdentifier.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/GroupIdentifier.java new file mode 100644 index 000000000..551c266c6 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/GroupIdentifier.java @@ -0,0 +1,33 @@ +package dev.dsf.fhir.search.parameters; + +import org.hl7.fhir.r4.model.Enumerations.SearchParamType; +import org.hl7.fhir.r4.model.Group; +import org.hl7.fhir.r4.model.Resource; + +import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; +import dev.dsf.fhir.search.parameters.basic.AbstractIdentifierParameter; + +@SearchParameterDefinition(name = AbstractIdentifierParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Group-identifier", type = SearchParamType.TOKEN, documentation = "External identifier for the group") +public class GroupIdentifier extends AbstractIdentifierParameter +{ + public static final String RESOURCE_COLUMN = "group_json"; + + public GroupIdentifier() + { + super(RESOURCE_COLUMN); + } + + @Override + public boolean matches(Resource resource) + { + if (!isDefined()) + throw notDefined(); + + if (!(resource instanceof Group)) + return false; + + Group l = (Group) resource; + + return identifierMatches(l.getIdentifier()); + } +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryStatus.java index 1d6e762e7..fe710eb95 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryStatus.java @@ -6,11 +6,13 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = LibraryStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Library-status", type = SearchParamType.TOKEN, documentation = "The current status of the library") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Library-status", type = SearchParamType.TOKEN, documentation = "The current status of the library") public class LibraryStatus extends AbstractStatusParameter { + public static final String RESOURCE_COLUMN = "library"; + public LibraryStatus() { - super("library", Library.class); + super(RESOURCE_COLUMN, Library.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryVersion.java index 9daef0fcb..cbd9d66c3 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/LibraryVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = LibraryVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Library-version", type = SearchParamType.TOKEN, documentation = "The business version of the library") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Library-version", type = SearchParamType.TOKEN, documentation = "The business version of the library") public class LibraryVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "library"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureDependsOn.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureDependsOn.java index 1150b8563..ac9685299 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureDependsOn.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureDependsOn.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; import org.hl7.fhir.r4.model.Library; @@ -21,13 +22,19 @@ @SearchParameterDefinition(name = MeasureDependsOn.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Measure-depends-on", type = SearchParamType.REFERENCE, documentation = "What resource is being referenced") public class MeasureDependsOn extends AbstractCanonicalReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "Measure"; + public static final String RESOURCE_TYPE_NAME = "Measure"; public static final String PARAMETER_NAME = "depends-on"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Library"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Library"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } public MeasureDependsOn() { - super(Measure.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(Measure.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureStatus.java index fb1f9f156..853544618 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureStatus.java @@ -6,11 +6,13 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = MeasureStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Measure-status", type = SearchParamType.TOKEN, documentation = "The current status of the measure") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Measure-status", type = SearchParamType.TOKEN, documentation = "The current status of the measure") public class MeasureStatus extends AbstractStatusParameter { + public static final String RESOURCE_COLUMN = "measure"; + public MeasureStatus() { - super("measure", Measure.class); + super(RESOURCE_COLUMN, Measure.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureVersion.java index f9dcbdcb7..e930cff4f 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/MeasureVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = MeasureVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Measure-version", type = SearchParamType.TOKEN, documentation = "The business version of the measure") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Measure-version", type = SearchParamType.TOKEN, documentation = "The business version of the measure") public class MeasureVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "measure"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/NamingSystemStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/NamingSystemStatus.java index c9dc2fe02..724320039 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/NamingSystemStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/NamingSystemStatus.java @@ -6,11 +6,13 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = NamingSystemStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/NamingSystem-status", type = SearchParamType.TOKEN, documentation = "The current status of the naming system") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/NamingSystem-status", type = SearchParamType.TOKEN, documentation = "The current status of the naming system") public class NamingSystemStatus extends AbstractStatusParameter { + public static final String RESOURCE_COLUMN = "naming_system"; + public NamingSystemStatus() { - super("naming_system", NamingSystem.class); + super(RESOURCE_COLUMN, NamingSystem.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationEndpoint.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationEndpoint.java index c3eb82827..34a03c255 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationEndpoint.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationEndpoint.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,13 +28,19 @@ @SearchParameterDefinition(name = OrganizationAffiliationEndpoint.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/OrganizationAffiliation-endpoint", type = SearchParamType.REFERENCE, documentation = "Technical endpoints providing access to services operated for this role") public class OrganizationAffiliationEndpoint extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; + public static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; public static final String PARAMETER_NAME = "endpoint"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Endpoint"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Endpoint"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } public OrganizationAffiliationEndpoint() { - super(OrganizationAffiliation.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(OrganizationAffiliation.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationParticipatingOrganization.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationParticipatingOrganization.java index f3b8a8aff..258ca6c72 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationParticipatingOrganization.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationParticipatingOrganization.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -28,16 +29,22 @@ public class OrganizationAffiliationParticipatingOrganization extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; + public static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; public static final String PARAMETER_NAME = "participating-organization"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } private static final String IDENTIFIERS_SUBQUERY = "(SELECT organization->'identifier' FROM current_organizations" + " WHERE concat('Organization/', organization->>'id') = organization_affiliation->'participatingOrganization'->>'reference')"; public OrganizationAffiliationParticipatingOrganization() { - super(OrganizationAffiliation.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(OrganizationAffiliation.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationPrimaryOrganization.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationPrimaryOrganization.java index 85df18adb..cdc5e59e4 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationPrimaryOrganization.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationAffiliationPrimaryOrganization.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,16 +28,22 @@ @SearchParameterDefinition(name = OrganizationAffiliationPrimaryOrganization.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/OrganizationAffiliation-primary-organization", type = SearchParamType.REFERENCE, documentation = "The organization that receives the services from the participating organization") public class OrganizationAffiliationPrimaryOrganization extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; + public static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; public static final String PARAMETER_NAME = "primary-organization"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } private static final String IDENTIFIERS_SUBQUERY = "(SELECT organization->'identifier' FROM current_organizations" + " WHERE concat('Organization/', organization->>'id') = organization_affiliation->'organization'->>'reference')"; public OrganizationAffiliationPrimaryOrganization() { - super(OrganizationAffiliation.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(OrganizationAffiliation.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationEndpoint.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationEndpoint.java index 99b968c3e..7854c9676 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationEndpoint.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/OrganizationEndpoint.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,13 +28,19 @@ @SearchParameterDefinition(name = OrganizationEndpoint.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Organization-endpoint", type = SearchParamType.REFERENCE, documentation = "Technical endpoints providing access to services operated for the organization") public class OrganizationEndpoint extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "Organization"; + public static final String RESOURCE_TYPE_NAME = "Organization"; public static final String PARAMETER_NAME = "endpoint"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Endpoint"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Endpoint"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } public OrganizationEndpoint() { - super(Organization.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(Organization.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRoleOrganization.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRoleOrganization.java index 592bc0507..00ec7c0cf 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRoleOrganization.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRoleOrganization.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,16 +28,22 @@ @SearchParameterDefinition(name = PractitionerRoleOrganization.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/PractitionerRole-organization", type = SearchParamType.REFERENCE, documentation = "The identity of the organization the practitioner represents / acts on behalf of") public class PractitionerRoleOrganization extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "PractitionerRole"; + public static final String RESOURCE_TYPE_NAME = "PractitionerRole"; public static final String PARAMETER_NAME = "organization"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } private static final String PRACTITIONER_IDENTIFIERS_SUBQUERY = "(SELECT organization->'identifier' FROM current_organizations" + " WHERE concat('Organization/', organization->>'id') = practitioner_role->'organization'->>'reference')"; public PractitionerRoleOrganization() { - super(PractitionerRole.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(PractitionerRole.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRolePractitioner.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRolePractitioner.java index 933bf815b..c1f26b1f4 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRolePractitioner.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/PractitionerRolePractitioner.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,16 +28,22 @@ @SearchParameterDefinition(name = PractitionerRolePractitioner.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/PractitionerRole-practitioner", type = SearchParamType.REFERENCE, documentation = "Practitioner that is able to provide the defined services for the organization") public class PractitionerRolePractitioner extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "PractitionerRole"; + public static final String RESOURCE_TYPE_NAME = "PractitionerRole"; public static final String PARAMETER_NAME = "practitioner"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Practitioner"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Practitioner"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } private static final String PRACTITIONER_IDENTIFIERS_SUBQUERY = "(SELECT practitioner->'identifier' FROM current_practitioners" + " WHERE concat('Practitioner/', practitioner->>'id') = practitioner_role->'practitioner'->>'reference')"; public PractitionerRolePractitioner() { - super(PractitionerRole.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(PractitionerRole.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseQuestionnaire.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseQuestionnaire.java index 6741a50f4..348083ed6 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseQuestionnaire.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseQuestionnaire.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; import org.hl7.fhir.r4.model.Questionnaire; @@ -21,13 +22,19 @@ @SearchParameterDefinition(name = QuestionnaireResponseQuestionnaire.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/QuestionnaireResponse-questionnaire", type = SearchParamType.REFERENCE, documentation = "The questionnaire the answers are provided for") public class QuestionnaireResponseQuestionnaire extends AbstractCanonicalReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "QuestionnaireResponse"; + public static final String RESOURCE_TYPE_NAME = "QuestionnaireResponse"; public static final String PARAMETER_NAME = "questionnaire"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Questionnaire"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Questionnaire"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } public QuestionnaireResponseQuestionnaire() { - super(QuestionnaireResponse.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(QuestionnaireResponse.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseStatus.java index 46635df11..732dd8b8e 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseStatus.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.exceptions.FHIRException; @@ -18,12 +17,12 @@ import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = QuestionnaireResponseStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/QuestionnaireResponse-status", type = SearchParamType.TOKEN, documentation = "The status of the questionnaire response") public class QuestionnaireResponseStatus extends AbstractTokenParameter { public static final String PARAMETER_NAME = "status"; + public static final String RESOURCE_COLUMN = "questionnaire_response"; private QuestionnaireResponse.QuestionnaireResponseStatus status; @@ -33,15 +32,17 @@ public QuestionnaireResponseStatus() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) - status = toStatus(valueAndType.codeValue, queryParameters.get(parameterName)); + status = toStatus(errors, valueAndType.codeValue, queryParameterValue); } - private QuestionnaireResponse.QuestionnaireResponseStatus toStatus(String status, List parameterValues) + private QuestionnaireResponse.QuestionnaireResponseStatus toStatus(List errors, + String status, String queryParameterValue) { if (status == null || status.isBlank()) return null; @@ -52,8 +53,8 @@ private QuestionnaireResponse.QuestionnaireResponseStatus toStatus(String status } catch (FHIRException e) { - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, - parameterValues, e)); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + queryParameterValue, e)); return null; } } @@ -67,7 +68,7 @@ public boolean isDefined() @Override public String getFilterQuery() { - return "questionnaire_response->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -84,11 +85,12 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), status.toCode()); + return status.toCode(); } + @Override public boolean matches(Resource resource) { @@ -107,6 +109,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "questionnaire_response->>'status'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->>'status'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseSubject.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseSubject.java index 0927b97d3..bc27129c3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseSubject.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireResponseSubject.java @@ -5,6 +5,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -32,12 +33,18 @@ @SearchParameterDefinition(name = QuestionnaireResponseSubject.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/QuestionnaireResponse-subject", type = SearchParamType.REFERENCE, documentation = "The subject of the questionnaire response") public class QuestionnaireResponseSubject extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "QuestionnaireResponse"; + public static final String RESOURCE_TYPE_NAME = "QuestionnaireResponse"; public static final String PARAMETER_NAME = "subject"; // TODO if needed, modify for Reference(Any), see also doResolveReferencesForMatching, matches, getIncludeSql private static final String[] TARGET_RESOURCE_TYPE_NAMES = { "Organization", "Practitioner", "PractitionerRole" }; + public static List getIncludeParameterValues() + { + return Arrays.stream(TARGET_RESOURCE_TYPE_NAMES) + .map(target -> RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + target).toList(); + } + private static final String IDENTIFIERS_SUBQUERY = "(SELECT practitioner->'identifier' FROM current_practitioners " + "WHERE concat('Practitioner/', practitioner->>'id') = questionnaire_response->'subject'->>'reference' " + "UNION SELECT organization->'identifier' FROM current_organizations " @@ -47,7 +54,7 @@ public class QuestionnaireResponseSubject extends AbstractReferenceParameter { + public static final String RESOURCE_COLUMN = "questionnaire"; + public QuestionnaireStatus() { - super("questionnaire", Questionnaire.class); + super(RESOURCE_COLUMN, Questionnaire.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireVersion.java index 6c027e8dc..f8ab506c8 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/QuestionnaireVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = QuestionnaireVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Questionnaire-version", type = SearchParamType.TOKEN, documentation = "The business version of the questionnaire") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Questionnaire-version", type = SearchParamType.TOKEN, documentation = "The business version of the questionnaire") public class QuestionnaireVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "questionnaire"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyEnrollment.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyEnrollment.java index 6d4358791..dd9083f62 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyEnrollment.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyEnrollment.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -27,13 +28,19 @@ @SearchParameterDefinition(name = ResearchStudyEnrollment.PARAMETER_NAME, definition = "http://dsf.dev/fhir/SearchParameter/ResearchStudy-enrollment", type = SearchParamType.REFERENCE, documentation = "Search by research study enrollment") public class ResearchStudyEnrollment extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "ResearchStudy"; + public static final String RESOURCE_TYPE_NAME = "ResearchStudy"; public static final String PARAMETER_NAME = "enrollment"; - private static final String TARGET_RESOURCE_TYPE_NAME = "Group"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Group"; + + public static List getIncludeParameterValues() + { + return List.of(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); + } public ResearchStudyEnrollment() { - super(ResearchStudy.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); + super(ResearchStudy.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAME); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyPrincipalInvestigator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyPrincipalInvestigator.java index 2431928aa..26c36f91a 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyPrincipalInvestigator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResearchStudyPrincipalInvestigator.java @@ -5,6 +5,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -30,9 +31,15 @@ @SearchParameterDefinition(name = ResearchStudyPrincipalInvestigator.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ResearchStudy-principalinvestigator", type = SearchParamType.REFERENCE, documentation = "Researcher who oversees multiple aspects of the study") public class ResearchStudyPrincipalInvestigator extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "ResearchStudy"; + public static final String RESOURCE_TYPE_NAME = "ResearchStudy"; public static final String PARAMETER_NAME = "principalinvestigator"; - private static final String[] TARGET_RESOURCE_TYPE_NAMES = { "Practitioner", "PractitionerRole" }; + public static final String[] TARGET_RESOURCE_TYPE_NAMES = { "Practitioner", "PractitionerRole" }; + + public static List getIncludeParameterValues() + { + return Arrays.stream(TARGET_RESOURCE_TYPE_NAMES) + .map(target -> RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + target).toList(); + } private static final String IDENTIFIERS_SUBQUERY = "(SELECT practitioner->'identifier' FROM current_practitioners " + "WHERE concat('Practitioner/', practitioner->>'id') = research_study->'principalInvestigator'->>'reference' " @@ -41,7 +48,7 @@ public class ResearchStudyPrincipalInvestigator extends AbstractReferenceParamet public ResearchStudyPrincipalInvestigator() { - super(ResearchStudy.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAMES); + super(ResearchStudy.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAMES); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceId.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceId.java index 32908d5d0..32ac6a201 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceId.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceId.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -18,7 +17,6 @@ import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; import dev.dsf.fhir.search.parameters.basic.AbstractSearchParameter; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = ResourceId.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Resource-id", type = SearchParamType.STRING, documentation = "Logical id of this resource") public class ResourceId extends AbstractSearchParameter @@ -36,37 +34,29 @@ public ResourceId(String resourceIdColumn) } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - String firstValue = getFirst(queryParameters, PARAMETER_NAME); - - if (firstValue == null) - return; // parameter not defined - - id = toId(firstValue, queryParameters.get(PARAMETER_NAME)); + id = toId(errors, queryParameterValue); } - private UUID toId(String firstValue, List values) + private UUID toId(List errors, String value) { - if (values != null && values.size() > 1) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - PARAMETER_NAME, values)); - - if (firstValue.isBlank()) + if (value.isBlank()) { - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, PARAMETER_NAME, - values)); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, PARAMETER_NAME, + value)); return null; } try { - return UUID.fromString(firstValue); + return UUID.fromString(value); } catch (IllegalArgumentException e) { - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, PARAMETER_NAME, - values, e)); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, PARAMETER_NAME, + value, e)); return null; } } @@ -115,9 +105,15 @@ private PGobject asUuidPgObject(UUID uuid) } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterName() + { + return PARAMETER_NAME; + } + + @Override + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME, id.toString()); + return id.toString(); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceLastUpdated.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceLastUpdated.java index 99ea9ccbe..3f69dc273 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceLastUpdated.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceLastUpdated.java @@ -1,13 +1,13 @@ package dev.dsf.fhir.search.parameters; -import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; +import org.hl7.fhir.r4.model.Resource; import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractDateTimeParameter; @SearchParameterDefinition(name = ResourceLastUpdated.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Resource-lastUpdated", type = SearchParamType.DATE, documentation = "When the resource version last changed") -public class ResourceLastUpdated extends AbstractDateTimeParameter +public class ResourceLastUpdated extends AbstractDateTimeParameter { public static final String PARAMETER_NAME = "_lastUpdated"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceProfile.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceProfile.java index 872ced255..57b9b91b3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceProfile.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ResourceProfile.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; -import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; import org.hl7.fhir.r4.model.Resource; @@ -13,7 +12,7 @@ import dev.dsf.fhir.search.parameters.basic.AbstractCanonicalUrlParameter; @SearchParameterDefinition(name = ResourceProfile.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Resource-profile", type = SearchParamType.TOKEN, documentation = "Profiles this resource claims to conform to") -public class ResourceProfile extends AbstractCanonicalUrlParameter +public class ResourceProfile extends AbstractCanonicalUrlParameter { public static final String PARAMETER_NAME = "_profile"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SearchQuerySortParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SearchQuerySortParameter.java new file mode 100644 index 000000000..1bb1f5f83 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SearchQuerySortParameter.java @@ -0,0 +1,19 @@ +package dev.dsf.fhir.search.parameters; + +import java.util.List; + +import dev.dsf.fhir.search.SearchQueryParameterError; +import dev.dsf.fhir.search.SearchQuerySortParameterConfiguration; + +public interface SearchQuerySortParameter +{ + /** + * @param errors + * not null + * @param queryParameterSortValue + * one of (parameterName, +parameterName or -parameterName), not null and not blank + * @return + */ + SearchQuerySortParameterConfiguration configureSort(List errors, + String queryParameterSortValue); +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionStatus.java index 37c166d1c..772116713 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionStatus.java @@ -6,7 +6,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = StructureDefinitionStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/StructureDefinition-status", type = SearchParamType.TOKEN, documentation = "The current status of the structure definition") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/StructureDefinition-status", type = SearchParamType.TOKEN, documentation = "The current status of the structure definition") public class StructureDefinitionStatus extends AbstractStatusParameter { public static final String RESOURCE_COLUMN = "structure_definition"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionVersion.java index 738b57da6..7bd82e20a 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/StructureDefinitionVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = StructureDefinitionVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/StructureDefinition-version", type = SearchParamType.TOKEN, documentation = "The business version of the structure definition") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/StructureDefinition-version", type = SearchParamType.TOKEN, documentation = "The business version of the structure definition") public class StructureDefinitionVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "structure_definition"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionPayload.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionPayload.java index e8b29e734..b7c74f0de 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionPayload.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionPayload.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; @@ -13,14 +12,15 @@ import dev.dsf.fhir.function.BiFunctionWithSqlException; import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; +import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = SubscriptionPayload.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Subscription-payload", type = SearchParamType.TOKEN, documentation = "The mime-type of the notification payload") public class SubscriptionPayload extends AbstractTokenParameter { public static final String PARAMETER_NAME = "payload"; + public static final String RESOURCE_COLUMN = "subscription"; private String payloadMimeType; @@ -30,9 +30,10 @@ public SubscriptionPayload() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) payloadMimeType = valueAndType.codeValue; @@ -47,7 +48,7 @@ public boolean isDefined() @Override public String getFilterQuery() { - return "subscription->'channel'->>'payload' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->'channel'->>'payload' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -64,9 +65,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), payloadMimeType); + return payloadMimeType; } @Override @@ -87,6 +88,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "subscription->'channel'->>'payload'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->'channel'->>'payload'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionStatus.java index 64e7d4e95..a9c9eaca6 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionStatus.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.exceptions.FHIRException; @@ -14,14 +13,16 @@ import dev.dsf.fhir.function.BiFunctionWithSqlException; import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; +import dev.dsf.fhir.search.SearchQueryParameterError; +import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = SubscriptionStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Subscription-status", type = SearchParamType.TOKEN, documentation = "Search by subscription status") public class SubscriptionStatus extends AbstractTokenParameter { public static final String PARAMETER_NAME = "status"; + public static final String RESOURCE_COLUMN = "subscription"; private org.hl7.fhir.r4.model.Subscription.SubscriptionStatus status; @@ -31,15 +32,17 @@ public SubscriptionStatus() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) - status = toStatus(valueAndType.codeValue); + status = toStatus(errors, valueAndType.codeValue, queryParameterValue); } - private org.hl7.fhir.r4.model.Subscription.SubscriptionStatus toStatus(String status) + private org.hl7.fhir.r4.model.Subscription.SubscriptionStatus toStatus( + List errors, String status, String queryParameterValue) { if (status == null || status.isBlank()) return null; @@ -50,6 +53,8 @@ private org.hl7.fhir.r4.model.Subscription.SubscriptionStatus toStatus(String st } catch (FHIRException e) { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + queryParameterValue, e)); return null; } } @@ -63,7 +68,7 @@ public boolean isDefined() @Override public String getFilterQuery() { - return "subscription->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -80,9 +85,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), status.toCode()); + return status.toCode(); } @Override @@ -103,6 +108,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "subscription->>'status'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->>'status'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionType.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionType.java index ddf48a766..22570decd 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionType.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/SubscriptionType.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.exceptions.FHIRException; @@ -14,14 +13,16 @@ import dev.dsf.fhir.function.BiFunctionWithSqlException; import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; +import dev.dsf.fhir.search.SearchQueryParameterError; +import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = SubscriptionType.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Subscription-type", type = SearchParamType.TOKEN, documentation = "The type of channel for the sent notifications") public class SubscriptionType extends AbstractTokenParameter { public static final String PARAMETER_NAME = "type"; + public static final String RESOURCE_COLUMN = "subscription"; private org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType channelType; @@ -31,15 +32,17 @@ public SubscriptionType() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) - channelType = toChannelType(valueAndType.codeValue); + channelType = toChannelType(errors, valueAndType.codeValue, queryParameterValue); } - private org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType toChannelType(String status) + private org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType toChannelType( + List errors, String status, String queryParameterValue) { if (status == null || status.isBlank()) return null; @@ -50,6 +53,8 @@ private org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType toChannelType } catch (FHIRException e) { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + queryParameterValue, e)); return null; } } @@ -63,7 +68,7 @@ public boolean isDefined() @Override public String getFilterQuery() { - return "subscription->'channel'->>'type' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->'channel'->>'type' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -80,9 +85,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), channelType.toCode()); + return channelType.toCode(); } @Override @@ -103,6 +108,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "subscription->'channel'->>'type'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->'channel'->>'type'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskRequester.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskRequester.java index a236dab2d..889a176a9 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskRequester.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskRequester.java @@ -5,6 +5,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; import java.util.UUID; import org.hl7.fhir.instance.model.api.IIdType; @@ -33,12 +34,18 @@ @SearchParameterDefinition(name = TaskRequester.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Task-requester", type = SearchParamType.REFERENCE, documentation = "Search by task requester") public class TaskRequester extends AbstractReferenceParameter { - private static final String RESOURCE_TYPE_NAME = "Task"; + public static final String RESOURCE_TYPE_NAME = "Task"; public static final String PARAMETER_NAME = "requester"; - private static final String[] TARGET_RESOURCE_TYPE_NAMES = { "Practitioner", "Organization", "Patient", + public static final String[] TARGET_RESOURCE_TYPE_NAMES = { "Practitioner", "Organization", "Patient", "PractitionerRole" }; // TODO add Device, RelatedPerson if supported, see also doResolveReferencesForMatching, matches, getIncludeSql + public static List getIncludeParameterValues() + { + return Arrays.stream(TARGET_RESOURCE_TYPE_NAMES) + .map(target -> RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + target).toList(); + } + private static final String IDENTIFIERS_SUBQUERY = "(SELECT practitioner->'identifier' FROM current_practitioners " + "WHERE concat('Practitioner/', practitioner->>'id') = task->'requester'->>'reference' " + "UNION SELECT organization->'identifier' FROM current_organizations " @@ -50,7 +57,7 @@ public class TaskRequester extends AbstractReferenceParameter public TaskRequester() { - super(Task.class, RESOURCE_TYPE_NAME, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAMES); + super(Task.class, PARAMETER_NAME, TARGET_RESOURCE_TYPE_NAMES); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskStatus.java index 7a9ece997..03a481190 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/TaskStatus.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.exceptions.FHIRException; @@ -18,12 +17,12 @@ import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; import dev.dsf.fhir.search.parameters.basic.AbstractTokenParameter; import dev.dsf.fhir.search.parameters.basic.TokenSearchType; -import jakarta.ws.rs.core.UriBuilder; @SearchParameterDefinition(name = TaskStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/Task-status", type = SearchParamType.TOKEN, documentation = "Search by task status") public class TaskStatus extends AbstractTokenParameter { public static final String PARAMETER_NAME = "status"; + public static final String RESOURCE_COLUMN = "task"; private org.hl7.fhir.r4.model.Task.TaskStatus status; @@ -33,15 +32,17 @@ public TaskStatus() } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) - status = toStatus(valueAndType.codeValue, queryParameters.get(parameterName)); + status = toStatus(errors, valueAndType.codeValue, queryParameterValue); } - private org.hl7.fhir.r4.model.Task.TaskStatus toStatus(String status, List parameterValues) + private org.hl7.fhir.r4.model.Task.TaskStatus toStatus(List errors, + String status, String queryParameterValue) { if (status == null || status.isBlank()) return null; @@ -52,8 +53,8 @@ private org.hl7.fhir.r4.model.Task.TaskStatus toStatus(String status, List>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; + return RESOURCE_COLUMN + "->>'status' " + (valueAndType.negated ? "<>" : "=") + " ?"; } @Override @@ -84,9 +85,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), status.toCode()); + return status.toCode(); } @Override @@ -107,6 +108,6 @@ public boolean matches(Resource resource) @Override protected String getSortSql(String sortDirectionWithSpacePrefix) { - return "task->>'status'" + sortDirectionWithSpacePrefix; + return RESOURCE_COLUMN + "->>'status'" + sortDirectionWithSpacePrefix; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetStatus.java index 18035ba06..c342d16e6 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetStatus.java @@ -6,11 +6,13 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractStatusParameter; -@SearchParameterDefinition(name = ValueSetStatus.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ValueSet-status", type = SearchParamType.TOKEN, documentation = "The current status of the value set") +@SearchParameterDefinition(name = AbstractStatusParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ValueSet-status", type = SearchParamType.TOKEN, documentation = "The current status of the value set") public class ValueSetStatus extends AbstractStatusParameter { + public static final String RESOURCE_COLUMN = "value_set"; + public ValueSetStatus() { - super("value_set", ValueSet.class); + super(RESOURCE_COLUMN, ValueSet.class); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetVersion.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetVersion.java index 0a3a35a14..812f439d5 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetVersion.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/ValueSetVersion.java @@ -7,7 +7,7 @@ import dev.dsf.fhir.search.SearchQueryParameter.SearchParameterDefinition; import dev.dsf.fhir.search.parameters.basic.AbstractVersionParameter; -@SearchParameterDefinition(name = ValueSetVersion.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ValueSet-version", type = SearchParamType.TOKEN, documentation = "The business version of the value set") +@SearchParameterDefinition(name = AbstractVersionParameter.PARAMETER_NAME, definition = "http://hl7.org/fhir/SearchParameter/ValueSet-version", type = SearchParamType.TOKEN, documentation = "The business version of the value set") public class ValueSetVersion extends AbstractVersionParameter { public static final String RESOURCE_COLUMN = "value_set"; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractBooleanParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractBooleanParameter.java index 2210c690c..4d373ff8c 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractBooleanParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractBooleanParameter.java @@ -1,15 +1,11 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.Collections; import java.util.List; -import java.util.Map; import org.hl7.fhir.r4.model.DomainResource; import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import dev.dsf.fhir.search.parameters.basic.AbstractCanonicalUrlParameter.UriSearchType; -import jakarta.ws.rs.core.UriBuilder; public abstract class AbstractBooleanParameter extends AbstractSearchParameter { @@ -21,18 +17,12 @@ public AbstractBooleanParameter(String parameterName) } @Override - protected void configureSearchParameter(Map> queryParameters) + public void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - List values = queryParameters.getOrDefault(parameterName + UriSearchType.PRECISE.modifier, - Collections.emptyList()); - if (values.size() > 1) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, values)); - - String param = getFirst(queryParameters, parameterName); - if (param != null && !param.isEmpty()) + if (queryParameterValue != null && !queryParameterValue.isEmpty()) { - switch (param) + switch (queryParameterValue) { case "true": value = true; @@ -42,8 +32,8 @@ protected void configureSearchParameter(Map> queryParameter break; default: value = null; - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, values, "true or false expected")); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue, "true or false expected")); break; } } @@ -56,9 +46,14 @@ public boolean isDefined() } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterName() + { + return parameterName; + } + + @Override + public String getBundleUriQueryParameterValue() { - if (isDefined()) - bundleUri.replaceQueryParam(parameterName, String.valueOf(value)); + return String.valueOf(value); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalReferenceParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalReferenceParameter.java index ec4a3bd6b..94b8c5fdf 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalReferenceParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalReferenceParameter.java @@ -1,8 +1,6 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.Collections; import java.util.List; -import java.util.Map; import org.hl7.fhir.r4.model.DomainResource; @@ -12,16 +10,17 @@ public abstract class AbstractCanonicalReferenceParameter extends AbstractReferenceParameter { - public AbstractCanonicalReferenceParameter(Class resourceType, String resourceTypeName, String parameterName, + public AbstractCanonicalReferenceParameter(Class resourceType, String parameterName, String... targetResourceTypeNames) { - super(resourceType, resourceTypeName, parameterName, targetResourceTypeNames); + super(resourceType, parameterName, targetResourceTypeNames); } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type != null) switch (valueAndType.type) @@ -32,45 +31,12 @@ protected void configureSearchParameter(Map> queryParameter case ID: case TYPE_AND_ID: - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, Collections.singletonList(valueAndType.id))); - return; case IDENTIFIER: - { - if (valueAndType.identifier != null && valueAndType.identifier.type != null) - switch (valueAndType.identifier.type) - { - case CODE: - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, Collections.singletonList(valueAndType.identifier.codeValue))); - return; - case CODE_AND_NO_SYSTEM_PROPERTY: - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, - Collections.singletonList("|" + valueAndType.identifier.codeValue))); - return; - case CODE_AND_SYSTEM: - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, Collections.singletonList(valueAndType.identifier.systemValue - + "|" + valueAndType.identifier.codeValue))); - return; - case SYSTEM: - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, - Collections.singletonList(valueAndType.identifier.systemValue + "|"))); - return; - default: - return; - } - return; - } case RESOURCE_NAME_AND_ID: case TYPE_AND_RESOURCE_NAME_AND_ID: - addError( - new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, - Collections.singletonList(valueAndType.resourceName + "/" + valueAndType.id))); - return; default: + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue)); return; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalUrlParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalUrlParameter.java index 3e50a9838..3496a13b5 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalUrlParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractCanonicalUrlParameter.java @@ -1,18 +1,13 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.stream.Stream; -import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Resource; import dev.dsf.fhir.search.SearchQueryParameterError; -import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import jakarta.ws.rs.core.UriBuilder; -public abstract class AbstractCanonicalUrlParameter extends AbstractSearchParameter +public abstract class AbstractCanonicalUrlParameter extends AbstractSearchParameter { public static enum UriSearchType { @@ -26,6 +21,11 @@ private UriSearchType(String modifier) } } + public static List getNameModifiers() + { + return Collections.singletonList(UriSearchType.BELOW.modifier); + } + protected static class CanonicalUrlAndSearchType { public final String url; @@ -48,51 +48,23 @@ public AbstractCanonicalUrlParameter(String parameterName) } @Override - protected Stream getModifiedParameterNames() + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - return Stream.of(getParameterName() + UriSearchType.BELOW.modifier); - } - - @Override - protected final void configureSearchParameter(Map> queryParameters) - { - List allValues = new ArrayList<>(); - allValues.addAll( - queryParameters.getOrDefault(parameterName + UriSearchType.PRECISE.modifier, Collections.emptyList())); - allValues.addAll( - queryParameters.getOrDefault(parameterName + UriSearchType.BELOW.modifier, Collections.emptyList())); - if (allValues.size() > 1) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, allValues)); - - String precise = getFirst(queryParameters, parameterName + UriSearchType.PRECISE.modifier); - if (precise != null) - { - valueAndType = toValueAndType(precise, UriSearchType.PRECISE); - return; - } - - String below = getFirst(queryParameters, parameterName + UriSearchType.BELOW.modifier); - if (below != null) - { - valueAndType = toValueAndType(below, UriSearchType.BELOW); - return; - } - + if ((parameterName + UriSearchType.PRECISE.modifier).equals(queryParameterName)) + valueAndType = toValueAndType(queryParameterValue, UriSearchType.PRECISE); + else if ((parameterName + UriSearchType.BELOW.modifier).equals(queryParameterName)) + valueAndType = toValueAndType(queryParameterValue, UriSearchType.BELOW); // TODO - // String above = queryParameters.getFirst(parameterName + UriSearchType.ABOVE.modifier); - // if (above != null && !above.isBlank()) - // { - // valueAndType = new UriValueAndSearchType(above, UriSearchType.ABOVE); - // return; - // } + // else if ((parameterName + UriSearchType.ABOVE.modifier).equals(queryParameterName)) + // valueAndType = toValueAndType(queryParameterValue, UriSearchType.ABOVE); } - protected static CanonicalUrlAndSearchType toValueAndType(String parameter, UriSearchType type) + private CanonicalUrlAndSearchType toValueAndType(String value, UriSearchType type) { - if (parameter != null && !parameter.isBlank()) + if (value != null && !value.isBlank()) { - String[] split = parameter.split("[|]"); + String[] split = value.split("[|]"); if (split.length == 1) return new CanonicalUrlAndSearchType(split[0], null, type); else if (split.length == 2) @@ -114,9 +86,14 @@ protected boolean hasVersion() } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterName() + { + return parameterName + valueAndType.type.modifier; + } + + @Override + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(parameterName + valueAndType.type.modifier, - valueAndType.url + (hasVersion() ? ("|" + valueAndType.version) : "")); + return valueAndType.url + (hasVersion() ? ("|" + valueAndType.version) : ""); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractDateTimeParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractDateTimeParameter.java index 3d4a6149f..7cdc8a5f1 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractDateTimeParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractDateTimeParameter.java @@ -10,25 +10,18 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; import java.util.List; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Resource; import dev.dsf.fhir.function.BiFunctionWithSqlException; import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import jakarta.ws.rs.core.UriBuilder; -public abstract class AbstractDateTimeParameter extends AbstractSearchParameter +public abstract class AbstractDateTimeParameter extends AbstractSearchParameter { public static enum DateTimeSearchType { @@ -89,10 +82,9 @@ public String toString() private static final DateTimeFormatter YEAR_FORMAT = DateTimeFormatter.ofPattern("yyyy"); private static final DateTimeFormatter YEAR_MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM"); - private List valuesAndTypes = new ArrayList<>(); + protected DateTimeValueAndTypeAndSearchType valueAndType; private final String timestampColumn; - private final List values = new ArrayList<>(); public AbstractDateTimeParameter(String parameterName, String timestampColumn) { @@ -102,56 +94,14 @@ public AbstractDateTimeParameter(String parameterName, String timestampColumn) } @Override - protected final void configureSearchParameter(Map> queryParameters) - { - List parameters = queryParameters.getOrDefault(parameterName, Collections.emptyList()); - - parameters.stream().limit(2).map(value -> parse(value, parameters)).filter(v -> v != null) - .collect(Collectors.toCollection(() -> valuesAndTypes)); - - DateTimeValueAndTypeAndSearchType first = valuesAndTypes.size() < 1 ? null : valuesAndTypes.get(0); - DateTimeValueAndTypeAndSearchType second = valuesAndTypes.size() < 2 ? null : valuesAndTypes.get(1); - - if (parameters.size() > 2) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, parameters, "More than two " + parameterName + " values")); - else if (valuesAndTypes.size() == 2) - { - // if two search operators, only for example lt and gt are allowed in combination - if (!((EnumSet.of(DateTimeSearchType.GE, DateTimeSearchType.GT).contains(first.searchType) - && EnumSet.of(DateTimeSearchType.LE, DateTimeSearchType.LT).contains(second.searchType)) - || (EnumSet.of(DateTimeSearchType.GE, DateTimeSearchType.GT).contains(second.searchType) - && EnumSet.of(DateTimeSearchType.LE, DateTimeSearchType.LT).contains(first.searchType)))) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, parameters, - "Seach operators " + first.searchType + " and " + second.searchType + " can't be combined")); - } - - if (valuesAndTypes.size() > 1 && (!((EnumSet.of(DateTimeSearchType.GE, DateTimeSearchType.GT) - .contains(first.searchType) - && EnumSet.of(DateTimeSearchType.LE, DateTimeSearchType.LT).contains(second.searchType)) - || (EnumSet.of(DateTimeSearchType.GE, DateTimeSearchType.GT).contains(second.searchType) - && EnumSet.of(DateTimeSearchType.LE, DateTimeSearchType.LT).contains(first.searchType))))) - { - valuesAndTypes.clear(); - valuesAndTypes.add(first); - } - - checkParameters(parameters); - } - - /** - * Override to perform additional parameter checks - * - * @param parameters - * to be checked, not null - * @see #addError(SearchQueryParameterError) - */ - protected void checkParameters(List parameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { + valueAndType = parse(errors, queryParameterValue); } - private DateTimeValueAndTypeAndSearchType parse(String parameterValue, List parameterValues) + private DateTimeValueAndTypeAndSearchType parse(List errors, + String parameterValue) { final String fixedParameterValue = parameterValue.replace(' ', '+'); @@ -160,16 +110,15 @@ private DateTimeValueAndTypeAndSearchType parse(String parameterValue, List parameterValues) + private DateTimeValueAndTypeAndSearchType parseValue(List errors, String value, + DateTimeSearchType searchType, String parameterValue) { try { @@ -227,110 +176,107 @@ private DateTimeValueAndTypeAndSearchType parseValue(String value, DateTimeSearc } } - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, - parameterValues, parameterValue + " not parsable")); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + parameterValue, parameterValue + " not parsable")); return null; } - /** - * @return list contains max 2 values - */ - public List getValuesAndTypes() + @Override + public boolean isDefined() { - return valuesAndTypes; + return valueAndType != null; } @Override - public boolean isDefined() + public String getBundleUriQueryParameterName() { - return !valuesAndTypes.isEmpty(); + return parameterName; } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(parameterName, - valuesAndTypes.stream().map(value -> value.searchType.prefix + toUrlValue(value)).toArray()); + return valueAndType.searchType.prefix + toUrlValue(valueAndType); } - private String toUrlValue(DateTimeValueAndTypeAndSearchType value) + protected final String toUrlValue(DateTimeValueAndTypeAndSearchType value) { - switch (value.type) + return switch (value.type) { - case ZONED_DATE_TIME: - return ((ZonedDateTime) value.value).format(DATE_TIME_FORMAT_OUT); - case LOCAL_DATE: - return ((LocalDate) value.value).format(DATE_FORMAT); - case YEAR_PERIOD: - return ((LocalDatePair) value.value).startInclusive.format(YEAR_FORMAT); - case YEAR_MONTH_PERIOD: - return ((LocalDatePair) value.value).startInclusive.format(YEAR_MONTH_FORMAT); - default: - return ""; - } + case ZONED_DATE_TIME -> ((ZonedDateTime) value.value).format(DATE_TIME_FORMAT_OUT); + case LOCAL_DATE -> ((LocalDate) value.value).format(DATE_FORMAT); + case YEAR_PERIOD -> ((LocalDatePair) value.value).startInclusive.format(YEAR_FORMAT); + case YEAR_MONTH_PERIOD -> ((LocalDatePair) value.value).startInclusive.format(YEAR_MONTH_FORMAT); + default -> throw new IllegalArgumentException( + "Unexpected " + DateTimeType.class.getName() + " value: " + value.type); + }; } @Override public String getFilterQuery() { - values.clear(); - - return getValuesAndTypes().stream().map(this::getSubquery).collect(Collectors.joining(" AND ")); - } - - private String getSubquery(DateTimeValueAndTypeAndSearchType value) - { - switch (value.type) + return switch (valueAndType.type) { - case ZONED_DATE_TIME: - return getSubquery((ZonedDateTime) value.value, value.searchType); - case LOCAL_DATE: - return getSubquery((LocalDate) value.value, value.searchType); - case YEAR_MONTH_PERIOD: - case YEAR_PERIOD: - return getSubquery((LocalDatePair) value.value); - default: - return ""; - } + case ZONED_DATE_TIME -> getDateTimeQuery(valueAndType.searchType.operator); + case LOCAL_DATE -> getDateQuery(valueAndType.searchType.operator); + case YEAR_MONTH_PERIOD, YEAR_PERIOD -> getDatePairQuery(); + default -> throw new IllegalArgumentException( + "Unexpected " + DateTimeType.class.getName() + " value: " + valueAndType.type); + }; } - private String getSubquery(ZonedDateTime value, DateTimeSearchType searchType) + private String getDateTimeQuery(String operator) { - values.add(value); - - return "(" + timestampColumn + ")::timestamp " + searchType.operator + " ?"; + return "(" + timestampColumn + ")::timestamp " + operator + " ?"; } - private String getSubquery(LocalDate value, DateTimeSearchType searchType) + private String getDateQuery(String operator) { - values.add(value); - - return "(" + timestampColumn + ")::date " + searchType.operator + " ?"; + return "(" + timestampColumn + ")::date " + operator + " ?"; } - private String getSubquery(LocalDatePair value) + private String getDatePairQuery() { - return getSubquery(value.startInclusive, DateTimeSearchType.GE) + " AND " - + getSubquery(value.endExclusive, DateTimeSearchType.LT); + return getDateQuery(DateTimeSearchType.GE.operator) + " AND " + getDateQuery(DateTimeSearchType.LT.operator); } @Override public int getSqlParameterCount() { - return values.size(); + return switch (valueAndType.type) + { + case ZONED_DATE_TIME, LOCAL_DATE -> 1; + case YEAR_MONTH_PERIOD, YEAR_PERIOD -> 2; + default -> throw new IllegalArgumentException( + "Unexpected " + DateTimeType.class.getName() + " value: " + valueAndType.type); + }; } @Override public void modifyStatement(int parameterIndex, int subqueryParameterIndex, PreparedStatement statement, BiFunctionWithSqlException arrayCreator) throws SQLException { - Object value = values.get(subqueryParameterIndex - 1); - - if (value instanceof ZonedDateTime) - statement.setTimestamp(parameterIndex, Timestamp - .valueOf(((ZonedDateTime) value).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime())); - else if (value instanceof LocalDate) - statement.setDate(parameterIndex, Date.valueOf((LocalDate) value)); + switch (valueAndType.type) + { + case ZONED_DATE_TIME: + statement.setTimestamp(parameterIndex, Timestamp.valueOf(((ZonedDateTime) valueAndType.value) + .withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime())); + break; + case LOCAL_DATE: + statement.setDate(parameterIndex, Date.valueOf((LocalDate) valueAndType.value)); + break; + case YEAR_MONTH_PERIOD: + case YEAR_PERIOD: + if (subqueryParameterIndex == 1) + statement.setDate(parameterIndex, + Date.valueOf(((LocalDatePair) valueAndType.value).startInclusive)); + if (subqueryParameterIndex == 2) + statement.setDate(parameterIndex, Date.valueOf(((LocalDatePair) valueAndType.value).endExclusive)); + break; + default: + throw new IllegalArgumentException( + "Unexpected " + DateTimeType.class.getName() + " value: " + valueAndType.type); + } } @Override @@ -340,7 +286,7 @@ public boolean matches(Resource resource) throw notDefined(); ZonedDateTime lastUpdated = toZonedDateTime(resource.getMeta().getLastUpdated()); - return lastUpdated != null && getValuesAndTypes().stream().allMatch(value -> matches(lastUpdated, value)); + return lastUpdated != null && matches(lastUpdated, valueAndType); } private ZonedDateTime toZonedDateTime(java.util.Date date) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractReferenceParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractReferenceParameter.java index 98380280f..e27339955 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractReferenceParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractReferenceParameter.java @@ -2,15 +2,10 @@ import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.DomainResource; @@ -18,15 +13,20 @@ import dev.dsf.fhir.dao.provider.DaoProvider; import dev.dsf.fhir.search.IncludeParts; -import dev.dsf.fhir.search.SearchQuery; import dev.dsf.fhir.search.SearchQueryIncludeParameter; +import dev.dsf.fhir.search.SearchQueryIncludeParameterConfiguration; import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import jakarta.ws.rs.core.UriBuilder; public abstract class AbstractReferenceParameter extends AbstractSearchParameter + implements SearchQueryIncludeParameter { - private static final String PARAMETER_NAME_IDENTIFIER_MODIFIER = ":identifier"; + public static final String PARAMETER_NAME_IDENTIFIER_MODIFIER = ":identifier"; + + public static List getNameModifiers() + { + return Collections.singletonList(PARAMETER_NAME_IDENTIFIER_MODIFIER); + } protected static enum ReferenceSearchType { @@ -58,251 +58,208 @@ private ReferenceValueAndSearchType(String resourceName, String id, String url, // // [parameter]=[url] -> absolute id or canonical // - // [parameter]:[Type]=[uuid] -> local type+id // [parameter]:identifier=[identifier] -> identifier (not supported for canonical references) - public static Optional fromParamValue(List targetResourceTypeNames, - String parameterName, Map> queryParameters, - Consumer errors) + // [parameter]:[Type]=[uuid] -> local type+id + public static ReferenceValueAndSearchType fromParamValue(List errors, + List targetResourceTypeNames, String parameterName, String queryParameterName, + String queryParameterValue) { - List allValues = new ArrayList<>(); - allValues.addAll(queryParameters.getOrDefault(parameterName, Collections.emptyList())); - allValues.addAll(queryParameters.getOrDefault(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER, - Collections.emptyList())); - allValues.addAll(targetResourceTypeNames.stream().flatMap( - type -> queryParameters.getOrDefault(parameterName + ":" + type, Collections.emptyList()).stream()) - .collect(Collectors.toList())); - - if (allValues.size() > 1) - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, allValues)); - else if (allValues.isEmpty()) - return Optional.empty(); - - final String value = allValues.get(0); - // simple case - if (queryParameters.containsKey(parameterName)) + if (parameterName.equals(queryParameterName)) { - if (value.indexOf('/') == -1 && targetResourceTypeNames.size() == 1) - return Optional.of(new ReferenceValueAndSearchType(targetResourceTypeNames.get(0), value, null, - null, ReferenceSearchType.ID)); - else if (value.indexOf('/') == -1 && targetResourceTypeNames.size() > 1) - return Optional - .of(new ReferenceValueAndSearchType(null, value, null, null, ReferenceSearchType.ID)); - else if (value.startsWith("http")) - return Optional - .of(new ReferenceValueAndSearchType(null, null, value, null, ReferenceSearchType.URL)); - else if (value.indexOf('/') >= 0) + if (queryParameterValue.indexOf('/') == -1 && targetResourceTypeNames.size() == 1) + return new ReferenceValueAndSearchType(targetResourceTypeNames.get(0), queryParameterValue, null, + null, ReferenceSearchType.ID); + else if (queryParameterValue.indexOf('/') == -1 && targetResourceTypeNames.size() > 1) + return new ReferenceValueAndSearchType(null, queryParameterValue, null, null, + ReferenceSearchType.ID); + else if (queryParameterValue.startsWith("http")) + return new ReferenceValueAndSearchType(null, null, queryParameterValue, null, + ReferenceSearchType.URL); + else if (queryParameterValue.indexOf('/') >= 0) { - String[] splitAtSlash = value.split("/"); - if (splitAtSlash.length == 2 - && targetResourceTypeNames.stream().anyMatch(name -> name.equals(splitAtSlash[0]))) - return Optional.of(new ReferenceValueAndSearchType(splitAtSlash[0], splitAtSlash[1], null, null, - ReferenceSearchType.RESOURCE_NAME_AND_ID)); + String[] splitAtSlash = queryParameterValue.split("/"); + if (splitAtSlash.length == 2) + { + + if (targetResourceTypeNames.stream().anyMatch(name -> name.equals(splitAtSlash[0]))) + return new ReferenceValueAndSearchType(splitAtSlash[0], splitAtSlash[1], null, null, + ReferenceSearchType.RESOURCE_NAME_AND_ID); + else + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue, "Unsupported target resource type name " + + splitAtSlash[0] + ", not one of " + targetResourceTypeNames)); + return null; + } + } else - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, queryParameters.get(parameterName), - "Unsupported target resource type name " + splitAtSlash[0] + ", not one of " - + targetResourceTypeNames)); + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue, + "Unsupported reference " + queryParameterValue + " not 'type/id'")); + return null; + } } - else - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, queryParameters.get(parameterName))); + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue)); + return null; + } } + + // identifier + // parameter:identifier=value + // parameter:identifier=system|value + else if ((parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER).equals(queryParameterName)) + { + TokenValueAndSearchType identifier = TokenValueAndSearchType.fromParamValue(parameterName, + queryParameterName, queryParameterValue); + return new ReferenceValueAndSearchType(null, null, null, identifier, ReferenceSearchType.IDENTIFIER); + } + // typed parameter - else if (targetResourceTypeNames.stream() - .anyMatch(type -> queryParameters.containsKey(parameterName + ":" + type))) + // parameter:type=id + // parameter:type=type/id + else { - final String paramType = targetResourceTypeNames.stream() - .filter(type -> queryParameters.containsKey(parameterName + ":" + type)).findFirst().get(); + Optional type = targetResourceTypeNames.stream() + .filter(t -> (parameterName + ":" + t).equals(queryParameterName)).findFirst(); - if (value.indexOf('/') == -1 && targetResourceTypeNames.size() == 1) + if (type.isPresent()) { - if (paramType.equals(targetResourceTypeNames.get(0))) - return Optional.of(new ReferenceValueAndSearchType(paramType, value, null, null, - ReferenceSearchType.TYPE_AND_ID)); + if (queryParameterValue.indexOf('/') == -1) + { + return new ReferenceValueAndSearchType(type.get(), queryParameterValue, null, null, + ReferenceSearchType.TYPE_AND_ID); + } else - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, queryParameters.get(parameterName), - "Unsupported target resource type name " + paramType + ", not equal to " - + targetResourceTypeNames.get(0))); - } - else if (value.indexOf('/') >= 0) - { - String[] splitAtSlash = value.split("/"); - if (splitAtSlash.length == 2 - && targetResourceTypeNames.stream().anyMatch(name -> name.equals(splitAtSlash[0]))) { - if (paramType.equals(splitAtSlash[0])) - return Optional.of(new ReferenceValueAndSearchType(splitAtSlash[0], splitAtSlash[1], null, - null, ReferenceSearchType.TYPE_AND_RESOURCE_NAME_AND_ID)); + String[] splitAtSlash = queryParameterValue.split("/"); + + if (splitAtSlash.length == 2) + { + if (type.get().equals(splitAtSlash[0])) + { + return new ReferenceValueAndSearchType(type.get(), splitAtSlash[1], null, null, + ReferenceSearchType.TYPE_AND_RESOURCE_NAME_AND_ID); + } + else + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue, "Inconsistent target resource type name " + + type.get() + " vs. " + splitAtSlash[0])); + return null; + } + } else - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, queryParameters.get(parameterName), - "Inconsistent target resource type name " + paramType + " vs. " + splitAtSlash[0])); + { + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue, + "Unsupported reference " + queryParameterValue + " not 'type/id'")); + return null; + } } - else - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, queryParameters.get(parameterName), - "Unsupported target resource type name " + splitAtSlash[0] + ", not one of " - + targetResourceTypeNames)); } - } - // identifier - else if (queryParameters.containsKey(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER)) - { - if (value != null && !value.isBlank()) - { - return TokenValueAndSearchType - .fromParamValue(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER, queryParameters, errors) - .map(identifier -> new ReferenceValueAndSearchType(null, null, null, identifier, - ReferenceSearchType.IDENTIFIER)); - } - else if (value == null || value.isBlank()) + else { - errors.accept(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, - parameterName, queryParameters.get(parameterName), "Value empty")); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, + parameterName, queryParameterValue, "Unsupported target resource type in " + + queryParameterName + " not one of " + targetResourceTypeNames)); + return null; } } - - return Optional.empty(); } } private final Class resourceType; - private final String resourceTypeName; private final List targetResourceTypeNames; - private final List includeParameters = new ArrayList<>(); - protected ReferenceValueAndSearchType valueAndType; - public AbstractReferenceParameter(Class resourceType, String resourceTypeName, String parameterName, - String... targetResourceTypeNames) + public AbstractReferenceParameter(Class resourceType, String parameterName, String... targetResourceTypeNames) { super(parameterName); this.resourceType = resourceType; - this.resourceTypeName = resourceTypeName; this.targetResourceTypeNames = Arrays.asList(targetResourceTypeNames); } @Override - protected Stream getModifiedParameterNames() + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - return Stream.of(getParameterName() + PARAMETER_NAME_IDENTIFIER_MODIFIER); + valueAndType = ReferenceValueAndSearchType.fromParamValue(errors, targetResourceTypeNames, parameterName, + queryParameterName, queryParameterValue); } @Override - protected void configureSearchParameter(Map> queryParameters) + public boolean isDefined() { - valueAndType = ReferenceValueAndSearchType - .fromParamValue(targetResourceTypeNames, parameterName, queryParameters, this::addError).orElse(null); + return valueAndType != null; } @Override - protected void configureIncludeParameter(Map> queryParameters) + public String getBundleUriQueryParameterName() { - List includeParts = getIncludeParts(queryParameters); - - for (IncludeParts ip : includeParts) + return switch (valueAndType.type) { - String includeSql = getIncludeSql(ip); - if (includeSql != null) - includeParameters.add(new SearchQueryIncludeParameter(includeSql, ip, - (resource, connection) -> modifyIncludeResource(ip, resource, connection))); - } - } - - private List getIncludeParts(Map> queryParameters) - { - List includeParameterValues = queryParameters.getOrDefault(SearchQuery.PARAMETER_INCLUDE, - Collections.emptyList()); - - List includeParts = includeParameterValues.stream().map(IncludeParts::fromString) - .filter(p -> resourceTypeName.equals(p.getSourceResourceTypeName()) - && parameterName.equals(p.getSearchParameterName()) - && ((targetResourceTypeNames.size() == 1 && p.getTargetResourceTypeName() == null) - || targetResourceTypeNames.contains(p.getTargetResourceTypeName()))) - .collect(Collectors.toList()); - - return includeParts; + case ID, URL, RESOURCE_NAME_AND_ID -> parameterName; + case TYPE_AND_ID, TYPE_AND_RESOURCE_NAME_AND_ID -> parameterName + ":" + valueAndType.resourceName; + case IDENTIFIER -> parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER; + default -> throw new IllegalArgumentException( + "Unexpected " + ReferenceSearchType.class.getName() + " value: " + valueAndType.type); + }; } @Override - public boolean isDefined() - { - return valueAndType != null; - } - - @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - switch (valueAndType.type) + return switch (valueAndType.type) { - case ID: - bundleUri.replaceQueryParam(parameterName, valueAndType.id); - break; - case URL: - bundleUri.replaceQueryParam(parameterName, valueAndType.url); - break; - case RESOURCE_NAME_AND_ID: - bundleUri.replaceQueryParam(parameterName, valueAndType.resourceName + "/" + valueAndType.id); - break; - - case TYPE_AND_ID: - bundleUri.replaceQueryParam(parameterName + ":" + valueAndType.resourceName, valueAndType.id); - break; - case TYPE_AND_RESOURCE_NAME_AND_ID: - bundleUri.replaceQueryParam(parameterName + ":" + valueAndType.resourceName, - valueAndType.resourceName + "/" + valueAndType.id); - break; - - case IDENTIFIER: + case ID, TYPE_AND_ID -> valueAndType.id; + case URL -> valueAndType.url; + case RESOURCE_NAME_AND_ID, TYPE_AND_RESOURCE_NAME_AND_ID -> + valueAndType.resourceName + "/" + valueAndType.id; + case IDENTIFIER -> switch (valueAndType.identifier.type) { - switch (valueAndType.identifier.type) - { - case CODE: - bundleUri.replaceQueryParam(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER, - valueAndType.identifier.codeValue); - break; - - case CODE_AND_SYSTEM: - bundleUri.replaceQueryParam(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER, - valueAndType.identifier.systemValue + "|" + valueAndType.identifier.codeValue); - break; - - case CODE_AND_NO_SYSTEM_PROPERTY: - bundleUri.replaceQueryParam(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER, - "|" + valueAndType.identifier.codeValue); - break; - - case SYSTEM: - bundleUri.replaceQueryParam(parameterName + PARAMETER_NAME_IDENTIFIER_MODIFIER, - valueAndType.identifier.systemValue + "|"); - break; - } - } - } + case CODE -> valueAndType.identifier.codeValue; + case CODE_AND_SYSTEM -> valueAndType.identifier.systemValue + "|" + valueAndType.identifier.codeValue; + case CODE_AND_NO_SYSTEM_PROPERTY -> "|" + valueAndType.identifier.codeValue; + case SYSTEM -> valueAndType.identifier.systemValue + "|"; + default -> throw new IllegalArgumentException( + "Unexpected " + TokenSearchType.class.getName() + " value: " + valueAndType.identifier.type); + }; + default -> throw new IllegalArgumentException( + "Unexpected " + ReferenceSearchType.class.getName() + " value: " + valueAndType.type); + }; } @Override - public List getIncludeParameters() + public void resolveReferencesForMatching(Resource resource, DaoProvider daoProvider) throws SQLException { - return Collections.unmodifiableList(includeParameters); + if (resourceType.isInstance(resource)) + doResolveReferencesForMatching(resourceType.cast(resource), daoProvider); } - protected abstract String getIncludeSql(IncludeParts includeParts); + protected abstract void doResolveReferencesForMatching(R resource, DaoProvider daoProvider) throws SQLException; @Override - public void resolveReferencesForMatching(Resource resource, DaoProvider daoProvider) throws SQLException + public SearchQueryIncludeParameterConfiguration configureInclude(List errors, + String queryParameterIncludeValue) { - if (resourceType.isInstance(resource)) - doResolveReferencesForMatching(resourceType.cast(resource), daoProvider); + IncludeParts includeParts = IncludeParts.fromString(queryParameterIncludeValue); + String includeSql = getIncludeSql(includeParts); + + if (includeSql != null) + return new SearchQueryIncludeParameterConfiguration(includeSql, includeParts, + (resource, connection) -> modifyIncludeResource(includeParts, resource, connection)); + else + return null; } - protected abstract void doResolveReferencesForMatching(R resource, DaoProvider daoProvider) throws SQLException; + protected abstract String getIncludeSql(IncludeParts includeParts); /** * Use this method to modify the include resources. This method can be used if the resources returned by the include diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractSearchParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractSearchParameter.java index 686e575bf..1ddb46aac 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractSearchParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractSearchParameter.java @@ -1,29 +1,20 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Stream; import org.hl7.fhir.r4.model.Resource; -import dev.dsf.fhir.search.SearchQuery; -import dev.dsf.fhir.search.SearchQueryIncludeParameter; import dev.dsf.fhir.search.SearchQueryParameter; import dev.dsf.fhir.search.SearchQueryParameterError; -import dev.dsf.fhir.search.SearchQuerySortParameter; -import dev.dsf.fhir.search.SearchQuerySortParameter.SortDirection; +import dev.dsf.fhir.search.SearchQuerySortParameterConfiguration; +import dev.dsf.fhir.search.SearchQuerySortParameterConfiguration.SortDirection; +import dev.dsf.fhir.search.parameters.SearchQuerySortParameter; -public abstract class AbstractSearchParameter implements SearchQueryParameter +public abstract class AbstractSearchParameter + implements SearchQueryParameter, SearchQuerySortParameter { protected final String parameterName; - private SearchQuerySortParameter sortParameter; - private final List errors = new ArrayList(); - public AbstractSearchParameter(String parameterName) { this.parameterName = parameterName; @@ -35,84 +26,30 @@ public final String getParameterName() return parameterName; } - @Override - public Stream getBaseAndModifiedParameterNames() - { - return Stream.concat(Stream.of(getParameterName()), getModifiedParameterNames()); - } - - protected Stream getModifiedParameterNames() - { - return Stream.empty(); - } - - protected IllegalStateException notDefined() + protected final IllegalStateException notDefined() { return new IllegalStateException("not defined"); } @Override - public final void configure(Map> queryParameters) - { - SortDirection sortDirection = getSortDirection(getFirst(queryParameters, SearchQuery.PARAMETER_SORT)); - if (sortDirection != null) - sortParameter = new SearchQuerySortParameter(getSortSql(sortDirection.getSqlModifierWithSpacePrefix()), - parameterName, sortDirection); - - configureIncludeParameter(queryParameters); - - configureSearchParameter(queryParameters); - } - - @Override - public List getErrors() - { - return Collections.unmodifiableList(errors); - } - - protected final void addError(SearchQueryParameterError error) - { - errors.add(error); - } - - protected static String getFirst(Map> queryParameters, String key) - { - if (queryParameters.containsKey(key) && !queryParameters.get(key).isEmpty()) - return queryParameters.get(key).get(0); - else - return null; - } - - private SortDirection getSortDirection(String sortParameters) - { - if (sortParameters == null || sortParameters.isBlank()) - return null; - - Optional sortParameter = Arrays.stream(sortParameters.split(",")) - .filter(s -> s.equals(parameterName) || s.equals("+" + parameterName) || s.equals("-" + parameterName)) - .findFirst(); - - return sortParameter.map(SortDirection::fromString).orElse(null); - } - - protected void configureIncludeParameter(Map> queryParameters) + public SearchQueryParameter configure(List errors, String queryParameterName, + String queryParameterValue) { - // default impl does nothing + doConfigure(errors, queryParameterName, queryParameterValue); + return this; } - protected abstract void configureSearchParameter(Map> queryParameters); + protected abstract void doConfigure(List errors, String queryParameterName, + String queryParameterValue); @Override - public Optional getSortParameter() + public SearchQuerySortParameterConfiguration configureSort(List errors, + String queryParameterSortValue) { - return Optional.ofNullable(sortParameter); + SortDirection direction = SortDirection.fromString(queryParameterSortValue); + return new SearchQuerySortParameterConfiguration(getSortSql(direction.getSqlModifierWithSpacePrefix()), + parameterName, direction); } protected abstract String getSortSql(String sortDirectionWithSpacePrefix); - - @Override - public List getIncludeParameters() - { - return Collections.emptyList(); - } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStatusParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStatusParameter.java index 473e242c4..4d469f601 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStatusParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStatusParameter.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.exceptions.FHIRException; @@ -15,7 +14,6 @@ import dev.dsf.fhir.function.BiFunctionWithSqlException; import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import jakarta.ws.rs.core.UriBuilder; public class AbstractStatusParameter extends AbstractTokenParameter { @@ -35,15 +33,17 @@ public AbstractStatusParameter(String resourceColumn, Class resourceType) } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) - status = toStatus(valueAndType.codeValue, queryParameters.get(parameterName)); + status = toStatus(errors, valueAndType.codeValue, queryParameterValue); } - private PublicationStatus toStatus(String status, List parameterValues) + private PublicationStatus toStatus(List errors, String status, + String queryParameterValue) { if (status == null || status.isBlank()) return null; @@ -54,8 +54,8 @@ private PublicationStatus toStatus(String status, List parameterValues) } catch (FHIRException e) { - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, - parameterValues, e)); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, parameterName, + queryParameterValue, e)); return null; } } @@ -86,9 +86,9 @@ public void modifyStatement(int parameterIndex, int subqueryParameterIndex, Prep } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(PARAMETER_NAME + (valueAndType.negated ? ":not" : ""), status.toCode()); + return status.toCode(); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStringParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStringParameter.java index cba0328f3..9e13e0f0a 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStringParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractStringParameter.java @@ -1,16 +1,10 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.stream.Stream; import org.hl7.fhir.r4.model.DomainResource; import dev.dsf.fhir.search.SearchQueryParameterError; -import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import jakarta.ws.rs.core.UriBuilder; public abstract class AbstractStringParameter extends AbstractSearchParameter { @@ -26,6 +20,11 @@ private StringSearchType(String modifier) } } + public static List getNameModifiers() + { + return List.of(StringSearchType.EXACT.modifier, StringSearchType.CONTAINS.modifier); + } + protected static class StringValueAndSearchType { public final String value; @@ -46,57 +45,32 @@ public AbstractStringParameter(String parameterName) } @Override - protected Stream getModifiedParameterNames() + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - return Stream.of(getParameterName() + StringSearchType.EXACT.modifier, - getParameterName() + StringSearchType.CONTAINS.modifier); + if ((parameterName + StringSearchType.STARTS_WITH.modifier).equals(queryParameterName)) + valueAndType = new StringValueAndSearchType(queryParameterValue, StringSearchType.STARTS_WITH); + else if ((parameterName + StringSearchType.EXACT.modifier).equals(queryParameterName)) + valueAndType = new StringValueAndSearchType(queryParameterValue, StringSearchType.EXACT); + else if ((parameterName + StringSearchType.CONTAINS.modifier).equals(queryParameterName)) + valueAndType = new StringValueAndSearchType(queryParameterValue, StringSearchType.CONTAINS); } @Override - protected final void configureSearchParameter(Map> queryParameters) + public boolean isDefined() { - List allValues = new ArrayList<>(); - allValues.addAll(queryParameters.getOrDefault(parameterName + StringSearchType.STARTS_WITH.modifier, - Collections.emptyList())); - allValues.addAll( - queryParameters.getOrDefault(parameterName + StringSearchType.EXACT.modifier, Collections.emptyList())); - allValues.addAll(queryParameters.getOrDefault(parameterName + StringSearchType.CONTAINS.modifier, - Collections.emptyList())); - if (allValues.size() > 1) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, allValues)); - - String startsWith = getFirst(queryParameters, parameterName + StringSearchType.STARTS_WITH.modifier); - if (startsWith != null) - { - valueAndType = new StringValueAndSearchType(startsWith, StringSearchType.STARTS_WITH); - return; - } - - String exact = getFirst(queryParameters, parameterName + StringSearchType.EXACT.modifier); - if (exact != null) - { - valueAndType = new StringValueAndSearchType(exact, StringSearchType.EXACT); - return; - } - - String contains = getFirst(queryParameters, parameterName + StringSearchType.CONTAINS.modifier); - if (contains != null) - { - valueAndType = new StringValueAndSearchType(contains, StringSearchType.CONTAINS); - return; - } + return valueAndType != null; } @Override - public boolean isDefined() + public String getBundleUriQueryParameterName() { - return valueAndType != null; + return parameterName + valueAndType.type.modifier; } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - bundleUri.replaceQueryParam(parameterName + valueAndType.type.modifier, valueAndType.value); + return valueAndType.value; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java index 81707a32f..bdbb2c281 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java @@ -1,21 +1,21 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.stream.Stream; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Resource; import dev.dsf.fhir.search.SearchQueryParameterError; -import dev.dsf.fhir.search.SearchQueryParameterError.SearchQueryParameterErrorType; -import jakarta.ws.rs.core.UriBuilder; public abstract class AbstractTokenParameter extends AbstractSearchParameter { + public static List getNameModifiers() + { + return List.of(TokenValueAndSearchType.NOT); + } + protected TokenValueAndSearchType valueAndType; public AbstractTokenParameter(String parameterName) @@ -24,61 +24,45 @@ public AbstractTokenParameter(String parameterName) } @Override - protected Stream getModifiedParameterNames() + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - return Stream.of(parameterName + TokenValueAndSearchType.NOT); + valueAndType = TokenValueAndSearchType.fromParamValue(parameterName, queryParameterName, queryParameterValue); + + // TODO + // if ((queryParameters.get(parameterName) != null + // || queryParameters.get(parameterName + TokenValueAndSearchType.NOT) != null) + // && ((queryParameters.getOrDefault(parameterName, Collections.emptyList())).size() + // + (queryParameters.getOrDefault(parameterName + TokenValueAndSearchType.NOT, + // Collections.emptyList())).size()) > 1) + // addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, + // parameterName, queryParameters.get(parameterName))); } @Override - protected void configureSearchParameter(Map> queryParameters) + public boolean isDefined() { - valueAndType = TokenValueAndSearchType.fromParamValue(parameterName, queryParameters, this::addError) - .orElse(null); - - if ((queryParameters.get(parameterName) != null - || queryParameters.get(parameterName + TokenValueAndSearchType.NOT) != null) - && ((queryParameters.getOrDefault(parameterName, Collections.emptyList())).size() - + (queryParameters.getOrDefault(parameterName + TokenValueAndSearchType.NOT, - Collections.emptyList())).size()) > 1) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - parameterName, queryParameters.get(parameterName))); + return valueAndType != null; } @Override - public boolean isDefined() + public String getBundleUriQueryParameterName() { - return valueAndType != null; + return valueAndType.negated ? parameterName + TokenValueAndSearchType.NOT : parameterName; } @Override - public void modifyBundleUri(UriBuilder bundleUri) + public String getBundleUriQueryParameterValue() { - switch (valueAndType.type) + return switch (valueAndType.type) { - case CODE: - bundleUri.replaceQueryParam( - valueAndType.negated ? parameterName + TokenValueAndSearchType.NOT : parameterName, - valueAndType.codeValue); - break; - - case CODE_AND_SYSTEM: - bundleUri.replaceQueryParam( - valueAndType.negated ? parameterName + TokenValueAndSearchType.NOT : parameterName, - valueAndType.systemValue + "|" + valueAndType.codeValue); - break; - - case CODE_AND_NO_SYSTEM_PROPERTY: - bundleUri.replaceQueryParam( - valueAndType.negated ? parameterName + TokenValueAndSearchType.NOT : parameterName, - "|" + valueAndType.codeValue); - break; - - case SYSTEM: - bundleUri.replaceQueryParam( - valueAndType.negated ? parameterName + TokenValueAndSearchType.NOT : parameterName, - valueAndType.systemValue + "|"); - break; - } + case CODE -> valueAndType.codeValue; + case CODE_AND_SYSTEM -> valueAndType.systemValue + "|" + valueAndType.codeValue; + case CODE_AND_NO_SYSTEM_PROPERTY -> "|" + valueAndType.codeValue; + case SYSTEM -> valueAndType.systemValue + "|"; + default -> throw new IllegalArgumentException( + "Unexpected " + TokenSearchType.class.getName() + " value: " + valueAndType.type); + }; } protected boolean codingMatches(List codes) @@ -89,20 +73,15 @@ protected boolean codingMatches(List codes) public static boolean codingMatches(TokenValueAndSearchType valueAndType, Coding coding) { - switch (valueAndType.type) + return switch (valueAndType.type) { - case CODE: - return Objects.equals(valueAndType.codeValue, coding.getCode()); - case CODE_AND_SYSTEM: - return Objects.equals(valueAndType.codeValue, coding.getCode()) - && Objects.equals(valueAndType.systemValue, coding.getSystem()); - case CODE_AND_NO_SYSTEM_PROPERTY: - return Objects.equals(valueAndType.codeValue, coding.getCode()) - && (coding.getSystem() == null || coding.getSystem().isBlank()); - case SYSTEM: - return Objects.equals(valueAndType.systemValue, coding.getSystem()); - default: - return false; - } + case CODE -> Objects.equals(valueAndType.codeValue, coding.getCode()); + case CODE_AND_SYSTEM -> Objects.equals(valueAndType.codeValue, coding.getCode()) + && Objects.equals(valueAndType.systemValue, coding.getSystem()); + case CODE_AND_NO_SYSTEM_PROPERTY -> Objects.equals(valueAndType.codeValue, coding.getCode()) + && (coding.getSystem() == null || coding.getSystem().isBlank()); + case SYSTEM -> Objects.equals(valueAndType.systemValue, coding.getSystem()); + default -> false; + }; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractVersionParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractVersionParameter.java index f0edc6242..6690bfdb3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractVersionParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractVersionParameter.java @@ -4,7 +4,6 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; import org.hl7.fhir.r4.model.MetadataResource; @@ -30,15 +29,16 @@ public AbstractVersionParameter(String resourceColumn) } @Override - protected void configureSearchParameter(Map> queryParameters) + protected void doConfigure(List errors, String queryParameterName, + String queryParameterValue) { - super.configureSearchParameter(queryParameters); + super.doConfigure(errors, queryParameterName, queryParameterValue); if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) version = valueAndType.codeValue; else if (valueAndType != null) - addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, PARAMETER_NAME, - queryParameters.get(PARAMETER_NAME))); + errors.add(new SearchQueryParameterError(SearchQueryParameterErrorType.UNPARSABLE_VALUE, PARAMETER_NAME, + queryParameterValue)); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/TokenValueAndSearchType.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/TokenValueAndSearchType.java index 6c1f83e22..c0b59288b 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/TokenValueAndSearchType.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/TokenValueAndSearchType.java @@ -1,15 +1,8 @@ package dev.dsf.fhir.search.parameters.basic; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; - -import dev.dsf.fhir.search.SearchQueryParameterError; - public class TokenValueAndSearchType { - static final String NOT = ":not"; + public static final String NOT = ":not"; public final String systemValue; public final String codeValue; @@ -24,37 +17,32 @@ private TokenValueAndSearchType(String systemValue, String codeValue, TokenSearc this.negated = negated; } - public static Optional fromParamValue(String parameterName, - Map> queryParameters, Consumer errors) + /** + * @param parameterName + * not null, not blank + * @param queryParameterName + * not null, not blank + * @param queryParameterValue + * not null, not blank + */ + public static TokenValueAndSearchType fromParamValue(String parameterName, String queryParameterName, + String queryParameterValue) { - String param = null; - if (queryParameters.containsKey(parameterName) && !queryParameters.get(parameterName).isEmpty()) - param = queryParameters.get(parameterName).get(0); - else if (queryParameters.containsKey(parameterName + NOT) - && !queryParameters.get(parameterName + NOT).isEmpty()) - param = queryParameters.get(parameterName + NOT).get(0); - - if (param != null && !param.isBlank()) + boolean negated = (parameterName + NOT).equals(queryParameterName); + + if (queryParameterValue.indexOf('|') == -1) + return new TokenValueAndSearchType(null, queryParameterValue, TokenSearchType.CODE, negated); + else if (queryParameterValue.charAt(0) == '|') + return new TokenValueAndSearchType(null, queryParameterValue.substring(1), + TokenSearchType.CODE_AND_NO_SYSTEM_PROPERTY, negated); + else if (queryParameterValue.charAt(queryParameterValue.length() - 1) == '|') + return new TokenValueAndSearchType(queryParameterValue.substring(0, queryParameterValue.length() - 1), null, + TokenSearchType.SYSTEM, negated); + else { - boolean negated = queryParameters.containsKey(parameterName + NOT) - && !queryParameters.get(parameterName + NOT).isEmpty(); - - if (param.indexOf('|') == -1) - return Optional.of(new TokenValueAndSearchType(null, param, TokenSearchType.CODE, negated)); - else if (param.charAt(0) == '|') - return Optional.of(new TokenValueAndSearchType(null, param.substring(1), - TokenSearchType.CODE_AND_NO_SYSTEM_PROPERTY, negated)); - else if (param.charAt(param.length() - 1) == '|') - return Optional.of(new TokenValueAndSearchType(param.substring(0, param.length() - 1), null, - TokenSearchType.SYSTEM, negated)); - else - { - String[] splitAtPipe = param.split("[|]"); - return Optional.of(new TokenValueAndSearchType(splitAtPipe[0], splitAtPipe[1], - TokenSearchType.CODE_AND_SYSTEM, negated)); - } + String[] splitAtPipe = queryParameterValue.split("[|]"); + return new TokenValueAndSearchType(splitAtPipe[0], splitAtPipe[1], TokenSearchType.CODE_AND_SYSTEM, + negated); } - else - return Optional.empty(); } } \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameter.java new file mode 100644 index 000000000..5d55af2d6 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameter.java @@ -0,0 +1,47 @@ +package dev.dsf.fhir.search.parameters.rev.include; + +import java.sql.Connection; +import java.util.List; + +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.Resource; + +import dev.dsf.fhir.search.IncludeParts; +import dev.dsf.fhir.search.SearchQueryIncludeParameterConfiguration; +import dev.dsf.fhir.search.SearchQueryParameterError; +import dev.dsf.fhir.search.SearchQueryRevIncludeParameter; + +public abstract class AbstractRevIncludeParameter implements SearchQueryRevIncludeParameter +{ + @Override + public SearchQueryIncludeParameterConfiguration configureRevInclude(List errors, + String queryParameterRevIncludeValue) + { + IncludeParts includeParts = IncludeParts.fromString(queryParameterRevIncludeValue); + String revIncludeSql = getRevIncludeSql(includeParts); + + if (revIncludeSql != null) + return new SearchQueryIncludeParameterConfiguration(revIncludeSql, includeParts, + (resource, connection) -> modifyRevIncludeResource(includeParts, resource, connection)); + else + return null; + + } + + protected abstract String getRevIncludeSql(IncludeParts includeParts); + + /** + * Use this method to modify the revinclude resources. This method can be used if the resources returned by the + * include SQL are not complete and additional content needs to be retrieved from a not included column. For example + * the content of a {@link Binary} resource might not be stored in the json column. + * + * @param includeParts + * not null + * @param resource + * not null + * @param connection + * not null + */ + protected abstract void modifyRevIncludeResource(IncludeParts includeParts, Resource resource, + Connection connection); +} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameterFactory.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameterFactory.java deleted file mode 100644 index 861d62b18..000000000 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/AbstractRevIncludeParameterFactory.java +++ /dev/null @@ -1,104 +0,0 @@ -package dev.dsf.fhir.search.parameters.rev.include; - -import java.sql.Connection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.hl7.fhir.r4.model.Binary; -import org.hl7.fhir.r4.model.Resource; - -import dev.dsf.fhir.search.IncludeParts; -import dev.dsf.fhir.search.SearchQueryIncludeParameter; -import dev.dsf.fhir.search.SearchQueryParameterError; -import dev.dsf.fhir.search.SearchQueryRevIncludeParameterFactory; - -public abstract class AbstractRevIncludeParameterFactory implements SearchQueryRevIncludeParameterFactory -{ - private final List errors = new ArrayList<>(); - - /** - * The name of the source resource from which the join comes - */ - private final String resourceTypeName; - /** - * The name of the search parameter which must be of type reference - */ - private final String parameterName; - /** - * (Optional) A specific of type of target resource (for when the search parameter refers to multiple possible - * target types) - */ - private final List targetResourceTypeNames; - - private List includeParts; - - /** - * @param resourceTypeName - * The name of the source resource from which the join comes - * @param parameterName - * The name of the search parameter which must be of type reference - * @param targetResourceTypeName - * target resource type - */ - public AbstractRevIncludeParameterFactory(String resourceTypeName, String parameterName, - String... targetResourceTypeName) - { - this.resourceTypeName = resourceTypeName; - this.parameterName = parameterName; - this.targetResourceTypeNames = Arrays.asList(targetResourceTypeName); - } - - @Override - public void configure(List revIncludeParameters) - { - includeParts = getRevIncludeParts(revIncludeParameters); - } - - private List getRevIncludeParts(List revIncludeParameterValues) - { - List includeParts = revIncludeParameterValues.stream().map(IncludeParts::fromString) - .filter(p -> resourceTypeName.equals(p.getSourceResourceTypeName()) - && parameterName.equals(p.getSearchParameterName()) - && ((targetResourceTypeNames.size() == 1 && p.getTargetResourceTypeName() == null) - || targetResourceTypeNames.contains(p.getTargetResourceTypeName()))) - .collect(Collectors.toList()); - - return includeParts; - } - - protected final void addError(SearchQueryParameterError error) - { - errors.add(error); - } - - @Override - public List getErrors() - { - return Collections.unmodifiableList(errors); - } - - protected abstract String getRevIncludeSql(IncludeParts includeParts); - - /** - * Use this method to modify the include resources. This method can be used if the resources returned by the include - * SQL are not complete and additional content needs to be retrieved from a not included column. For example the - * content of a {@link Binary} resource might not be stored in the json column. - * - * @param resource - * not null - * @param connection - * not null - */ - protected abstract void modifyIncludeResource(Resource resource, Connection connection); - - @Override - public List getRevIncludeParameters() - { - return includeParts.stream() - .map(ip -> new SearchQueryIncludeParameter(getRevIncludeSql(ip), ip, this::modifyIncludeResource)) - .collect(Collectors.toList()); - } -} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/EndpointOrganizationRevInclude.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/EndpointOrganizationRevInclude.java index e1f0d3c3d..6a18c277a 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/EndpointOrganizationRevInclude.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/EndpointOrganizationRevInclude.java @@ -1,6 +1,8 @@ package dev.dsf.fhir.search.parameters.rev.include; import java.sql.Connection; +import java.util.Arrays; +import java.util.List; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.Organization; @@ -10,11 +12,16 @@ import dev.dsf.fhir.search.IncludeParts; @IncludeParameterDefinition(resourceType = Endpoint.class, parameterName = "organization", targetResourceTypes = Organization.class) -public class EndpointOrganizationRevInclude extends AbstractRevIncludeParameterFactory +public class EndpointOrganizationRevInclude extends AbstractRevIncludeParameter { - public EndpointOrganizationRevInclude() + public static final String RESOURCE_TYPE_NAME = "Endpoint"; + public static final String PARAMETER_NAME = "organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getRevIncludeParameterValues() { - super("Endpoint", "organization", "Organization"); + return Arrays.asList(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); } @Override @@ -24,7 +31,7 @@ protected String getRevIncludeSql(IncludeParts includeParts) } @Override - protected void modifyIncludeResource(Resource resource, Connection connection) + protected void modifyRevIncludeResource(IncludeParts includeParts, Resource resource, Connection connection) { // Nothing to do for organizations } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationParticipatingOrganizationRevInclude.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationParticipatingOrganizationRevInclude.java index 068d3a933..bc279fa93 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationParticipatingOrganizationRevInclude.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationParticipatingOrganizationRevInclude.java @@ -1,6 +1,8 @@ package dev.dsf.fhir.search.parameters.rev.include; import java.sql.Connection; +import java.util.Arrays; +import java.util.List; import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.OrganizationAffiliation; @@ -10,11 +12,16 @@ import dev.dsf.fhir.search.IncludeParts; @IncludeParameterDefinition(resourceType = OrganizationAffiliation.class, parameterName = "participating-organization", targetResourceTypes = Organization.class) -public class OrganizationAffiliationParticipatingOrganizationRevInclude extends AbstractRevIncludeParameterFactory +public class OrganizationAffiliationParticipatingOrganizationRevInclude extends AbstractRevIncludeParameter { - public OrganizationAffiliationParticipatingOrganizationRevInclude() + public static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; + public static final String PARAMETER_NAME = "participating-organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getRevIncludeParameterValues() { - super("OrganizationAffiliation", "participating-organization", "Organization"); + return Arrays.asList(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); } @Override @@ -24,7 +31,7 @@ protected String getRevIncludeSql(IncludeParts includeParts) } @Override - protected void modifyIncludeResource(Resource resource, Connection connection) + protected void modifyRevIncludeResource(IncludeParts includeParts, Resource resource, Connection connection) { // Nothing to do for organizations } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationPrimaryOrganizationRevInclude.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationPrimaryOrganizationRevInclude.java index c9df779c5..2c38742b5 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationPrimaryOrganizationRevInclude.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationAffiliationPrimaryOrganizationRevInclude.java @@ -1,6 +1,8 @@ package dev.dsf.fhir.search.parameters.rev.include; import java.sql.Connection; +import java.util.Arrays; +import java.util.List; import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.OrganizationAffiliation; @@ -10,11 +12,16 @@ import dev.dsf.fhir.search.IncludeParts; @IncludeParameterDefinition(resourceType = OrganizationAffiliation.class, parameterName = "primary-organization", targetResourceTypes = Organization.class) -public class OrganizationAffiliationPrimaryOrganizationRevInclude extends AbstractRevIncludeParameterFactory +public class OrganizationAffiliationPrimaryOrganizationRevInclude extends AbstractRevIncludeParameter { - public OrganizationAffiliationPrimaryOrganizationRevInclude() + public static final String RESOURCE_TYPE_NAME = "OrganizationAffiliation"; + public static final String PARAMETER_NAME = "primary-organization"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Organization"; + + public static List getRevIncludeParameterValues() { - super("OrganizationAffiliation", "primary-organization", "Organization"); + return Arrays.asList(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); } @Override @@ -24,7 +31,7 @@ protected String getRevIncludeSql(IncludeParts includeParts) } @Override - protected void modifyIncludeResource(Resource resource, Connection connection) + protected void modifyRevIncludeResource(IncludeParts includeParts, Resource resource, Connection connection) { // Nothing to do for organizations } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationEndpointRevInclude.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationEndpointRevInclude.java index 8578bf614..fe298c702 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationEndpointRevInclude.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/OrganizationEndpointRevInclude.java @@ -1,6 +1,8 @@ package dev.dsf.fhir.search.parameters.rev.include; import java.sql.Connection; +import java.util.Arrays; +import java.util.List; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.Organization; @@ -10,11 +12,16 @@ import dev.dsf.fhir.search.IncludeParts; @IncludeParameterDefinition(resourceType = Organization.class, parameterName = "endpoint", targetResourceTypes = Endpoint.class) -public class OrganizationEndpointRevInclude extends AbstractRevIncludeParameterFactory +public class OrganizationEndpointRevInclude extends AbstractRevIncludeParameter { - public OrganizationEndpointRevInclude() + public static final String RESOURCE_TYPE_NAME = "Organization"; + public static final String PARAMETER_NAME = "endpoint"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Endpoint"; + + public static List getRevIncludeParameterValues() { - super("Organization", "endpoint", "Endpoint"); + return Arrays.asList(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); } @Override @@ -24,7 +31,7 @@ protected String getRevIncludeSql(IncludeParts includeParts) } @Override - protected void modifyIncludeResource(Resource resource, Connection connection) + protected void modifyRevIncludeResource(IncludeParts includeParts, Resource resource, Connection connection) { // Nothing to do for organizations } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/ResearchStudyEnrollmentRevInclude.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/ResearchStudyEnrollmentRevInclude.java index 6567148ba..2f07584a4 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/ResearchStudyEnrollmentRevInclude.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/rev/include/ResearchStudyEnrollmentRevInclude.java @@ -1,6 +1,8 @@ package dev.dsf.fhir.search.parameters.rev.include; import java.sql.Connection; +import java.util.Arrays; +import java.util.List; import org.hl7.fhir.r4.model.Group; import org.hl7.fhir.r4.model.ResearchStudy; @@ -10,11 +12,16 @@ import dev.dsf.fhir.search.IncludeParts; @IncludeParameterDefinition(resourceType = ResearchStudy.class, parameterName = "enrollment", targetResourceTypes = Group.class) -public class ResearchStudyEnrollmentRevInclude extends AbstractRevIncludeParameterFactory +public class ResearchStudyEnrollmentRevInclude extends AbstractRevIncludeParameter { - public ResearchStudyEnrollmentRevInclude() + public static final String RESOURCE_TYPE_NAME = "ResearchStudy"; + public static final String PARAMETER_NAME = "enrollment"; + public static final String TARGET_RESOURCE_TYPE_NAME = "Group"; + + public static List getRevIncludeParameterValues() { - super("ResearchStudy", "enrollment", "Group"); + return Arrays.asList(RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME, + RESOURCE_TYPE_NAME + ":" + PARAMETER_NAME + ":" + TARGET_RESOURCE_TYPE_NAME); } @Override @@ -24,7 +31,7 @@ protected String getRevIncludeSql(IncludeParts includeParts) } @Override - protected void modifyIncludeResource(Resource resource, Connection connection) + protected void modifyRevIncludeResource(IncludeParts includeParts, Resource resource, Connection connection) { // Nothing to do for groups } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java index c33af75d0..cf8ed4b15 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java @@ -340,8 +340,7 @@ private Optional search(Identity identity, Connection connection, Reso SearchQuery query = referenceTargetDao.createSearchQuery(identity, 1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) { String unsupportedQueryParametersString = unsupportedQueryParameters.stream() @@ -607,8 +606,7 @@ private Optional search(Identity identity, Resource resource, SearchQuery query = referenceTargetDao.createSearchQuery(identity, 1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) return Optional .of(responseGenerator.badReference(logicalNotConditional, bundleIndex, resource, resourceReference, diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java index 6e6dd5a61..077762cd8 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java @@ -331,8 +331,7 @@ private void checkAlreadyExists(HttpHeaders headers) throws WebApplicationExcept SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) throw new WebApplicationException( responseGenerator.badIfNoneExistHeaderValue(ifNoneExistHeader.get(), unsupportedQueryParameters)); @@ -649,7 +648,7 @@ public Response search(UriInfo uri, HttpHeaders headers) SearchQuery query = dao.createSearchQuery(getCurrentIdentity(), effectivePage, effectiveCount); query.configureParameters(queryParameters); - List errors = query.getUnsupportedQueryParameters(queryParameters); + List errors = query.getUnsupportedQueryParameters(); // if query parameter errors and client requests strict handling -> bad request outcome if (!errors.isEmpty() && PreferHandlingType.STRICT.equals(parameterConverter.getPreferHandling(headers))) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java index 35a4c7970..c60ac5af3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java @@ -94,6 +94,7 @@ import dev.dsf.fhir.search.parameters.EndpointName; import dev.dsf.fhir.search.parameters.EndpointOrganization; import dev.dsf.fhir.search.parameters.EndpointStatus; +import dev.dsf.fhir.search.parameters.GroupIdentifier; import dev.dsf.fhir.search.parameters.HealthcareServiceActive; import dev.dsf.fhir.search.parameters.HealthcareServiceIdentifier; import dev.dsf.fhir.search.parameters.LibraryDate; @@ -165,7 +166,7 @@ import dev.dsf.fhir.search.parameters.ValueSetUrl; import dev.dsf.fhir.search.parameters.ValueSetVersion; import dev.dsf.fhir.search.parameters.basic.AbstractSearchParameter; -import dev.dsf.fhir.search.parameters.rev.include.AbstractRevIncludeParameterFactory; +import dev.dsf.fhir.search.parameters.rev.include.AbstractRevIncludeParameter; import dev.dsf.fhir.search.parameters.rev.include.EndpointOrganizationRevInclude; import dev.dsf.fhir.search.parameters.rev.include.OrganizationAffiliationParticipatingOrganizationRevInclude; import dev.dsf.fhir.search.parameters.rev.include.OrganizationAffiliationPrimaryOrganizationRevInclude; @@ -310,7 +311,7 @@ private CapabilityStatement createCapabilityStatement() StructureDefinition.class, Subscription.class, Task.class, ValueSet.class); var searchParameters = new HashMap, List>>>(); - var revIncludeParameters = new HashMap, List>>(); + var revIncludeParameters = new HashMap, List>>(); searchParameters.put(ActivityDefinition.class, Arrays.asList(ActivityDefinitionDate.class, ActivityDefinitionUrl.class, @@ -330,7 +331,7 @@ private CapabilityStatement createCapabilityStatement() EndpointName.class, EndpointOrganization.class, EndpointStatus.class)); revIncludeParameters.put(Endpoint.class, Arrays.asList(OrganizationEndpointRevInclude.class)); - // no Group search parameters + searchParameters.put(Group.class, Arrays.asList(GroupIdentifier.class)); revIncludeParameters.put(Group.class, Arrays.asList(ResearchStudyEnrollmentRevInclude.class)); searchParameters.put(HealthcareService.class, diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java index fa3f5efb8..fb357f037 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java @@ -543,8 +543,7 @@ private PartialResult getExisting(Map> queryParameters) SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) { audit.info( @@ -625,8 +624,7 @@ public Response delete(UriInfo uri, HttpHeaders headers) SearchQuery query = dao.createSearchQuery(getCurrentIdentity(), 1, 1); query.configureParameters(queryParameters); - List unsupportedQueryParameters = query - .getUnsupportedQueryParameters(queryParameters); + List unsupportedQueryParameters = query.getUnsupportedQueryParameters(); if (!unsupportedQueryParameters.isEmpty()) { audit.info( diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java index f08e8766a..d5b36dd51 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.Collections; import java.util.UUID; import org.apache.commons.dbcp2.BasicDataSource; @@ -69,7 +70,7 @@ public void testReadHistory() throws Exception History history = dao.readHistory( filterFactory.getIdentityFilters(TestOrganizationIdentity.local(createdOrganization)), - new PageAndCount(1, 1000), new AtParameter(), new SinceParameter()); + new PageAndCount(1, 1000), Collections.singletonList(new AtParameter()), new SinceParameter()); assertNotNull(history); assertEquals(1, history.getTotal()); assertNotNull(history.getEntries()); @@ -88,7 +89,8 @@ public void testReadHistoryOrganization() throws Exception History history = dao.readHistory( filterFactory.getIdentityFilter(TestOrganizationIdentity.local(createdOrganization), Organization.class), - new PageAndCount(1, 1000), new AtParameter(), new SinceParameter(), Organization.class); + new PageAndCount(1, 1000), Collections.singletonList(new AtParameter()), new SinceParameter(), + Organization.class); assertNotNull(history); assertEquals(1, history.getTotal()); assertNotNull(history.getEntries()); @@ -107,8 +109,8 @@ public void testReadHistoryOrganizationWithId() throws Exception History history = dao.readHistory( filterFactory.getIdentityFilter(TestOrganizationIdentity.local(createdOrganization), Organization.class), - new PageAndCount(1, 1000), new AtParameter(), new SinceParameter(), Organization.class, - UUID.fromString(createdOrganization.getIdElement().getIdPart())); + new PageAndCount(1, 1000), Collections.singletonList(new AtParameter()), new SinceParameter(), + Organization.class, UUID.fromString(createdOrganization.getIdElement().getIdPart())); assertNotNull(history); assertEquals(1, history.getTotal()); diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/GroupIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/GroupIntegrationTest.java new file mode 100644 index 000000000..6e9f33a7f --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/GroupIntegrationTest.java @@ -0,0 +1,104 @@ +package dev.dsf.fhir.integration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Group; +import org.hl7.fhir.r4.model.Group.GroupType; +import org.junit.Test; + +import dev.dsf.fhir.dao.GroupDao; + +public class GroupIntegrationTest extends AbstractIntegrationTest +{ + @Test + public void testCreateGroup() throws Exception + { + Group g = createGroup(); + getReadAccessHelper().addAll(g); + + getWebserviceClient().create(g); + } + + @Test + public void testCreateGroupFrobidden() throws Exception + { + expectForbidden(() -> getWebserviceClient().create(createGroup())); + } + + @Test + public void testSearchGroupByIdentifier() throws Exception + { + final String identifier = UUID.randomUUID().toString(); + + Group g = createGroup(identifier); + getReadAccessHelper().addAll(g); + + GroupDao dao = getSpringWebApplicationContext().getBean(GroupDao.class); + Group created = dao.create(g); + assertNotNull(created); + assertTrue(created.hasIdElement()); + assertTrue(created.hasIdentifier()); + assertEquals(identifier, created.getIdentifierFirstRep().getValue()); + + Bundle bundle = getWebserviceClient().search(Group.class, + Map.of("identifier", Collections.singletonList(identifier))); + assertNotNull(bundle); + assertEquals(1, bundle.getTotal()); + assertTrue(bundle.hasEntry()); + assertEquals(1, bundle.getEntry().size()); + assertTrue(bundle.getEntry().get(0).hasResource()); + assertTrue(bundle.getEntry().get(0).getResource() instanceof Group); + assertTrue(bundle.getEntry().get(0).getResource().hasIdElement()); + assertEquals(created.getIdElement(), bundle.getEntry().get(0).getResource().getIdElement()); + } + + @Test + public void testSearchGroupByTwoIdentifiers() throws Exception + { + final String identifier2 = UUID.randomUUID().toString(); + final String identifier1 = UUID.randomUUID().toString(); + + Group g1 = createGroup(identifier1, identifier2); + getReadAccessHelper().addAll(g1); + GroupDao dao = getSpringWebApplicationContext().getBean(GroupDao.class); + Group created1 = dao.create(g1); + assertNotNull(created1); + assertTrue(created1.hasIdElement()); + + Group g2 = createGroup(identifier1); + getReadAccessHelper().addAll(g2); + Group created2 = dao.create(g2); + assertNotNull(created2); + assertTrue(created2.hasIdElement()); + + Bundle bundle = getWebserviceClient().search(Group.class, + Map.of("identifier", Arrays.asList(identifier1, identifier2))); + assertNotNull(bundle); + assertEquals(1, bundle.getTotal()); + assertTrue(bundle.hasEntry()); + assertEquals(1, bundle.getEntry().size()); + assertTrue(bundle.getEntry().get(0).hasResource()); + assertTrue(bundle.getEntry().get(0).getResource() instanceof Group); + assertTrue(bundle.getEntry().get(0).getResource().hasIdElement()); + assertEquals(created1.getIdElement(), bundle.getEntry().get(0).getResource().getIdElement()); + } + + private Group createGroup(String... identifiers) + { + Group g = new Group(); + g.setType(GroupType.PERSON); + g.setActual(false); + for (String identifier : identifiers) + g.addIdentifier().setSystem("http://test.org/sid/group-id").setValue(identifier); + + return g; + } +} From 2dc59e370b29f1d4340d7d89d3b34cbd10bd6e09 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 9 Aug 2023 13:06:39 +0200 Subject: [PATCH 10/39] fixes property name in FHIR server config used when run via the IDE --- dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties b/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties index 67f5b5018..7af2bbc07 100755 --- a/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties +++ b/dsf-fhir/dsf-fhir-server-jetty/conf/jetty.properties @@ -6,4 +6,4 @@ dev.dsf.server.certificate=target/localhost_certificate.pem dev.dsf.server.certificate.key=target/localhost_private-key.pem dev.dsf.server.certificate.key.password=password -dev.dsf.server.trust.client.certificate.cas=target/testca_certificate.pem \ No newline at end of file +dev.dsf.server.auth.trust.client.certificate.cas=target/testca_certificate.pem \ No newline at end of file From d8d0dfc1d63063a60d09c3f196d7525249dc9511 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 9 Aug 2023 13:09:25 +0200 Subject: [PATCH 11/39] adds _at and _since to capability statement and history url help UIs --- .../webservice/impl/ConformanceServiceImpl.java | 14 ++++++++++++++ .../src/main/resources/static/help.js | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java index c60ac5af3..03f9f3fe5 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/ConformanceServiceImpl.java @@ -459,6 +459,8 @@ private CapabilityStatement createCapabilityStatement() r.addSearchParam(createPrettyParameter()); r.addSearchParam(createSummaryParameter()); r.addSearchParam(createProfileParameter()); + r.addSearchParam(createSinceParameter()); + r.addSearchParam(createAtParameter()); var resourceRevIncludeParameters = revIncludeParameters.getOrDefault(resource, Collections.emptyList()); var revIncludes = resourceRevIncludeParameters.stream() @@ -548,6 +550,18 @@ private CapabilityStatementRestResourceSearchParamComponent createCountParameter "Specify the numer of returned resources per page, " + defaultPageCount + " if not specified"); } + private CapabilityStatementRestResourceSearchParamComponent createAtParameter() + { + return createSearchParameter("_at", "", SearchParamType.DATE, + "Only include resource versions that were current at some point during the time period specified in the date time value"); + } + + private CapabilityStatementRestResourceSearchParamComponent createSinceParameter() + { + return createSearchParameter("_since", "", SearchParamType.SPECIAL, + "Only include resource versions that were created at or after the given instant in time"); + } + private CapabilityStatementRestResourceSearchParamComponent createFormatParameter() { String formatValues = Stream diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js index f8a29f865..e6a2eb622 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js @@ -27,7 +27,7 @@ function createAndShowHelp(httpRequest) { if (window.location.pathname.endsWith('/metadata')) { createHelp(searchParam.filter(p => ['_format', '_pretty', '_summary'].includes(p.name))); } else if (window.location.pathname.endsWith('/_history')) { - createHelp(searchParam.filter(p => ['_count', '_format', '_page', '_pretty', '_summary'].includes(p.name))); + createHelp(searchParam.filter(p => ['_count', '_format', '_page', '_pretty', '_summary', '_at', '_since'].includes(p.name))); } else { createHelp(searchParam.filter(p => ['_format', '_pretty', '_summary'].includes(p.name))); } @@ -40,7 +40,7 @@ function createAndShowHelp(httpRequest) { } //Resource/_history else if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] !== undefined && resourceType[4] === undefined) { - createHelp(searchParam.filter(p => ['_count', '_format', '_page', '_pretty', '_summary'].includes(p.name))); + createHelp(searchParam.filter(p => ['_count', '_format', '_page', '_pretty', '_summary', '_at', '_since'].includes(p.name))); } //Resource/id else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] === undefined && resourceType[4] === undefined) { @@ -48,7 +48,7 @@ function createAndShowHelp(httpRequest) { } //Resource/id/_history else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] !== undefined && resourceType[4] === undefined) { - createHelp(searchParam.filter(p => ['_count', '_format', '_page', '_pretty', '_summary'].includes(p.name))); + createHelp(searchParam.filter(p => ['_count', '_format', '_page', '_pretty', '_summary', '_at', '_since'].includes(p.name))); } //Resource/id/_history/version else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] !== undefined && resourceType[4] !== undefined) { From a4d6555c3ee1099b3c7dcabb4917eb7410131e76 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 9 Aug 2023 15:17:08 +0200 Subject: [PATCH 12/39] reworks unique criteria of the OrgAff auth rule, closes #65 New implementation supports multiple OrganizationAffiliation resources for a parent/member organization combination if no resource with the same role or roles exists. Adds OrganizationAffiliation integration test. Improves error handling for searches in auth rules. --- .../AbstractAuthorizationRule.java | 16 +- .../EndpointAuthorizationRule.java | 25 ++- ...anizationAffiliationAuthorizationRule.java | 57 +++++- .../SubscriptionAuthorizationRule.java | 20 +- ...rganizationAffiliationIntegrationTest.java | 181 ++++++++++++++++++ 5 files changed, 266 insertions(+), 33 deletions(-) create mode 100644 dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java index cc64a198c..af62e2e39 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java @@ -185,8 +185,8 @@ protected final boolean organizationWithIdentifierExists(Connection connection, List uQp = query.getUnsupportedQueryParameters(); if (!uQp.isEmpty()) { - logger.warn("Unsupported query parameters {} while searching for Organization", uQp); - return false; + logger.warn("Unable to search for Organization: Unsupported query parameters: {}", uQp); + throw new IllegalStateException("Unable to search for Organization: Unsupported query parameters."); } try @@ -196,8 +196,8 @@ protected final boolean organizationWithIdentifierExists(Connection connection, } catch (SQLException e) { - logger.warn("Error while searching for Organization with identifier", e); - return false; + logger.warn("Unable to search for Organization", e); + throw new RuntimeException("Unable to search for Organization", e); } } @@ -216,8 +216,8 @@ protected final boolean roleExists(Connection connection, Coding coding) List uQp = query.getUnsupportedQueryParameters(); if (!uQp.isEmpty()) { - logger.warn("Unsupported query parameters {} while searching for CodeSystem", uQp); - return false; + logger.warn("Unable to search for CodeSystem: Unsupported query parameters: {}", uQp); + throw new IllegalStateException("Unable to search for CodeSystem: Unsupported query parameters"); } try @@ -227,8 +227,8 @@ protected final boolean roleExists(Connection connection, Coding coding) } catch (SQLException e) { - logger.warn("Error while searching for Organization with identifier", e); - return false; + logger.warn("Unable to search for CodeSystem", e); + throw new RuntimeException("Unable to search for CodeSystem", e); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java index f8396f6b6..94d8348da 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/EndpointAuthorizationRule.java @@ -22,6 +22,7 @@ import dev.dsf.fhir.help.ParameterConverter; import dev.dsf.fhir.search.PartialResult; import dev.dsf.fhir.search.SearchQuery; +import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.service.ReferenceResolver; public class EndpointAuthorizationRule extends AbstractMetaTagAuthorizationRule @@ -111,8 +112,12 @@ private boolean endpointWithAddressExists(Connection connection, String address) EndpointDao dao = getDao(); SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0).configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters().isEmpty()) - return false; + List uQp = query.getUnsupportedQueryParameters(); + if (!uQp.isEmpty()) + { + logger.warn("Unable to search for Endpoint: Unsupported query parameters: {}", uQp); + throw new IllegalStateException("Unable to search for Endpoint: Unsupported query parameters"); + } try { @@ -121,8 +126,8 @@ private boolean endpointWithAddressExists(Connection connection, String address) } catch (SQLException e) { - logger.warn("Error while searching for Endpoint with address", e); - return false; + logger.warn("Unable to search for Endpoint", e); + throw new RuntimeException("Unable to search for Endpoint", e); } } @@ -133,8 +138,12 @@ private boolean endpointWithIdentifierExists(Connection connection, String ident EndpointDao dao = getDao(); SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0).configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters().isEmpty()) - return false; + List uQp = query.getUnsupportedQueryParameters(); + if (!uQp.isEmpty()) + { + logger.warn("Unable to search for Endpoint: Unsupported query parameters: {}", uQp); + throw new IllegalStateException("Unable to search for Endpoint: Unsupported query parameters"); + } try { @@ -143,8 +152,8 @@ private boolean endpointWithIdentifierExists(Connection connection, String ident } catch (SQLException e) { - logger.warn("Error while searching for Endpoint with identifier", e); - return false; + logger.warn("Unable to search for Endpoint", e); + throw new RuntimeException("Unable to search for Endpoint", e); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java index 615469329..6878b4d7a 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java @@ -9,6 +9,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.OrganizationAffiliation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +22,7 @@ import dev.dsf.fhir.help.ParameterConverter; import dev.dsf.fhir.search.PartialResult; import dev.dsf.fhir.search.SearchQuery; +import dev.dsf.fhir.search.SearchQueryParameterError; import dev.dsf.fhir.service.ReferenceResolver; public class OrganizationAffiliationAuthorizationRule @@ -79,6 +81,36 @@ private Optional newResourceOk(Connection connection, Identity identity, errors.add("OrganizationAffiliation.participatingOrganization missing"); } + if (newResource.hasEndpoint()) + { + for (int i = 0; i < newResource.getEndpoint().size(); i++) + { + if (!newResource.getEndpoint().get(i).hasReference()) + { + errors.add("OrganizationAffiliation.endpoint[" + i + "].reference missing"); + } + } + } + else + { + errors.add("OrganizationAffiliation.endpoint missing"); + } + + if (newResource.hasCode()) + { + for (int i = 0; i < newResource.getCode().size(); i++) + { + if (!newResource.getCode().get(i).hasCoding()) + { + errors.add("OrganizationAffiliation.code[" + i + "].coding missing"); + } + } + } + else + { + errors.add("OrganizationAffiliation.code missing"); + } + if (!hasValidReadAccessTag(connection, newResource)) { errors.add("OrganizationAffiliation is missing valid read access tag"); @@ -93,21 +125,29 @@ private Optional newResourceOk(Connection connection, Identity identity, @Override protected boolean resourceExists(Connection connection, OrganizationAffiliation newResource) { - return organizationAffiliationWithParentAndMemberExists(connection, newResource); + return organizationAffiliationWithParentAndMemberAndRoleExists(connection, newResource); } - private boolean organizationAffiliationWithParentAndMemberExists(Connection connection, + private boolean organizationAffiliationWithParentAndMemberAndRoleExists(Connection connection, OrganizationAffiliation newResource) { Map> queryParameters = Map.of("primary-organization", Collections.singletonList(newResource.getOrganization().getReference()), "participating-organization", - Collections.singletonList(newResource.getParticipatingOrganization().getReference())); + Collections.singletonList(newResource.getParticipatingOrganization().getReference()), "role", + newResource.getCode().stream().map(CodeableConcept::getCoding).flatMap(List::stream) + .map(c -> c.getSystem() + "|" + c.getCode()).toList()); + OrganizationAffiliationDao dao = getDao(); SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0) .configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters().isEmpty()) - return false; + List uQp = query.getUnsupportedQueryParameters(); + if (!uQp.isEmpty()) + { + logger.warn("Unable to search for OrganizationAffiliation: Unsupported query parameters: {}", uQp); + throw new IllegalStateException( + "Unable to search for OrganizationAffiliation: Unsupported query parameters"); + } try { @@ -116,8 +156,8 @@ private boolean organizationAffiliationWithParentAndMemberExists(Connection conn } catch (SQLException e) { - logger.warn("Error while searching for Endpoint with address", e); - return false; + logger.warn("Unable to search for OrganizationAffiliation", e); + throw new RuntimeException("Unable to search for OrganizationAffiliation", e); } } @@ -125,7 +165,8 @@ private boolean organizationAffiliationWithParentAndMemberExists(Connection conn protected boolean modificationsOk(Connection connection, OrganizationAffiliation oldResource, OrganizationAffiliation newResource) { - return isParentSame(oldResource, newResource) && isMemberSame(oldResource, newResource); + return isParentSame(oldResource, newResource) && isMemberSame(oldResource, newResource) + && !resourceExists(connection, newResource); } private boolean isParentSame(OrganizationAffiliation oldResource, OrganizationAffiliation newResource) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java index 0481cfb73..01b5dcb97 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java @@ -88,12 +88,10 @@ private Optional newResourceOk(Connection connection, Identity identity, if (optDao.isPresent()) { SearchQuery searchQuery = optDao.get().createSearchQueryWithoutUserFilter(1, 1); - List unsupportedQueryParameters = searchQuery - .getUnsupportedQueryParameters(); - - if (!unsupportedQueryParameters.isEmpty()) + List uQp = searchQuery.getUnsupportedQueryParameters(); + if (!uQp.isEmpty()) { - errors.add("Subscription.criteria invalid (parameters '" + unsupportedQueryParameters.stream() + errors.add("Subscription.criteria invalid (parameters '" + uQp.stream() .map(SearchQueryParameterError::toString).collect(Collectors.joining(", ")) + "' not supported)"); } @@ -136,8 +134,12 @@ protected boolean resourceExists(Connection connection, Subscription newResource SearchQuery query = dao.createSearchQueryWithoutUserFilter(1, 1) .configureParameters(queryParameters); - if (!query.getUnsupportedQueryParameters().isEmpty()) - return false; + List uQp = query.getUnsupportedQueryParameters(); + if (!uQp.isEmpty()) + { + logger.warn("Unable to search for Subscription: Unsupported query parameters: {}", uQp); + throw new IllegalStateException("Unable to search for Subscription: Unsupported query parameters"); + } try { @@ -146,8 +148,8 @@ protected boolean resourceExists(Connection connection, Subscription newResource } catch (SQLException e) { - logger.warn("Error while searching for Subscriptions", e); - return false; + logger.warn("Unable to search for Subscription", e); + throw new RuntimeException("Unable to search for Subscription", e); } } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java new file mode 100644 index 000000000..9ca4fa894 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java @@ -0,0 +1,181 @@ +package dev.dsf.fhir.integration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.hl7.fhir.r4.model.Endpoint; +import org.hl7.fhir.r4.model.Endpoint.EndpointStatus; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.OrganizationAffiliation; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.Test; + +import dev.dsf.fhir.dao.EndpointDao; +import dev.dsf.fhir.dao.OrganizationDao; + +public class OrganizationAffiliationIntegrationTest extends AbstractIntegrationTest +{ + private final OrganizationDao organizationDao = getSpringWebApplicationContext().getBean(OrganizationDao.class); + private final EndpointDao endpointDao = getSpringWebApplicationContext().getBean(EndpointDao.class); + + @Test + public void testCreateAllowed() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint eDic = endpointDao.create(createEndpoint("dic.endpoint")); + assertNotNull(eDic); + assertTrue(eDic.hasIdElement()); + Endpoint eDms = endpointDao.create(createEndpoint("dms.endpoint")); + assertNotNull(eDms); + assertTrue(eDms.hasIdElement()); + + OrganizationAffiliation aDic = getWebserviceClient().create(createOrganizationAffiliation(p, m, eDic, "DIC")); + assertNotNull(aDic); + assertTrue(aDic.hasIdElement()); + + OrganizationAffiliation aDms = getWebserviceClient().create(createOrganizationAffiliation(p, m, eDms, "DMS")); + assertNotNull(aDms); + assertTrue(aDms.hasIdElement()); + } + + @Test + public void testCreateForbiddenResourceExists() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint eDic = endpointDao.create(createEndpoint("dic.endpoint")); + assertNotNull(eDic); + assertTrue(eDic.hasIdElement()); + Endpoint eDms = endpointDao.create(createEndpoint("dms.endpoint")); + assertNotNull(eDms); + assertTrue(eDms.hasIdElement()); + + OrganizationAffiliation aDic = getWebserviceClient().create(createOrganizationAffiliation(p, m, eDic, "DIC")); + assertNotNull(aDic); + assertTrue(aDic.hasIdElement()); + + expectForbidden(() -> getWebserviceClient().create(createOrganizationAffiliation(p, m, eDms, "DIC"))); + } + + @Test + public void testUpdateAllowed() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint eDic = endpointDao.create(createEndpoint("dic.endpoint")); + assertNotNull(eDic); + assertTrue(eDic.hasIdElement()); + Endpoint eDms = endpointDao.create(createEndpoint("dms.endpoint")); + assertNotNull(eDms); + assertTrue(eDms.hasIdElement()); + + OrganizationAffiliation aDic = getWebserviceClient().create(createOrganizationAffiliation(p, m, eDic, "DIC")); + assertNotNull(aDic); + assertTrue(aDic.hasIdElement()); + + OrganizationAffiliation aDms = getWebserviceClient().create(createOrganizationAffiliation(p, m, eDms, "DMS")); + assertNotNull(aDms); + assertTrue(aDms.hasIdElement()); + + aDic.getCodeFirstRep().getCodingFirstRep().setCode("TTP"); + OrganizationAffiliation updated = getWebserviceClient().update(aDic); + assertNotNull(updated); + assertEquals("2", updated.getIdElement().getVersionIdPart()); + } + + @Test + public void testUpdateForbiddenResourceExists() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint eDic = endpointDao.create(createEndpoint("dic.endpoint")); + assertNotNull(eDic); + assertTrue(eDic.hasIdElement()); + Endpoint eDms = endpointDao.create(createEndpoint("dms.endpoint")); + assertNotNull(eDms); + assertTrue(eDms.hasIdElement()); + + OrganizationAffiliation aDic = getWebserviceClient() + .create(createOrganizationAffiliation(p, m, eDic, "DIC", "TTP")); + assertNotNull(aDic); + assertTrue(aDic.hasIdElement()); + + OrganizationAffiliation aDms = getWebserviceClient() + .create(createOrganizationAffiliation(p, m, eDms, "DMS", "TTP")); + assertNotNull(aDms); + assertTrue(aDms.hasIdElement()); + + aDic.getCodeFirstRep().getCodingFirstRep().setCode("DMS"); + expectForbidden(() -> getWebserviceClient().update(aDic)); + } + + private OrganizationAffiliation createOrganizationAffiliation(Organization parent, Organization member, + Endpoint endpoint, String... codes) + { + OrganizationAffiliation a = new OrganizationAffiliation(); + a.setActive(true); + a.getOrganization().setType(ResourceType.Organization.name()) + .setReference(parent.getIdElement().toVersionless().getValue()); + a.getParticipatingOrganization().setType(ResourceType.Organization.name()) + .setReference(member.getIdElement().toVersionless().getValue()); + a.addEndpoint().setType(ResourceType.Endpoint.name()) + .setReference(endpoint.getIdElement().toVersionless().getValue()); + + Arrays.stream(codes).forEach( + c -> a.addCode().addCoding().setSystem("http://dsf.dev/fhir/CodeSystem/organization-role").setCode(c)); + + return getReadAccessHelper().addAll(a); + } + + private Organization createParentOrganization() + { + Organization p = new Organization(); + p.addIdentifier().setSystem("http://dsf.dev/sid/organization-identifier").setValue("parent.org"); + p.setActive(true); + + return getReadAccessHelper().addAll(p); + } + + private Organization createMemberOrganization() + { + Organization m = new Organization(); + m.addIdentifier().setSystem("http://dsf.dev/sid/organization-identifier").setValue("member.org"); + m.setActive(true); + + return getReadAccessHelper().addAll(m); + } + + private Endpoint createEndpoint(String subdomain) + { + Endpoint e = new Endpoint(); + e.addIdentifier().setSystem("http://dsf.dev/sid/endpoint-identifier").setValue(subdomain + ".member.org"); + e.setAddress("https://" + subdomain + ".member.org/fhir"); + + e.setStatus(EndpointStatus.ACTIVE); + e.getConnectionType().setSystem("http://terminology.hl7.org/CodeSystem/endpoint-connection-type") + .setCode("hl7-fhir-rest"); + e.getPayloadTypeFirstRep().getCodingFirstRep().setSystem("http://hl7.org/fhir/resource-types").setCode("Task"); + + return getReadAccessHelper().addAll(e); + } +} From 9a276f053584c3d55a7a3f8c8863aafb4d099780 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 14 Aug 2023 11:09:44 +0200 Subject: [PATCH 13/39] custom user-agent header, modified rev-proxy log pattern Webservice and websocket clients now send a custom user-agent header "DSF/${version}". The rev-proxy log pattern now includes the user-agent header and client certificate subject DN string. closes #68 --- .../dev/dsf/bpe/start/ExampleStarter.java | 4 +-- .../bpe/client/FhirClientProviderImpl.java | 21 ++++++++++----- .../bpe/spring/config/FhirClientConfig.java | 6 ++++- .../fhir_proxy/conf/extra/host-ssl.conf | 2 +- .../dsf/fhir/client/ClientProviderImpl.java | 14 ++++++---- .../dsf/fhir/spring/config/ClientConfig.java | 6 ++++- .../dsf/fhir/client/ClientProviderTest.java | 11 +++++--- .../integration/AbstractIntegrationTest.java | 7 ++--- .../dsf/fhir/client/AbstractJerseyClient.java | 15 ++++++++--- .../client/FhirWebserviceClientJersey.java | 11 ++++---- .../dsf/fhir/client/WebsocketClientTyrus.java | 26 +++++++++++++++++-- .../java/dev/dsf/tools/proxy/TestClient.java | 4 +-- 12 files changed, 91 insertions(+), 36 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v1/src/test/java/dev/dsf/bpe/start/ExampleStarter.java b/dsf-bpe/dsf-bpe-process-api-v1/src/test/java/dev/dsf/bpe/start/ExampleStarter.java index 968171f8f..ee307c371 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1/src/test/java/dev/dsf/bpe/start/ExampleStarter.java +++ b/dsf-bpe/dsf-bpe-process-api-v1/src/test/java/dev/dsf/bpe/start/ExampleStarter.java @@ -129,7 +129,7 @@ public FhirWebserviceClient createClient(String baseUrl) throws Exception FhirContext context = FhirContext.forR4(); ReferenceCleaner referenceCleaner = new ReferenceCleanerImpl(new ReferenceExtractorImpl()); - return new FhirWebserviceClientJersey(baseUrl, trustStore, keyStore, certificatePassword, null, null, null, 0, - 0, false, null, context, referenceCleaner); + return new FhirWebserviceClientJersey(baseUrl, trustStore, keyStore, certificatePassword, null, null, null, + null, 0, 0, false, "DSF Example Starter", context, referenceCleaner); } } diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/FhirClientProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/FhirClientProviderImpl.java index 1964a1cd0..476518d7d 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/FhirClientProviderImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/FhirClientProviderImpl.java @@ -17,10 +17,12 @@ import dev.dsf.fhir.client.WebsocketClient; import dev.dsf.fhir.client.WebsocketClientTyrus; import dev.dsf.fhir.service.ReferenceCleaner; +import dev.dsf.tools.build.BuildInfoReader; public class FhirClientProviderImpl implements FhirClientProvider, InitializingBean { private static final Logger logger = LoggerFactory.getLogger(FhirClientProviderImpl.class); + private static final String USER_AGENT_VALUE = "DSF/"; private final Map webserviceClientsByUrl = new HashMap<>(); private final Map websocketClientsBySubscriptionId = new HashMap<>(); @@ -47,13 +49,15 @@ public class FhirClientProviderImpl implements FhirClientProvider, InitializingB private final char[] localWebsocketKeyStorePassword; private final ProxyConfig proxyConfig; + private final BuildInfoReader buildInfoReader; public FhirClientProviderImpl(FhirContext fhirContext, ReferenceCleaner referenceCleaner, String localWebserviceBaseUrl, int localWebserviceReadTimeout, int localWebserviceConnectTimeout, boolean localWebserviceLogRequests, KeyStore webserviceTrustStore, KeyStore webserviceKeyStore, char[] webserviceKeyStorePassword, int remoteWebserviceReadTimeout, int remoteWebserviceConnectTimeout, boolean remoteWebserviceLogRequests, String localWebsocketUrl, KeyStore localWebsocketTrustStore, - KeyStore localWebsocketKeyStore, char[] localWebsocketKeyStorePassword, ProxyConfig proxyConfig) + KeyStore localWebsocketKeyStore, char[] localWebsocketKeyStorePassword, ProxyConfig proxyConfig, + BuildInfoReader buildInfoReader) { this.fhirContext = fhirContext; this.referenceCleaner = referenceCleaner; @@ -77,6 +81,7 @@ public FhirClientProviderImpl(FhirContext fhirContext, ReferenceCleaner referenc this.localWebsocketKeyStorePassword = localWebsocketKeyStorePassword; this.proxyConfig = proxyConfig; + this.buildInfoReader = buildInfoReader; } @Override @@ -101,6 +106,7 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(localWebsocketKeyStorePassword, "localWebsocketKeyStorePassword"); Objects.requireNonNull(proxyConfig, "proxyConfig"); + Objects.requireNonNull(buildInfoReader, "buildInfoReader"); } public String getLocalBaseUrl() @@ -123,14 +129,14 @@ private FhirWebserviceClient getClient(String webserviceUrl) FhirWebserviceClient client; if (localWebserviceBaseUrl.equals(webserviceUrl)) client = new FhirWebserviceClientJersey(webserviceUrl, webserviceTrustStore, webserviceKeyStore, - webserviceKeyStorePassword, proxyUrl, proxyUsername, proxyPassword, - localWebserviceConnectTimeout, localWebserviceReadTimeout, localWebserviceLogRequests, null, - fhirContext, referenceCleaner); + webserviceKeyStorePassword, null, proxyUrl, proxyUsername, proxyPassword, + localWebserviceConnectTimeout, localWebserviceReadTimeout, localWebserviceLogRequests, + USER_AGENT_VALUE + buildInfoReader.getProjectVersion(), fhirContext, referenceCleaner); else client = new FhirWebserviceClientJersey(webserviceUrl, webserviceTrustStore, webserviceKeyStore, - webserviceKeyStorePassword, proxyUrl, proxyUsername, proxyPassword, + webserviceKeyStorePassword, null, proxyUrl, proxyUsername, proxyPassword, remoteWebserviceConnectTimeout, remoteWebserviceReadTimeout, remoteWebserviceLogRequests, - null, fhirContext, referenceCleaner); + USER_AGENT_VALUE + buildInfoReader.getProjectVersion(), fhirContext, referenceCleaner); webserviceClientsByUrl.put(webserviceUrl, client); return client; @@ -179,7 +185,8 @@ protected WebsocketClientTyrus createWebsocketClient(Runnable reconnector, Strin localWebsocketKeyStore, localWebsocketKeyStorePassword, proxyConfig.isEnabled(localWebsocketUrl) ? proxyConfig.getUrl() : null, proxyConfig.isEnabled(localWebsocketUrl) ? proxyConfig.getUsername() : null, - proxyConfig.isEnabled(localWebsocketUrl) ? proxyConfig.getPassword() : null, subscriptionId); + proxyConfig.isEnabled(localWebsocketUrl) ? proxyConfig.getPassword() : null, + USER_AGENT_VALUE + buildInfoReader.getProjectVersion(), subscriptionId); } @Override diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/FhirClientConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/FhirClientConfig.java index 9fd745544..b56c46ef5 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/FhirClientConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/FhirClientConfig.java @@ -44,6 +44,9 @@ public class FhirClientConfig implements InitializingBean @Autowired private FhirConfig fhirConfig; + @Autowired + private BuildInfoReaderConfig buildInfoReaderConfig; + @Override public void afterPropertiesSet() throws Exception { @@ -105,7 +108,8 @@ public FhirClientProvider clientProvider() keyStorePassword, propertiesConfig.getWebserviceClientRemoteReadTimeout(), propertiesConfig.getWebserviceClientRemoteConnectTimeout(), propertiesConfig.getWebserviceClientRemoteVerbose(), getWebsocketUrl(), webserviceTrustStore, - webserviceKeyStore, keyStorePassword, propertiesConfig.proxyConfig()); + webserviceKeyStore, keyStorePassword, propertiesConfig.proxyConfig(), + buildInfoReaderConfig.buildInfoReader()); } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | PKCSException e) { diff --git a/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf b/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf index 1581ebfb8..4982da927 100755 --- a/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf +++ b/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf @@ -49,6 +49,6 @@ Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains ProxyPassReverse ws://${APP_SERVER_IP}:8080/fhir/ws -CustomLog /proc/self/fd/1 "%h %t \"%r\" %>s %b %{SSL_PROTOCOL}x %{SSL_CIPHER}x" +CustomLog /proc/self/fd/1 "%h %t \"%r\" %>s %b %{SSL_PROTOCOL}x %{SSL_CIPHER}x %{user-agent}i %{SSL_CLIENT_S_DN}x" \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/client/ClientProviderImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/client/ClientProviderImpl.java index c64303f83..d51bddf7e 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/client/ClientProviderImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/client/ClientProviderImpl.java @@ -13,10 +13,12 @@ import dev.dsf.fhir.dao.EndpointDao; import dev.dsf.fhir.help.ExceptionHandler; import dev.dsf.fhir.service.ReferenceCleaner; +import dev.dsf.tools.build.BuildInfoReader; public class ClientProviderImpl implements ClientProvider, InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ClientProviderImpl.class); + private static final String USER_AGENT_VALUE = "DSF/"; private final KeyStore webserviceTrustStore; private final KeyStore webserviceKeyStore; @@ -30,11 +32,12 @@ public class ClientProviderImpl implements ClientProvider, InitializingBean private final ReferenceCleaner referenceCleaner; private final EndpointDao endpointDao; private final ExceptionHandler exceptionHandler; + private final BuildInfoReader buildInfoReader; public ClientProviderImpl(KeyStore webserviceTrustStore, KeyStore webserviceKeyStore, char[] webserviceKeyStorePassword, int remoteReadTimeout, int remoteConnectTimeout, ProxyConfig proxyConfig, boolean logRequests, FhirContext fhirContext, ReferenceCleaner referenceCleaner, EndpointDao endpointDao, - ExceptionHandler exceptionHandler) + ExceptionHandler exceptionHandler, BuildInfoReader buildInfoReader) { this.webserviceTrustStore = webserviceTrustStore; this.webserviceKeyStore = webserviceKeyStore; @@ -47,6 +50,7 @@ public ClientProviderImpl(KeyStore webserviceTrustStore, KeyStore webserviceKeyS this.referenceCleaner = referenceCleaner; this.endpointDao = endpointDao; this.exceptionHandler = exceptionHandler; + this.buildInfoReader = buildInfoReader; } @Override @@ -55,13 +59,12 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(webserviceTrustStore, "webserviceTrustStore"); Objects.requireNonNull(webserviceKeyStore, "webserviceKeyStore"); Objects.requireNonNull(webserviceKeyStorePassword, "webserviceKeyStorePassword"); - Objects.requireNonNull(proxyConfig, "proxyConfig"); - Objects.requireNonNull(fhirContext, "fhirContext"); Objects.requireNonNull(referenceCleaner, "referenceCleaner"); Objects.requireNonNull(endpointDao, "endpointDao"); Objects.requireNonNull(exceptionHandler, "exceptionHandler"); + Objects.requireNonNull(buildInfoReader, "buildInfoReader"); } @Override @@ -74,8 +77,9 @@ public Optional getClient(String serverBase) char[] proxyPassword = proxyConfig.isEnabled(serverBase) ? proxyConfig.getPassword() : null; FhirWebserviceClient client = new FhirWebserviceClientJersey(serverBase, webserviceTrustStore, - webserviceKeyStore, webserviceKeyStorePassword, proxyUrl, proxyUsername, proxyPassword, - remoteConnectTimeout, remoteReadTimeout, logRequests, null, fhirContext, referenceCleaner); + webserviceKeyStore, webserviceKeyStorePassword, null, proxyUrl, proxyUsername, proxyPassword, + remoteConnectTimeout, remoteReadTimeout, logRequests, + USER_AGENT_VALUE + buildInfoReader.getProjectVersion(), fhirContext, referenceCleaner); return Optional.of(client); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/ClientConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/ClientConfig.java index b6a943975..ffd8b61ad 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/ClientConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/ClientConfig.java @@ -50,6 +50,9 @@ public class ClientConfig implements InitializingBean @Autowired private ReferenceConfig referenceConfig; + @Autowired + private BuildInfoReaderConfig buildInfoReaderConfig; + @Bean public ClientProvider clientProvider() { @@ -67,7 +70,8 @@ public ClientProvider clientProvider() propertiesConfig.getWebserviceClientReadTimeout(), propertiesConfig.getWebserviceClientConnectTimeout(), propertiesConfig.proxyConfig(), propertiesConfig.getWebserviceClientVerbose(), fhirConfig.fhirContext(), - referenceConfig.referenceCleaner(), daoConfig.endpointDao(), helperConfig.exceptionHandler()); + referenceConfig.referenceCleaner(), daoConfig.endpointDao(), helperConfig.exceptionHandler(), + buildInfoReaderConfig.buildInfoReader()); } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | PKCSException e) { diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/client/ClientProviderTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/client/ClientProviderTest.java index 37cc1cae9..e176c1b78 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/client/ClientProviderTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/client/ClientProviderTest.java @@ -21,6 +21,7 @@ import dev.dsf.fhir.function.SupplierWithSqlException; import dev.dsf.fhir.help.ExceptionHandler; import dev.dsf.fhir.service.ReferenceCleaner; +import dev.dsf.tools.build.BuildInfoReader; public class ClientProviderTest { @@ -28,6 +29,7 @@ public class ClientProviderTest private EndpointDao endpointDao; private ExceptionHandler exceptionHandler; private ClientProvider provider; + private BuildInfoReader buildInfoReader; @Before public void before() throws Exception @@ -49,11 +51,12 @@ public void before() throws Exception referenceCleaner = mock(ReferenceCleaner.class); endpointDao = mock(EndpointDao.class); exceptionHandler = mock(ExceptionHandler.class); + buildInfoReader = mock(BuildInfoReader.class); provider = new ClientProviderImpl(webserviceTrustStore, webserviceKeyStore, webserviceKeyStorePassword, remoteReadTimeout, remoteConnectTimeout, new ProxyConfigImpl(remoteProxySchemeHostPort, remoteProxyUsername, remoteProxyPassword, null), - logRequests, fhirContext, referenceCleaner, endpointDao, exceptionHandler); + logRequests, fhirContext, referenceCleaner, endpointDao, exceptionHandler, buildInfoReader); } @Test @@ -62,6 +65,7 @@ public void testGetClientExisting() throws Exception { final String serverBase = "http://foo/fhir/"; + when(buildInfoReader.getProjectVersion()).thenReturn("1.2.3-TEST"); when(exceptionHandler.handleSqlException(any(SupplierWithSqlException.class))).thenReturn(true); Optional client = provider.getClient(serverBase); @@ -69,8 +73,9 @@ public void testGetClientExisting() throws Exception assertTrue(client.isPresent()); assertEquals(serverBase, client.get().getBaseUrl()); + verify(buildInfoReader).getProjectVersion(); verify(exceptionHandler).handleSqlException(any(SupplierWithSqlException.class)); - verifyNoMoreInteractions(referenceCleaner, endpointDao, exceptionHandler); + verifyNoMoreInteractions(referenceCleaner, endpointDao, exceptionHandler, buildInfoReader); } @Test @@ -84,6 +89,6 @@ public void testGetClientExistingNotFound() throws Exception assertTrue(client.isEmpty()); verify(exceptionHandler).handleSqlException(any(SupplierWithSqlException.class)); - verifyNoMoreInteractions(referenceCleaner, endpointDao, exceptionHandler); + verifyNoMoreInteractions(referenceCleaner, endpointDao, exceptionHandler, buildInfoReader); } } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java index 4fa9b858e..507514ba8 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java @@ -157,15 +157,16 @@ public static void beforeClass() throws Exception private static FhirWebserviceClient createWebserviceClient(KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, FhirContext fhirContext, ReferenceCleaner referenceCleaner) { - return new FhirWebserviceClientJersey(BASE_URL, trustStore, keyStore, keyStorePassword, null, null, null, 0, 0, - false, null, fhirContext, referenceCleaner); + return new FhirWebserviceClientJersey(BASE_URL, trustStore, keyStore, keyStorePassword, null, null, null, null, + 0, 0, false, "DSF Integration Test Client", fhirContext, referenceCleaner); } private static WebsocketClient createWebsocketClient(KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, String subscriptionIdPart) { return new WebsocketClientTyrus(() -> - {}, URI.create(WEBSOCKET_URL), trustStore, keyStore, keyStorePassword, null, null, null, subscriptionIdPart); + {}, URI.create(WEBSOCKET_URL), trustStore, keyStore, keyStorePassword, null, null, null, + "Integration Test Client", subscriptionIdPart); } private static JettyServer startFhirServer() throws Exception diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/AbstractJerseyClient.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/AbstractJerseyClient.java index dcef0efbe..7a1dc6240 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/AbstractJerseyClient.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/AbstractJerseyClient.java @@ -20,7 +20,9 @@ import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.HttpHeaders; public class AbstractJerseyClient { @@ -37,13 +39,14 @@ public class AbstractJerseyClient public AbstractJerseyClient(String baseUrl, KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, ObjectMapper objectMapper, Collection componentsToRegister) { - this(baseUrl, trustStore, keyStore, keyStorePassword, null, null, null, 0, 0, objectMapper, - componentsToRegister, false); + this(baseUrl, trustStore, keyStore, keyStorePassword, objectMapper, componentsToRegister, null, null, null, 0, + 0, false, null); } public AbstractJerseyClient(String baseUrl, KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, - String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, int connectTimeout, int readTimeout, - ObjectMapper objectMapper, Collection componentsToRegister, boolean logRequests) + ObjectMapper objectMapper, Collection componentsToRegister, String proxySchemeHostPort, + String proxyUserName, char[] proxyPassword, int connectTimeout, int readTimeout, boolean logRequests, + String userAgentValue) { SSLContext sslContext = null; if (trustStore != null && keyStore == null && keyStorePassword == null) @@ -64,6 +67,10 @@ else if (trustStore != null && keyStore != null && keyStorePassword != null) config.property(ClientProperties.PROXY_PASSWORD, proxyPassword == null ? null : String.valueOf(proxyPassword)); builder = builder.withConfig(config); + if (userAgentValue != null && !userAgentValue.isBlank()) + builder = builder.register((ClientRequestFilter) requestContext -> requestContext.getHeaders() + .add(HttpHeaders.USER_AGENT, userAgentValue)); + builder = builder.readTimeout(readTimeout, TimeUnit.MILLISECONDS).connectTimeout(connectTimeout, TimeUnit.MILLISECONDS); diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java index d3cad44ae..8e2b15236 100755 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java @@ -77,12 +77,13 @@ private static Class getFhirClass(ResourceType type) private final PreferReturnOutcomeWithRetry preferReturnOutcome; public FhirWebserviceClientJersey(String baseUrl, KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, - String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, int connectTimeout, int readTimeout, - boolean logRequests, ObjectMapper objectMapper, FhirContext fhirContext, ReferenceCleaner referenceCleaner) + ObjectMapper objectMapper, String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, + int connectTimeout, int readTimeout, boolean logRequests, String userAgentValue, FhirContext fhirContext, + ReferenceCleaner referenceCleaner) { - super(baseUrl, trustStore, keyStore, keyStorePassword, proxySchemeHostPort, proxyUserName, proxyPassword, - connectTimeout, readTimeout, objectMapper, Collections.singleton(new FhirAdapter(fhirContext)), - logRequests); + super(baseUrl, trustStore, keyStore, keyStorePassword, objectMapper, + Collections.singleton(new FhirAdapter(fhirContext)), proxySchemeHostPort, proxyUserName, proxyPassword, + connectTimeout, readTimeout, logRequests, userAgentValue); this.referenceCleaner = referenceCleaner; diff --git a/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java b/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java index 334e057c4..856194fdf 100755 --- a/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java +++ b/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.util.Base64; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; @@ -22,9 +23,11 @@ import org.slf4j.LoggerFactory; import ca.uhn.fhir.parser.IParser; +import jakarta.websocket.ClientEndpointConfig; import jakarta.websocket.CloseReason; import jakarta.websocket.DeploymentException; import jakarta.websocket.Session; +import jakarta.ws.rs.core.HttpHeaders; public class WebsocketClientTyrus implements WebsocketClient { @@ -76,6 +79,7 @@ public boolean onDisconnect(CloseReason closeReason) private final String proxySchemeHostPort; private final String proxyUserName; private final char[] proxyPassword; + private final String userAgentValue; private final ClientEndpoint endpoint; private ClientManager manager; @@ -84,7 +88,7 @@ public boolean onDisconnect(CloseReason closeReason) public WebsocketClientTyrus(Runnable reconnector, URI wsUri, KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, - String subscriptionIdPart) + String userAgentValue, String subscriptionIdPart) { this.wsUri = wsUri; @@ -99,6 +103,7 @@ else if (trustStore != null && keyStore != null && keyStorePassword != null) this.proxySchemeHostPort = proxySchemeHostPort; this.proxyUserName = proxyUserName; this.proxyPassword = proxyPassword; + this.userAgentValue = userAgentValue; this.endpoint = createClientEndpoint(reconnector, subscriptionIdPart); } @@ -133,10 +138,12 @@ public void connect() manager.getProperties().put(ClientProperties.PROXY_HEADERS, proxyHeaders); } + ClientEndpointConfig config = createConfig(userAgentValue); + try { logger.debug("Connecting to websocket {} and waiting for connection", wsUri); - connection = manager.connectToServer(endpoint, wsUri); + connection = manager.connectToServer(endpoint, config, wsUri); } catch (DeploymentException e) { @@ -150,6 +157,21 @@ public void connect() } } + private ClientEndpointConfig createConfig(String userAgentValue) + { + if (userAgentValue == null || userAgentValue.isBlank()) + return null; + + ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() + { + public void beforeRequest(java.util.Map> headers) + { + headers.put(HttpHeaders.USER_AGENT, Collections.singletonList(userAgentValue)); + } + }; + return ClientEndpointConfig.Builder.create().configurator(configurator).build(); + } + @Override public void disconnect() { diff --git a/dsf-tools/dsf-tools-proxy-test/src/main/java/dev/dsf/tools/proxy/TestClient.java b/dsf-tools/dsf-tools-proxy-test/src/main/java/dev/dsf/tools/proxy/TestClient.java index fddab51df..6936dbd2c 100755 --- a/dsf-tools/dsf-tools-proxy-test/src/main/java/dev/dsf/tools/proxy/TestClient.java +++ b/dsf-tools/dsf-tools-proxy-test/src/main/java/dev/dsf/tools/proxy/TestClient.java @@ -15,8 +15,8 @@ public class TestClient extends AbstractJerseyClient public TestClient(String baseUrl, String proxySchemeHostPort, String proxyUserName, char[] proxyPassword) { - super(baseUrl, null, null, null, proxySchemeHostPort, proxyUserName, proxyPassword, 5_000, 5_000, null, null, - true); + super(baseUrl, null, null, null, null, null, proxySchemeHostPort, proxyUserName, proxyPassword, 5_000, 5_000, + true, "DSF Proxy Test Client"); logger.info("baseUrl: {}", baseUrl); logger.info("proxySchemeHostPort: {}", proxySchemeHostPort); From 9ec5a96d48631d5928a33a7f2f6d1431a97430ee Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 14 Aug 2023 12:19:08 +0200 Subject: [PATCH 14/39] Adds environment variable to configure mod_ssl param SSLVerifyClient closes #70 --- dsf-docker/fhir_proxy/Dockerfile | 3 +++ dsf-docker/fhir_proxy/README.md | 5 +++-- dsf-docker/fhir_proxy/conf/extra/host-ssl.conf | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/dsf-docker/fhir_proxy/Dockerfile b/dsf-docker/fhir_proxy/Dockerfile index 61ee64d1f..128e04ad3 100755 --- a/dsf-docker/fhir_proxy/Dockerfile +++ b/dsf-docker/fhir_proxy/Dockerfile @@ -14,6 +14,9 @@ RUN mkdir /usr/local/apache2/ssl/ && \ ENV SSL_CERTIFICATE_CHAIN_FILE="/does/not/exist" ENV SSL_CA_DN_REQUEST_FILE="/does/not/exist" +# setting default value - client certificate required, use 'optional' when using OIDC +ENV SSL_VERIFY_CLIENT="require" + # timeout (seconds) for reverse proxy to app server http connection, time the proxy waits for a reply ENV PROXY_PASS_TIMEOUT_HTTP=60 # timeout (seconds) for reverse proxy to app server ws connection, time the proxy waits for a reply diff --git a/dsf-docker/fhir_proxy/README.md b/dsf-docker/fhir_proxy/README.md index d36e8e1d9..8fef8d6b4 100755 --- a/dsf-docker/fhir_proxy/README.md +++ b/dsf-docker/fhir_proxy/README.md @@ -4,9 +4,10 @@ ${HTTPS\_SERVER\_NAME\_PORT} - example: localhost:8443 ${APP\_SERVER\_IP} - example: 172.28.1.3 ${SSL\_CERTIFICATE\_FILE} - to set apache config param `SSLCertificateFile` ${SSL\_CERTIFICATE\_KEY\_FILE} - to set apache config param `SSLCertificateKeyFile` -${SSL\_CERTIFICATE\_CHAIN\_FILE} - to set apache config param `SSLCertificateChainFile` (optional) +${SSL\_CERTIFICATE\_CHAIN\_FILE} - to set apache config param `SSLCertificateChainFile` (optional environment variable) ${SSL\_CA\_CERTIFICATE\_FILE} - to set apache config param `SSLCACertificateFile` -${SSL\_CA\_DN\_REQUEST\_FILE} - to set apache config param `SSLCADNRequestFile` (optional) +${SSL\_CA\_DN\_REQUEST\_FILE} - to set apache config param `SSLCADNRequestFile` (optional environment variable) +${SSL\_VERIFY\_CLIENT} - to set apache config param `SSLVerifyClient `, default value `require`, set to `optional` when using OIDC authentication ${PROXY\_PASS\_TIMEOUT\_HTTP} - timeout (seconds) for reverse proxy to app server http connection, time the proxy waits for a reply, default: `60` seconds ${PROXY\_PASS\_TIMEOUT\_WS} - timeout (seconds) for reverse proxy to app server ws connection, time the proxy waits for a reply, default: `60` seconds ${PROXY\_PASS\_CONNECTION\_TIMEOUT\_HTTP} - connection timeout (seconds) for reverse proxy to app server http connection, time the proxy waits for a connection to be established, default: `30` seconds diff --git a/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf b/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf index 1581ebfb8..e17493c88 100755 --- a/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf +++ b/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf @@ -28,7 +28,7 @@ SSLCACertificateFile "${SSL_CA_CERTIFICATE_FILE}" # SSLVerifyDepth n: max n signing CAs allowed between client certificate and root certificate SSLVerifyDepth 3 -SSLVerifyClient require +SSLVerifyClient "${SSL_VERIFY_CLIENT}" SSLOptions +ExportCertData RequestHeader set X-ClientCert "" From 4f5dec7bdf5fc727a9b54de0559631ee12e90a43 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 14 Aug 2023 16:09:02 +0200 Subject: [PATCH 15/39] added user role config Co-authored-by: Reto Wettstein --- dsf-docker-test-setup/fhir/docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dsf-docker-test-setup/fhir/docker-compose.yml b/dsf-docker-test-setup/fhir/docker-compose.yml index 62992c61b..dfcf993ec 100755 --- a/dsf-docker-test-setup/fhir/docker-compose.yml +++ b/dsf-docker-test-setup/fhir/docker-compose.yml @@ -74,6 +74,8 @@ services: - SEARCH - HISTORY - PERMANENT_DELETE + practitioner-role: + - http://dsf.dev/fhir/CodeSystem/practitioner-role|DSF_ADMIN DEV_DSF_SERVER_AUTH_TRUST_CLIENT_CERTIFICATE_CAS: /run/secrets/app_client_trust_certificates.pem networks: frontend: From 7aa6b5c61dcbdfc2b4ff5e63aa6b4660fe1a94a2 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 14 Aug 2023 16:24:54 +0200 Subject: [PATCH 16/39] refactored code, minor ui design improvements Co-authored-by: Reto Wettstein --- .../dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 4 +- .../dsf/fhir/adapter/InputHtmlGenerator.java | 4 +- .../src/main/resources/static/form.css | 46 ++++++------------- .../src/main/resources/static/form.js | 39 ++++++++-------- 4 files changed, 36 insertions(+), 57 deletions(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java index 46c1df7fb..c61087df1 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java @@ -160,7 +160,7 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno + uriInfo.getPath() + "\n"); out.write("\n"); out.write("\n"); + + ");checkBookmarked();" + adaptFormInputsIfTask(resource) + "\">\n"); out.write("
\n"); @@ -435,7 +435,7 @@ private boolean isHtmlEnabled(Class resourceType) return htmlGeneratorsByType.containsKey(resourceType); } - private String adaptTaskFormInputs(BaseResource resource) + private String adaptFormInputsIfTask(BaseResource resource) { if (resource instanceof Task task) return Task.TaskStatus.DRAFT.equals(task.getStatus()) ? "adaptTaskFormInputs();" : ""; diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java index 9e8a29ae9..b8396e7ac 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java @@ -231,9 +231,9 @@ private void writePlaceholderButton(String elementName, String value, boolean wr out.write("\n"); - out.write("Use placeholder\n"); + out.write("Use placeholder value\n"); out.write( - "\n"); + "\n"); out.write("\n"); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css index f1a0cb4b9..0ce7c2171 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css @@ -15,8 +15,8 @@ fieldset#form-fieldset { .row { border-radius: 5px; - padding: 0 15px 15px 15px; - margin-bottom: 10px; + padding: 0 1em 1em 1em; + margin-bottom: 0.7em; background-color: #f2f2f2; } @@ -302,8 +302,8 @@ button.submit[disabled] { .plus-minus-icon { position: absolute; - bottom: 0; - right: 0; + bottom: 0.2em; + right: 0.1em; cursor: pointer; } @@ -316,14 +316,15 @@ button.submit[disabled] { } .input-group { - position: relative + display: flex; + flex-direction: row; } .input-group-svg { - position: absolute; - right: 0.45rem; - bottom: 0.65rem; cursor: pointer; + align-self: center; + margin-left: 0.46em; + rotate: 180deg; } .input-group-svg > path { @@ -334,32 +335,11 @@ button.submit[disabled] { fill: #326F95; } -input[type="date"]::-webkit-inner-spin-button, -input[type="date"]::-webkit-calendar-picker-indicator { - display: none; - -webkit-appearance: none; -} - -input[type="time"]::-webkit-inner-spin-button, -input[type="time"]::-webkit-calendar-picker-indicator { - display: none; - -webkit-appearance: none; -} - -input[type="dateTime-local"]::-webkit-inner-spin-button, -input[type="dateTime-local"]::-webkit-calendar-picker-indicator { - display: none; - -webkit-appearance: none; -} - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - input[type=number] { - -moz-appearance: textfield; + -webkit-appearance: textfield; + -moz-appearance: textfield; + -o-appearance: textfield; + appearance: textfield; } ::placeholder { diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js index d943d81b5..c629d1004 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js @@ -397,19 +397,12 @@ function adaptTaskFormInputs() { const task = getResourceAsJson() if (task.meta !== null && task.meta.profile !== null && task.meta.profile.length > 0) { - const profile = task.meta.profile[0].split("|") + const profile = task.meta.profile[0] - if (profile.length > 0) { - let currentUrl = window.location.origin + window.location.pathname - let requestUrl = currentUrl.slice(0, currentUrl.indexOf("/Task")) + "/StructureDefinition?url=" + profile[0] + let currentUrl = window.location.origin + window.location.pathname + let requestUrl = currentUrl.slice(0, currentUrl.indexOf("/Task")) + "/StructureDefinition?url=" + profile - if (profile.length > 1) { - requestUrl = requestUrl + "&version=" + profile[1] - } - - loadStructureDefinition(requestUrl) - .then(bundle => parseStructureDefinition(bundle)) - } + loadStructureDefinition(requestUrl).then(bundle => parseStructureDefinition(bundle)) } } } @@ -499,12 +492,12 @@ function modifyInputRow(definition, indices) { const label = row.querySelector("label") if (label) { - const cardinalities = htmlToElement(" [" + definition.min + ".." + definition.max + "]") + const cardinalities = htmlToElement('', " [" + definition.min + ".." + definition.max + "]") label.appendChild(cardinalities) if (definition.max !== "1") { - const plusIcon = htmlToElement("") - const plusIconSvg = htmlToElement("Add additional input") + const plusIcon = htmlToElement('') + const plusIconSvg = htmlToElement('Add additional input') plusIconSvg.addEventListener("click", () => { appendInputRowAfter(row, definition, indices) @@ -527,11 +520,12 @@ function appendInputRowAfter(inputRow, definition, indices) { clone.querySelectorAll("[index]").forEach( e => { e.setAttribute("index", index) }) clone.querySelector("span[class='plus-minus-icon']").remove() + clone.querySelectorAll("input").forEach( e => { e.value = '' }) const label = clone.querySelector("label") if (label) { - const minusIcon = htmlToElement("") - const minusIconSvg = htmlToElement("") + const minusIcon = htmlToElement('') + const minusIconSvg = htmlToElement('') minusIconSvg.addEventListener("click", () => { clone.remove() }) @@ -548,10 +542,15 @@ function insertPlaceholderInValue(element, name, placeholder) { input.value = placeholder } -function htmlToElement(html) { - const template = document.createElement('template'); - template.innerHTML = html; - return template.content.firstChild; +function htmlToElement(html, innerText) { + const template = document.createElement('template') + template.innerHTML = html + const child = template.content.firstChild + + if (innerText) + child.innerText = innerText + + return child } function getDefinitionId(definition) { From 2fee6b1e444d9d6535eb17a51e0092c71b6b12c4 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 14 Aug 2023 16:33:14 +0200 Subject: [PATCH 17/39] removed not needed TODO and commented out code --- .../src/main/java/dev/dsf/fhir/search/SearchQuery.java | 1 - .../search/parameters/basic/AbstractTokenParameter.java | 9 --------- 2 files changed, 10 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java index 8204695a5..fbd7a2b91 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java @@ -273,7 +273,6 @@ private String createFilterQuery(Map> queryParameters) return elements.collect(Collectors.joining(" AND ")); } - // TODO rename ? public List getUnsupportedQueryParameters() { return errors; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java index bdbb2c281..59cdc9b96 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/basic/AbstractTokenParameter.java @@ -28,15 +28,6 @@ protected void doConfigure(List errors, Strin String queryParameterValue) { valueAndType = TokenValueAndSearchType.fromParamValue(parameterName, queryParameterName, queryParameterValue); - - // TODO - // if ((queryParameters.get(parameterName) != null - // || queryParameters.get(parameterName + TokenValueAndSearchType.NOT) != null) - // && ((queryParameters.getOrDefault(parameterName, Collections.emptyList())).size() - // + (queryParameters.getOrDefault(parameterName + TokenValueAndSearchType.NOT, - // Collections.emptyList())).size()) > 1) - // addError(new SearchQueryParameterError(SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, - // parameterName, queryParameters.get(parameterName))); } @Override From 26bd6f68b991f426cd34bc3b641bc71825c57636 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 15 Aug 2023 16:34:01 +0200 Subject: [PATCH 18/39] reworked OrganizationAffiliationAuthorizationRule OrganizationAffiliation resources are unique based on parent organization + member organization + endpoint. A role can not be defined for different OrganizationAffiliation resources for the same parent organization + member organization combination. --- ...anizationAffiliationAuthorizationRule.java | 85 ++++++++++--- .../OrganizationAuthorizationRule.java | 4 +- .../fhir/dao/OrganizationAffiliationDao.java | 5 + .../jdbc/OrganizationAffiliationDaoJdbc.java | 33 +++++ .../dao/OrganizationAffiliationDaoTest.java | 36 ++++++ ...rganizationAffiliationIntegrationTest.java | 114 +++++++++++++++--- 6 files changed, 243 insertions(+), 34 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java index 6878b4d7a..763795930 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAffiliationAuthorizationRule.java @@ -7,10 +7,13 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.OrganizationAffiliation; +import org.hl7.fhir.r4.model.ResourceType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,19 +84,16 @@ private Optional newResourceOk(Connection connection, Identity identity, errors.add("OrganizationAffiliation.participatingOrganization missing"); } - if (newResource.hasEndpoint()) + if (newResource.getEndpoint().size() == 1) { - for (int i = 0; i < newResource.getEndpoint().size(); i++) + if (!newResource.getEndpointFirstRep().hasReference()) { - if (!newResource.getEndpoint().get(i).hasReference()) - { - errors.add("OrganizationAffiliation.endpoint[" + i + "].reference missing"); - } + errors.add("OrganizationAffiliation.endpoint.reference missing"); } } else { - errors.add("OrganizationAffiliation.endpoint missing"); + errors.add("OrganizationAffiliation.endpoint missing or more than one"); } if (newResource.hasCode()) @@ -125,18 +125,35 @@ private Optional newResourceOk(Connection connection, Identity identity, @Override protected boolean resourceExists(Connection connection, OrganizationAffiliation newResource) { - return organizationAffiliationWithParentAndMemberAndRoleExists(connection, newResource); + return organizationAffiliationWithParentAndMemberAndEndpointExists(connection, newResource) + || organizationAffiliationWithParentAndMemberAndAnyRoleExists(connection, newResource); } - private boolean organizationAffiliationWithParentAndMemberAndRoleExists(Connection connection, + private boolean organizationAffiliationWithParentAndMemberAndEndpointExists(Connection connection, OrganizationAffiliation newResource) { - Map> queryParameters = Map.of("primary-organization", - Collections.singletonList(newResource.getOrganization().getReference()), "participating-organization", - Collections.singletonList(newResource.getParticipatingOrganization().getReference()), "role", - newResource.getCode().stream().map(CodeableConcept::getCoding).flatMap(List::stream) - .map(c -> c.getSystem() + "|" + c.getCode()).toList()); + return organizationAffiliationExists(connection, + queryParameters(newResource, "endpoint", newResource.getEndpointFirstRep().getReference())); + } + + private boolean organizationAffiliationWithParentAndMemberAndAnyRoleExists(Connection connection, + OrganizationAffiliation newResource) + { + return newResource.getCode().stream().map(CodeableConcept::getCoding).flatMap(List::stream) + .anyMatch(role -> organizationAffiliationExists(connection, + queryParameters(newResource, "role", role.getSystem() + "|" + role.getCode()))); + } + private Map> queryParameters(OrganizationAffiliation newResource, String param, String value) + { + return Map.of("primary-organization", Collections.singletonList(newResource.getOrganization().getReference()), + "participating-organization", + Collections.singletonList(newResource.getParticipatingOrganization().getReference()), param, + Collections.singletonList(value)); + } + + private boolean organizationAffiliationExists(Connection connection, Map> queryParameters) + { OrganizationAffiliationDao dao = getDao(); SearchQuery query = dao.createSearchQueryWithoutUserFilter(0, 0) .configureParameters(queryParameters); @@ -166,7 +183,39 @@ protected boolean modificationsOk(Connection connection, OrganizationAffiliation OrganizationAffiliation newResource) { return isParentSame(oldResource, newResource) && isMemberSame(oldResource, newResource) - && !resourceExists(connection, newResource); + && isEndpointSame(oldResource, newResource) + && !organizationAffiliationWithParentAndMemberAndAnyRoleAndNotEndpointExists(connection, newResource); + } + + private boolean organizationAffiliationWithParentAndMemberAndAnyRoleAndNotEndpointExists(Connection connection, + OrganizationAffiliation newResource) + { + return newResource.getCode().stream().map(CodeableConcept::getCoding).flatMap(List::stream).anyMatch( + organizationAffiliationWithParentAndMemberAndRoleAndNotEndpointExists(connection, newResource)); + } + + private Predicate organizationAffiliationWithParentAndMemberAndRoleAndNotEndpointExists( + Connection connection, OrganizationAffiliation newResource) + { + return role -> + { + try + { + return getDao().existsNotDeletedByParentOrganizationMemberOrganizationRoleAndNotEndpointWithTransaction( + connection, + parameterConverter.toUuid(ResourceType.Organization.name(), + newResource.getOrganization().getReferenceElement().getIdPart()), + parameterConverter.toUuid(ResourceType.Organization.name(), + newResource.getParticipatingOrganization().getReferenceElement().getIdPart()), + role.getSystem(), role.getCode(), parameterConverter.toUuid(ResourceType.Endpoint.name(), + newResource.getEndpointFirstRep().getReferenceElement().getIdPart())); + } + catch (SQLException e) + { + logger.warn("Unable to search for OrganizationAffiliation", e); + throw new RuntimeException("Unable to search for OrganizationAffiliation", e); + } + }; } private boolean isParentSame(OrganizationAffiliation oldResource, OrganizationAffiliation newResource) @@ -179,4 +228,10 @@ private boolean isMemberSame(OrganizationAffiliation oldResource, OrganizationAf return oldResource.getParticipatingOrganization().getReference() .equals(newResource.getParticipatingOrganization().getReference()); } + + private boolean isEndpointSame(OrganizationAffiliation oldResource, OrganizationAffiliation newResource) + { + return oldResource.getEndpointFirstRep().getReference() + .equals(newResource.getEndpointFirstRep().getReference()); + } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAuthorizationRule.java index 5b6b18082..bb1e306e7 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/OrganizationAuthorizationRule.java @@ -135,8 +135,8 @@ private boolean organizationWithThumbprintExists(Connection connection, String t } catch (SQLException e) { - logger.warn("Error while searching for Organization with thumbprint", e); - return false; + logger.warn("Unable to search for Organization", e); + throw new RuntimeException("Unable to search for OrganizationAffiliation", e); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/OrganizationAffiliationDao.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/OrganizationAffiliationDao.java index 5e66c6dc9..663251620 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/OrganizationAffiliationDao.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/OrganizationAffiliationDao.java @@ -3,6 +3,7 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.hl7.fhir.r4.model.OrganizationAffiliation; @@ -10,4 +11,8 @@ public interface OrganizationAffiliationDao extends ResourceDao readActiveNotDeletedByMemberOrganizationIdentifierIncludingOrganizationIdentifiersWithTransaction( Connection connection, String identifierValue) throws SQLException; + + boolean existsNotDeletedByParentOrganizationMemberOrganizationRoleAndNotEndpointWithTransaction( + Connection connection, UUID parentOrganization, UUID memberOrganization, String roleSystem, String roleCode, + UUID endpoint) throws SQLException; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java index 199b57f74..0b814ac96 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationAffiliationDaoJdbc.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.UUID; import javax.sql.DataSource; @@ -106,4 +107,36 @@ public List readActiveNotDeletedByMemberOrganizationIde } } } + + @Override + public boolean existsNotDeletedByParentOrganizationMemberOrganizationRoleAndNotEndpointWithTransaction( + Connection connection, UUID parentOrganization, UUID memberOrganization, String roleSystem, String roleCode, + UUID endpoint) throws SQLException + { + Objects.requireNonNull(connection, "connection"); + Objects.requireNonNull(parentOrganization, "parentOrganization"); + Objects.requireNonNull(memberOrganization, "memberOrganization"); + Objects.requireNonNull(roleSystem, "roleSystem"); + Objects.requireNonNull(roleCode, "roleCode"); + Objects.requireNonNull(endpoint, "endpoint"); + + try (PreparedStatement statement = connection + .prepareStatement("SELECT count(*) FROM current_organization_affiliations " + + "WHERE organization_affiliation->'organization'->>'reference' = ? " + + "AND organization_affiliation->'participatingOrganization'->>'reference' = ? " + + "AND (SELECT jsonb_agg(coding) FROM jsonb_array_elements(organization_affiliation->'code') AS code, jsonb_array_elements(code->'coding') AS coding) @> ?::jsonb " + + "AND ? NOT IN (SELECT reference->>'reference' FROM jsonb_array_elements(organization_affiliation->'endpoint') AS reference)")) + { + statement.setString(1, "Organization/" + parentOrganization.toString()); + statement.setString(2, "Organization/" + memberOrganization.toString()); + statement.setString(3, "[{\"code\": \"" + roleCode + "\", \"system\": \"" + roleSystem + "\"}]"); + statement.setString(4, "Endpoint/" + endpoint.toString()); + + logger.trace("Executing query '{}'", statement); + try (ResultSet result = statement.executeQuery()) + { + return result.next() && result.getInt(1) > 0; + } + } + } } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationAffiliationDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationAffiliationDaoTest.java index eb4c23915..1d33a86c5 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationAffiliationDaoTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationAffiliationDaoTest.java @@ -1,6 +1,7 @@ package dev.dsf.fhir.dao; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -476,4 +477,39 @@ public void testUpdateWithExistingBinaryUpdateParentOrg() throws Exception orgDao.update(createdParentOrg); } + + @Test + public void testExistsNotDeletedByParentOrganizationMemberOrganizationRoleAndNotEndpointWithTransaction() + throws Exception + { + final UUID parentOrganization = UUID.randomUUID(); + final UUID memberOrganization = UUID.randomUUID(); + final UUID endpoint = UUID.randomUUID(); + final String roleSystem = "system"; + final String roleCode = "code"; + + OrganizationAffiliation a = new OrganizationAffiliation(); + a.setActive(true); + a.getOrganization().setReference("Organization/" + parentOrganization.toString()).setType("Organization"); + a.getParticipatingOrganization().setReference("Organization/" + memberOrganization.toString()) + .setType("Organization"); + a.addEndpoint().setReference("Endpoint/" + endpoint.toString()).setType("Endpoint"); + a.getCodeFirstRep().getCodingFirstRep().setSystem(roleSystem).setCode(roleCode); + + OrganizationAffiliation created = dao.create(a); + assertNotNull(created); + + try (Connection connection = dao.newReadWriteTransaction()) + { + boolean exists1 = dao + .existsNotDeletedByParentOrganizationMemberOrganizationRoleAndNotEndpointWithTransaction(connection, + parentOrganization, memberOrganization, roleSystem, roleCode, endpoint); + assertFalse(exists1); + + boolean exists2 = dao + .existsNotDeletedByParentOrganizationMemberOrganizationRoleAndNotEndpointWithTransaction(connection, + parentOrganization, memberOrganization, roleSystem, roleCode, UUID.randomUUID()); + assertTrue(exists2); + } + } } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java index 9ca4fa894..9ff2daac3 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationAffiliationIntegrationTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; +import java.util.UUID; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.Endpoint.EndpointStatus; @@ -47,7 +48,27 @@ public void testCreateAllowed() throws Exception } @Test - public void testCreateForbiddenResourceExists() throws Exception + public void testCreateForbiddenResourceExists1() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint e = endpointDao.create(createEndpoint("endpoint")); + assertNotNull(e); + assertTrue(e.hasIdElement()); + + OrganizationAffiliation aDic = getWebserviceClient().create(createOrganizationAffiliation(p, m, e, "DIC")); + assertNotNull(aDic); + assertTrue(aDic.hasIdElement()); + + expectForbidden(() -> getWebserviceClient().create(createOrganizationAffiliation(p, m, e, "DMS"))); + } + + @Test + public void testCreateForbiddenResourceExists2() throws Exception { Organization p = organizationDao.create(createParentOrganization()); assertNotNull(p); @@ -108,25 +129,84 @@ public void testUpdateForbiddenResourceExists() throws Exception Organization m = organizationDao.create(createMemberOrganization()); assertNotNull(m); assertTrue(m.hasIdElement()); - Endpoint eDic = endpointDao.create(createEndpoint("dic.endpoint")); - assertNotNull(eDic); - assertTrue(eDic.hasIdElement()); - Endpoint eDms = endpointDao.create(createEndpoint("dms.endpoint")); - assertNotNull(eDms); - assertTrue(eDms.hasIdElement()); + Endpoint eDicDms = endpointDao.create(createEndpoint("dic.endpoint")); + assertNotNull(eDicDms); + assertTrue(eDicDms.hasIdElement()); + Endpoint eTtp = endpointDao.create(createEndpoint("ttp.endpoint")); + assertNotNull(eTtp); + assertTrue(eTtp.hasIdElement()); - OrganizationAffiliation aDic = getWebserviceClient() - .create(createOrganizationAffiliation(p, m, eDic, "DIC", "TTP")); - assertNotNull(aDic); - assertTrue(aDic.hasIdElement()); + OrganizationAffiliation aDicDms = getWebserviceClient() + .create(createOrganizationAffiliation(p, m, eDicDms, "DIC", "DMS")); + assertNotNull(aDicDms); + assertTrue(aDicDms.hasIdElement()); - OrganizationAffiliation aDms = getWebserviceClient() - .create(createOrganizationAffiliation(p, m, eDms, "DMS", "TTP")); - assertNotNull(aDms); - assertTrue(aDms.hasIdElement()); + OrganizationAffiliation aTtp = getWebserviceClient().create(createOrganizationAffiliation(p, m, eTtp, "TTP")); + assertNotNull(aTtp); + assertTrue(aTtp.hasIdElement()); + + aTtp.getCodeFirstRep().getCodingFirstRep().setCode("DMS"); + expectForbidden(() -> getWebserviceClient().update(aTtp)); + } + + @Test + public void testUpdateForbiddenParentModified() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint e = endpointDao.create(createEndpoint("endpoint")); + assertNotNull(e); + + OrganizationAffiliation a = getWebserviceClient().create(createOrganizationAffiliation(p, m, e, "DIC")); + assertNotNull(a); + assertTrue(a.hasIdElement()); + + a.getOrganization().setReference("Organization/" + UUID.randomUUID().toString()); + expectForbidden(() -> getWebserviceClient().update(a)); + } + + @Test + public void testUpdateForbiddenMemberModified() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint e = endpointDao.create(createEndpoint("endpoint")); + assertNotNull(e); + + OrganizationAffiliation a = getWebserviceClient().create(createOrganizationAffiliation(p, m, e, "DIC")); + assertNotNull(a); + assertTrue(a.hasIdElement()); + + a.getParticipatingOrganization().setReference("Organization/" + UUID.randomUUID().toString()); + expectForbidden(() -> getWebserviceClient().update(a)); + } + + @Test + public void testUpdateForbiddenEndpointModified() throws Exception + { + Organization p = organizationDao.create(createParentOrganization()); + assertNotNull(p); + assertTrue(p.hasIdElement()); + Organization m = organizationDao.create(createMemberOrganization()); + assertNotNull(m); + assertTrue(m.hasIdElement()); + Endpoint e = endpointDao.create(createEndpoint("endpoint")); + assertNotNull(e); + + OrganizationAffiliation a = getWebserviceClient().create(createOrganizationAffiliation(p, m, e, "DIC")); + assertNotNull(a); + assertTrue(a.hasIdElement()); - aDic.getCodeFirstRep().getCodingFirstRep().setCode("DMS"); - expectForbidden(() -> getWebserviceClient().update(aDic)); + a.getEndpointFirstRep().setReference("Endpoint/" + UUID.randomUUID().toString()); + expectForbidden(() -> getWebserviceClient().update(a)); } private OrganizationAffiliation createOrganizationAffiliation(Organization parent, Organization member, From a1dad5388dd130ccf06f44e015e9bdba6e202b91 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 15 Aug 2023 16:57:45 +0200 Subject: [PATCH 19/39] removes test for version property of NamingSystem resources, fixes #72 --- .../dev/dsf/bpe/plugin/AbstractProcessPlugin.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/AbstractProcessPlugin.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/AbstractProcessPlugin.java index 0af7ec673..37116b2de 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/AbstractProcessPlugin.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/AbstractProcessPlugin.java @@ -1108,8 +1108,6 @@ private boolean isValid(Measure resource, String file) private boolean isValid(NamingSystem resource, String file) { boolean nameOk = resource.hasName(); - boolean versionDefined = resource.hasVersion(); - boolean versionOk = versionDefined && resource.getVersion().equals(getDefinitionResourceVersion()); if (!nameOk) { @@ -1117,18 +1115,7 @@ private boolean isValid(NamingSystem resource, String file) getDefinitionName(), getDefinitionVersion()); } - if (!versionDefined) - { - logger.warn("Ignoring FHIR resource {} from process plugin {}-{}: NamingSystem.version empty", file, - getDefinitionName(), getDefinitionVersion()); - } - else if (!versionOk) - { - logger.warn("Ignoring FHIR resource {} from process plugin {}-{}: NamingSystem.version not equal to {}", - file, getDefinitionName(), getDefinitionVersion(), getDefinitionResourceVersion()); - } - - return nameOk && versionOk; + return nameOk; } private boolean isValid(Questionnaire resource, String file) From 48c18dfe553fef4794795dad020f53cbbe98c19c Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 15 Aug 2023 23:33:15 +0200 Subject: [PATCH 20/39] adds ui dark mode UI theme (light/dark) is selected by OS preference or button. If UI theme is selected via button, preference is stored in local storage and overrides OS default. Remove _at and _since parameters from help UI for resource urls. Fixes non clickable links in bookmark ui. --- .../dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 10 +- .../src/main/resources/static/dsf.css | 54 ++++++--- .../src/main/resources/static/form.css | 5 +- .../src/main/resources/static/help.js | 2 +- .../src/main/resources/static/prettify.css | 4 +- .../src/main/resources/static/tabs.js | 107 ++++++++++-------- 6 files changed, 115 insertions(+), 67 deletions(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java index c61087df1..4e4f8a767 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java @@ -160,7 +160,7 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno + uriInfo.getPath() + "\n"); out.write("\n"); out.write("\n"); + + ");checkBookmarked();" + adaptFormInputsIfTask(resource) + "setUiTheme();\">\n"); out.write("
\n"); @@ -193,6 +193,14 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno Show Help + + Enable Light Mode + + + + Enable Dark Mode + + diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css index 5e9c75f1f..f6ac2c401 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css @@ -1,6 +1,29 @@ +[theme="light"] { + --color-prime: #326F95; + --color-background: #fff; + --color-alt-background: #eee; + --color-tab-button: #000; + --color-tab-button-background: #f1f1f1; + --color-tab-button-hover: #000; + --color-tab-button-background-hover: #ddd; + --color-tab-button-background-active: #ccc; +} + +[theme="dark"] { + --color-prime: #fff; + --color-background: #000; + --color-alt-background: #181818; + --color-tab-button: #fff; + --color-tab-button-background: #181818; + --color-tab-button-background-hover: #ddd; + --color-tab-button-hover: #000; + --color-tab-button-background-active: #888; +} + body { margin: 2em; font-family: sans-serif; + background-color: var(--color-background); } table#header { @@ -19,13 +42,13 @@ td#url { td#url h1 { font-family: monospace; - color: #326F95; + color: var(--color-prime); margin: 0 0 0 1em; word-break: break-word; } td#url h1 a:link, td#url h1 a:visited, td#url h1 a:active { - color: #326F95; + color: var(--color-prime); text-decoration: none; } @@ -39,7 +62,8 @@ td#url h1 a:hover { .tab button { padding: 0.2em 0.6em; - background-color: #f1f1f1; + background-color: var(--color-tab-button-background); + color: var(--color-tab-button); border: none; outline: none; cursor: pointer; @@ -47,11 +71,13 @@ td#url h1 a:hover { } .tab button:hover { - background-color: #ddd; + background-color: var(--color-tab-button-background-hover); + color: var(--color-tab-button-hover); } .tab button.active { - background-color: #ccc; + background-color: var(--color-tab-button-background-active); + color: var(--color-tab-button); } pre { @@ -94,7 +120,7 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { } .icon:hover>path { - fill: #326F95; + fill: var(--color-prime); } #help { @@ -103,14 +129,16 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { right: 1em; padding: 1em; border: 1px solid #ccc; - background: white; + color: var(--color-prime); + background: var(--color-background); padding-top: 2em; max-width: 83%; min-width: 12em; + z-index: 1; } #help>#help-title { - color: #326F95; + color: var(--color-prime); position: relative; top: -1.9em; font-family: sans-serif; @@ -148,31 +176,29 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { right: 1em; padding: 1em; border: 1px solid #ccc; - background: white; + background: var(--color-background); padding-top: 2em; max-width: 83%; min-width: 8em; + z-index: 1; } #bookmarks>#bookmarks-title { - color: #326F95; + color: var(--color-prime); position: relative; top: -1.9em; font-family: sans-serif; } #bookmarks a:link, #bookmarks a:visited, #bookmarks a:active { - color: #326F95; + color: var(--color-prime); text-decoration: none; vertical-align: super; font-family: monospace; } #bookmarks a:hover { - /* color: #326F95; */ text-decoration: underline; - /* vertical-align: super; - font-family: monospace; */ } #bookmarks>#bookmarks-list { diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css index 0ce7c2171..0ec8863c1 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css @@ -1,6 +1,5 @@ form { border: 1px solid #ccc; - background-color: #ffffff; padding: 20px 20px 10px 20px; font-family: Epilogue, sans-serif; } @@ -168,7 +167,7 @@ fieldset#form-fieldset { } .row-submit { - background-color: #ffffff; + background-color: transparent; padding: 0; } @@ -218,7 +217,7 @@ input.identifier-coding-code { } button.submit { - background-color: #29235c; + background-color: #326F95; color: white; padding: 12px 60px; border: none; diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js index e6a2eb622..22fc06a25 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/help.js @@ -36,7 +36,7 @@ function createAndShowHelp(httpRequest) { const searchParam = metadata.rest[0].resource.filter(r => r.type === resourceType[1])[0].searchParam; //Resource if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] === undefined && resourceType[4] === undefined) { - createHelp(searchParam); + createHelp(searchParam.filter(p => !['_at', '_since'].includes(p.name))); } //Resource/_history else if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] !== undefined && resourceType[4] === undefined) { diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css index b655dce96..421a37d53 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css @@ -63,10 +63,10 @@ li.L3, li.L5, li.L6, li.L7, -li.L8 { list-style-type: none } +li.L8 { list-style-type: none; color: var(--color-prime) } /* Alternate shading for lines */ li.L1, li.L3, li.L5, li.L7, -li.L9 { background: #eee } +li.L9 { background: var(--color-alt-background) } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/tabs.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/tabs.js index 7ff4ba911..57fa21c4b 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/tabs.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/tabs.js @@ -1,79 +1,94 @@ function openTab(lang) { - const tabcontent = document.getElementsByClassName("prettyprint"); - for (let i = 0; i < tabcontent.length; i++) { - tabcontent[i].style.display = "none"; - } + const tabcontent = document.getElementsByClassName("prettyprint") + for (let i = 0; i < tabcontent.length; i++) + tabcontent[i].style.display = "none" - const tablinks = document.getElementsByClassName("tablinks"); - for (let i = 0; i < tablinks.length; i++) { - tablinks[i].className = tablinks[i].className.replace(" active", ""); - } + const tablinks = document.getElementsByClassName("tablinks") + for (let i = 0; i < tablinks.length; i++) + tablinks[i].className = tablinks[i].className.replace(" active", "") - document.getElementById(lang).style.display = "block"; - document.getElementById(lang + "-button").className += " active"; + document.getElementById(lang).style.display = "block" + document.getElementById(lang + "-button").className += " active" if (lang != "html" && localStorage != null) - localStorage.setItem('lang', lang); + localStorage.setItem('lang', lang) if (lang == "html") - lang = localStorage != null && localStorage.getItem("lang") != null ? localStorage.getItem("lang") : "xml"; + lang = localStorage != null && localStorage.getItem("lang") != null ? localStorage.getItem("lang") : "xml" - setDownloadLink(lang); + setDownloadLink(lang) } function openInitialTab(htmlEnabled) { if (htmlEnabled) - openTab("html"); + openTab("html") else { - const lang = localStorage != null && localStorage.getItem("lang") != null ? localStorage.getItem("lang") : "xml"; + const lang = localStorage != null && localStorage.getItem("lang") != null ? localStorage.getItem("lang") : "xml" if (lang == "xml" || lang == "json") - openTab(lang); + openTab(lang); } } function setDownloadLink(lang) { - const searchParams = new URLSearchParams(document.location.search); - searchParams.set('_format', lang); - searchParams.set('_pretty', 'true'); + const searchParams = new URLSearchParams(document.location.search) + searchParams.set('_format', lang) + searchParams.set('_pretty', 'true') - const downloadLink = document.getElementById('download-link'); - downloadLink.href = '?' + searchParams.toString(); - downloadLink.download = getDownloadFileName(lang); - downloadLink.title = 'Download as ' + lang.toUpperCase(); + const downloadLink = document.getElementById('download-link') + downloadLink.href = '?' + searchParams.toString() + downloadLink.download = getDownloadFileName(lang) + downloadLink.title = 'Download as ' + lang.toUpperCase() } function getDownloadFileName(lang) { - const resourceType = getResourceTypeForCurrentUrl(); + const resourceType = getResourceTypeForCurrentUrl() /* /, /metadata, /_history */ if (resourceType == null) { - if (window.location.pathname.endsWith('/metadata')) { - return "metadata." + lang; - } else if (window.location.pathname.endsWith('/_history')) { - return "history." + lang; - } else { - return "root." + lang; - } + if (window.location.pathname.endsWith('/metadata')) + return "metadata." + lang + else if (window.location.pathname.endsWith('/_history')) + return "history." + lang + else + return "root." + lang } else { //Resource - if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] === undefined && resourceType[4] === undefined) { - return resourceType[1] + '_Search.' + lang; - } + if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] === undefined && resourceType[4] === undefined) + return resourceType[1] + '_Search.' + lang //Resource/_history - else if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] !== undefined && resourceType[4] === undefined) { - return resourceType[1] + '_History.' + lang; - } + else if (resourceType[1] !== undefined && resourceType[2] === undefined && resourceType[3] !== undefined && resourceType[4] === undefined) + return resourceType[1] + '_History.' + lang //Resource/id - else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] === undefined && resourceType[4] === undefined) { - return resourceType[1] + '_' + resourceType[2].replace('/', '') + '.' + lang; - } + else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] === undefined && resourceType[4] === undefined) + return resourceType[1] + '_' + resourceType[2].replace('/', '') + '.' + lang //Resource/id/_history - else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] !== undefined && resourceType[4] === undefined) { - return resourceType[1] + '_' + resourceType[2].replace('/', '') + '_History.' + lang; - } + else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] !== undefined && resourceType[4] === undefined) + return resourceType[1] + '_' + resourceType[2].replace('/', '') + '_History.' + lang //Resource/id/_history/version - else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] !== undefined && resourceType[4] !== undefined) { - return resourceType[1] + '_' + resourceType[2].replace('/', '') + '_v' + resourceType[4].replace('/', '') + '.' + lang; - } + else if (resourceType[1] !== undefined && resourceType[2] !== undefined && resourceType[3] !== undefined && resourceType[4] !== undefined) + return resourceType[1] + '_' + resourceType[2].replace('/', '') + '_v' + resourceType[4].replace('/', '') + '.' + lang + } +} + +function setUiTheme(theme = getUiTheme()) { + if (theme === 'dark') { + document.getElementById('light-mode').style.display = 'block' + document.getElementById('dark-mode').style.display = 'none' } + else { + document.getElementById('light-mode').style.display = 'none' + document.getElementById('dark-mode').style.display = 'block' + } + + document.querySelector("html").setAttribute("theme", theme); + localStorage.setItem("theme", theme); +} + +function getUiTheme() { + if (localStorage !== null && localStorage.getItem("theme") !== null) + return localStorage.getItem("theme") + else if (window.matchMedia("(prefers-color-scheme: dark)").matches) + return "dark" + else + return "light" } \ No newline at end of file From 030bd69a60a38814adece94ada825833e7f0407a Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Thu, 17 Aug 2023 12:28:39 +0200 Subject: [PATCH 21/39] add check for existing input row --- .../src/main/resources/static/form.js | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js index c629d1004..bb8831d44 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.js @@ -487,29 +487,34 @@ function getValueOfDifferential(differentials, path, property) { function modifyInputRow(definition, indices) { const row = document.querySelector("[name='" + definition.typeCode + "-input-row']") - const index = parseInt(row.getAttribute("index")) - indices.set(getDefinitionId(definition), index) + if (row) { + const rowIndex = row.getAttribute("index") + if (rowIndex) { + const index = parseInt(rowIndex) + indices.set(getDefinitionId(definition), index) + } - const label = row.querySelector("label") - if (label) { - const cardinalities = htmlToElement('', " [" + definition.min + ".." + definition.max + "]") - label.appendChild(cardinalities) + const label = row.querySelector("label") + if (label) { + const cardinalities = htmlToElement('', " [" + definition.min + ".." + definition.max + "]") + label.appendChild(cardinalities) - if (definition.max !== "1") { - const plusIcon = htmlToElement('') - const plusIconSvg = htmlToElement('Add additional input') + if (definition.max !== "1") { + const plusIcon = htmlToElement('') + const plusIconSvg = htmlToElement('Add additional input') - plusIconSvg.addEventListener("click", () => { - appendInputRowAfter(row, definition, indices) - }) + plusIconSvg.addEventListener("click", () => { + appendInputRowAfter(row, definition, indices) + }) - plusIcon.appendChild(plusIconSvg) - label.appendChild(plusIcon) + plusIcon.appendChild(plusIconSvg) + label.appendChild(plusIcon) + } } - } - if (definition.min < 1 || definition.min === undefined) - row.setAttribute("optional", "") + if (definition.min < 1 || definition.min === undefined) + row.setAttribute("optional", "") + } } function appendInputRowAfter(inputRow, definition, indices) { From 0c3e5ce5fb023302bf581c410509fefb718305eb Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Thu, 17 Aug 2023 17:43:10 +0200 Subject: [PATCH 22/39] add dark mode for input ui --- .../QuestionnaireResponseHtmlGenerator.java | 2 +- .../dsf/fhir/adapter/TaskHtmlGenerator.java | 6 +- .../src/main/resources/static/dsf.css | 38 ++++ .../src/main/resources/static/form.css | 177 +++++++++++------- 4 files changed, 147 insertions(+), 76 deletions(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java index c4de31090..446986f29 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java @@ -69,7 +69,7 @@ public void writeHtml(String basePath, QuestionnaireResponse questionnaireRespon out.write("
\n"); out.write("
\n"); - out.write("
\n"); + out.write("
\n"); Map elemenIndexMap = new HashMap<>(); for (QuestionnaireResponse.QuestionnaireResponseItemComponent item : questionnaireResponse.getItem()) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java index 3654ab1a2..bc904a9c1 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java @@ -76,17 +76,17 @@ public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws out.write("\n"); out.write("\n"); - out.write("
\n"); + out.write("
\n"); out.write("
\n"); out.write("\n"); - out.write("\n"); out.write("
\n"); out.write("
\n"); out.write("\n"); - out.write("\n"); out.write("
\n"); diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css index f6ac2c401..71c71281f 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css @@ -7,6 +7,25 @@ --color-tab-button-hover: #000; --color-tab-button-background-hover: #ddd; --color-tab-button-background-active: #ccc; + --color-row-background: #f2f2f2; + --color-row-background-deep: #e2e2e2; + --color-input-text: #000; + --color-input-background: #fff; + --color-input-border: #ccc; + --color-input-radio-border: #000; + --color-input-placeholder: #aaa; + --color-info-background-draft-requested: #deecf9; + --color-info-text-draft-requested: #326F95; + --color-info-background-progress: #fff7c7; + --color-info-text-progress: #a25b36; + --color-info-background-completed: #e4fde1; + --color-info-text-completed: #14452f; + --color-info-background-stopped-failed: #ffe1e1; + --color-info-text-stopped-failed: #761137; + --color-error-background: #ffe1e1; + --color-error-text: #761137; + --color-disabled-background: #f2f2f2; + --color-disabled-text: #545454; } [theme="dark"] { @@ -18,6 +37,25 @@ --color-tab-button-background-hover: #ddd; --color-tab-button-hover: #000; --color-tab-button-background-active: #888; + --color-row-background: #333; + --color-row-background-deep: #444; + --color-input-text: #fff; + --color-input-background: #181818; + --color-input-border: #666; + --color-input-radio-border: #fff; + --color-input-placeholder: #666; + --color-info-background-draft-requested: #326F95; + --color-info-text-draft-requested: #deecf9; + --color-info-background-progress: #a25b36; + --color-info-text-progress: #fff7c7; + --color-info-background-completed: #14452f; + --color-info-text-completed: #e4fde1; + --color-info-background-stopped-failed: #5e0101; + --color-info-text-stopped-failed: #ffe1e1; + --color-error-background: #5e0101; + --color-error-text: #ffe1e1; + --color-disabled-background: #333; + --color-disabled-text: #aaa; } body { diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css index 0ec8863c1..aba141675 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css @@ -16,14 +16,14 @@ fieldset#form-fieldset { border-radius: 5px; padding: 0 1em 1em 1em; margin-bottom: 0.7em; - background-color: #f2f2f2; + background-color: var(--color-row-background); } .row-extension-0 { margin-top: 12px; padding: 0 0 12px 12px; border-radius: 5px; - background-color: #e2e2e2; + background-color: var(--color-row-background-deep); } .row-label-extension-no-value { @@ -40,10 +40,11 @@ fieldset#form-fieldset { .p-display { margin: 0; + color: var(--color-input-text) } .error { - background-color: #ffa590; + background-color: var(--color-error-background); } .error-list-not-visible { @@ -53,7 +54,7 @@ fieldset#form-fieldset { .error-list-visible { display: block; font-size: small; - color: #470003; + color: var(--color-error-text); padding-left: 20px; margin-bottom: 0; } @@ -64,23 +65,23 @@ fieldset#form-fieldset { } .info-color-draft-requested { - background-color: #deecf9; - color: #12395d; + background-color: var(--color-info-background-draft-requested); + color: var(--color-info-text-draft-requested); } .info-color-progress { - background-color: #edd273; - color: #864401; + background-color: var(--color-info-background-progress); + color: var(--color-info-text-progress); } .info-color-completed { - background-color: #a3c585; - color: #4b6043; + background-color: var(--color-info-background-completed); + color: var(--color-info-text-completed); } .info-color-stopped-failed { - background-color: #ffb3b3; - color: #761137; + background-color: var(--color-info-background-stopped-failed); + color: var(--color-info-text-stopped-failed); } .info-link { @@ -93,19 +94,19 @@ fieldset#form-fieldset { } .info-link-draft-requested { - color: #12395d; + color: var(--color-info-text-draft-requested); } .info-link-progress { - color: #864401; + color: var(--color-info-text-progress); } .info-link-completed { - color: #4b6043; + color: var(--color-info-text-completed); } .info-link-stopped-failed { - color: #761137; + color: var(--color-info-text-stopped-failed); } .info-link:active { @@ -114,19 +115,19 @@ fieldset#form-fieldset { } .info-link-draft-requested:active { - color: #12395d; + color: var(--color-info-text-draft-requested); } .info-link-progress:active { - color: #864401; + color: var(--color-info-text-progress); } .info-link-completed:active { - color: #4b6043; + color: var(--color-info-text-completed); } .info-link-stopped-failed:active { - color: #761137; + color: var(--color-info-text-stopped-failed); } .info-icon { @@ -137,19 +138,19 @@ fieldset#form-fieldset { } .info-icon > path.info-path-draft-requested { - fill: #12395d; + fill: var(--color-info-text-draft-requested); } .info-icon > path.info-path-progress { - fill: #864401; + fill: var(--color-info-text-progress); } .info-icon > path.info-path-completed { - fill: #4b6043; + fill: var(--color-info-text-completed); } .info-icon > path.info-path-stopped-failed { - fill: #761137; + fill: var(--color-info-text-stopped-failed); } .info-list { @@ -180,17 +181,55 @@ label { label.radio { padding: 5px 12px 5px 0; font-weight: 100; + color: var(--color-input-text) +} + +input { + background-color: var(--color-input-background); + color: var(--color-input-text); } input[type=radio] { margin-right: 7px; } +input[type="radio"] { + border: 0.1em solid var(--color-input-radio-border); + border-radius: 9px; + height: 18px; + width: 18px; + margin-bottom: -0.2em; + -webkit-appearance: none; +} + +input[type="radio"][disabled], fieldset#form-fieldset[disabled] input[type="radio"] { + border: 1px solid var(--color-disabled-text); +} + +input[type="radio"]:checked { + border: 0.1em solid var(--color-prime); + background-color: var(--color-prime); + box-shadow: inset 0 0 0 0.2em var(--color-background); +} + +input[type="radio"][disabled]:checked, fieldset#form-fieldset[disabled] input[type="radio"]:checked { + border: 0.1em solid var(--color-disabled-text); + background-color: var(--color-disabled-text); + box-shadow: inset 0 0 0 0.2em var(--color-disabled-background); +} + +input[disabled], fieldset#form-fieldset[disabled] input { + cursor: default; + background-color: var(--color-disabled-background); + color: var(--color-disabled-text); + border-color: rgba(118, 118, 118, 0.5); +} + input[type=text], input[type=url], select, textarea { width: 100%; min-width: 200px; padding: 12px; - border: 1px solid #ccc; + border: 1px solid var(--color-input-border); border-radius: 4px; box-sizing: border-box; resize: vertical; @@ -201,24 +240,59 @@ input[type=date], input[type=time], input[type=datetime-local], input[type=numbe width: 100%; min-width: 200px; padding: 12px; - border: 1px solid #ccc; + border: 1px solid var(--color-input-border); border-radius: 4px; box-sizing: border-box; resize: none; display: block; } +input[type=number] { + -webkit-appearance: textfield; + -moz-appearance: textfield; + -o-appearance: textfield; + appearance: textfield; +} + +.input-group { + display: flex; + flex-direction: row; +} + +.input-group-svg { + cursor: pointer; + align-self: center; + margin-left: 0.46em; + rotate: 180deg; +} + +.input-group-svg > path { + fill: #aaa; +} + +.input-group-svg:hover > path { + fill: #326F95; +} + input.identifier-coding-code { margin-top: 6px; } +.input-output-header { + font-family: monospace; + font-size: 1.75em; + color: #326F95; + padding-left: 5px; + margin-bottom: 12px; +} + .invisible { display: none; } button.submit { background-color: #326F95; - color: white; + color: #fff; padding: 12px 60px; border: none; border-radius: 4px; @@ -226,21 +300,6 @@ button.submit { float: left; } -button.submit:disabled, -button.submit[disabled] { - background-color: #ccc; - color: #555; - cursor: not-allowed; -} - -.input-output-header { - font-family: monospace; - font-size: 1.75em; - color: #326F95; - padding-left: 5px; - margin-bottom: 12px; -} - .spinner-enabled { display: block; } @@ -289,10 +348,11 @@ button.submit[disabled] { .cardinalities { font-weight: normal; font-size: x-small; - color: gray; + color: #aaa; } .row-label { + color: var(--color-input-text); padding: 12px 0 6px 6px; font-weight: bold; font-size: small; @@ -314,37 +374,10 @@ button.submit[disabled] { fill: #aaa; } -.input-group { - display: flex; - flex-direction: row; -} - -.input-group-svg { - cursor: pointer; - align-self: center; - margin-left: 0.46em; - rotate: 180deg; -} - -.input-group-svg > path { - fill: #aaa; -} - -.input-group-svg:hover > path { - fill: #326F95; -} - -input[type=number] { - -webkit-appearance: textfield; - -moz-appearance: textfield; - -o-appearance: textfield; - appearance: textfield; -} - ::placeholder { - color: #ddd; + color: var(--color-input-placeholder); } ::-ms-input-placeholder { - color: #ddd; + color: var(--color-input-placeholder); } \ No newline at end of file From 6040e69210cc943b5c0cefbdfd51897fd1adbc3c Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Thu, 17 Aug 2023 20:56:46 +0200 Subject: [PATCH 23/39] adds missing super.doConfigure(...) call The call of the super method is needed to initialize the valueAndType field. --- .../java/dev/dsf/fhir/search/parameters/EndpointStatus.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java index 75674f82e..3c1899079 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/parameters/EndpointStatus.java @@ -35,6 +35,8 @@ public EndpointStatus() protected void doConfigure(List errors, String queryParameterName, String queryParameterValue) { + super.doConfigure(errors, queryParameterName, queryParameterValue); + if (valueAndType != null && valueAndType.type == TokenSearchType.CODE) status = toStatus(errors, valueAndType.codeValue, queryParameterValue); } From bc48348079df113c9848e79278600a32ecff2e73 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Thu, 17 Aug 2023 20:59:35 +0200 Subject: [PATCH 24/39] fixes Organization "status" search parameter --- .../java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java index c6171ef3d..32174025b 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/v1/service/OrganizationProviderImpl.java @@ -73,7 +73,7 @@ public Optional getOrganization(Identifier organizationIdentifier) String organizationIdSp = toSearchParameter(organizationIdentifier); Bundle resultBundle = clientProvider.getLocalWebserviceClient().searchWithStrictHandling(Organization.class, - Map.of("status", Collections.singletonList("active"), "identifier", + Map.of("active", Collections.singletonList("true"), "identifier", Collections.singletonList(organizationIdSp))); if (resultBundle == null || resultBundle.getEntry() == null || resultBundle.getTotal() != 1 From 966e7446eaa557ccb78564b77479f6e7aeafd590 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 01:57:27 +0200 Subject: [PATCH 25/39] adding jersey-common dependency for query parameter parsing --- dsf-fhir/dsf-fhir-rest-adapter/pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml index bf9bd91a6..363bcb437 100755 --- a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml +++ b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml @@ -14,7 +14,11 @@ dev.dsf dsf-common-auth - + + org.glassfish.jersey.core + jersey-common + + ca.uhn.hapi.fhir hapi-fhir-structures-r4 From ebfbd4f62859ed174dcb59e45bce86c6aa288c2d Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 01:58:32 +0200 Subject: [PATCH 26/39] fixed not working sort parameters (copy/paste error) --- .../src/main/java/dev/dsf/fhir/search/SearchQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java index fbd7a2b91..8373cd71d 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java @@ -293,7 +293,7 @@ private String createSortSql(List sortParameterValues) { if (value != null && !value.isBlank()) { - SearchQueryParameterFactory sortParameterFactory = searchParameterFactoriesByParameterName + SearchQueryParameterFactory sortParameterFactory = searchParameterFactoriesBySortParameterName .get(value); if (sortParameterFactory != null) { From e0efdc2e9b8b0212b789eed33214319245c40ef1 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 01:59:29 +0200 Subject: [PATCH 27/39] adding DSF_ADMIN role to WebbrowserTestUser of IDE test setup --- .../src/main/java/dev/dsf/tools/generator/ConfigGenerator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/ConfigGenerator.java b/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/ConfigGenerator.java index ea0c8ac3e..1d9df72c0 100755 --- a/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/ConfigGenerator.java +++ b/dsf-tools/dsf-tools-test-data-generator/src/main/java/dev/dsf/tools/generator/ConfigGenerator.java @@ -90,6 +90,8 @@ public void modifyJavaTestFhirConfigProperties(Map cli - SEARCH - HISTORY - PERMANENT_DELETE + practitioner-role: + - http://dsf.dev/fhir/CodeSystem/practitioner-role|DSF_ADMIN """, webbrowserTestUser.getCertificateSha512ThumbprintHex())); writeProperties(Paths.get("config/java-test-fhir-config.properties"), javaTestFhirConfigProperties); From 8776fbad76e0da9791872ae0301b117ed1b53e23 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 02:05:51 +0200 Subject: [PATCH 28/39] new Task / QuestionnaireResponse search UIs, reworked entity UIs --- .../dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 65 ++- .../dev/dsf/fhir/adapter/HtmlGenerator.java | 20 +- .../QuestionnaireResponseHtmlGenerator.java | 98 ++-- .../adapter/SearchBundleHtmlGenerator.java | 242 +++++++++ .../dsf/fhir/adapter/TaskHtmlGenerator.java | 121 ++--- .../dsf/fhir/spring/config/AdapterConfig.java | 4 +- .../src/main/resources/static/bookmarks.js | 4 +- .../src/main/resources/static/dsf.css | 211 ++++++-- .../src/main/resources/static/form.css | 464 ++++++++++-------- .../src/main/resources/static/prettify.css | 6 +- 10 files changed, 806 insertions(+), 429 deletions(-) create mode 100644 dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java index 4e4f8a767..c520498a6 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java @@ -13,10 +13,10 @@ import java.security.Principal; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,7 +30,6 @@ import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import org.hl7.fhir.r4.model.BaseResource; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Resource; @@ -52,7 +51,7 @@ @Provider @Produces(MediaType.TEXT_HTML) -public class HtmlFhirAdapter extends AbstractAdapter implements MessageBodyWriter +public class HtmlFhirAdapter extends AbstractAdapter implements MessageBodyWriter { private static final String RESOURCE_NAMES = "Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary" + "|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse" @@ -89,7 +88,7 @@ public class HtmlFhirAdapter extends AbstractAdapter implements MessageBodyWrite private final FhirContext fhirContext; private final Supplier serverBaseProvider; - private final Map, HtmlGenerator> htmlGeneratorsByType; + private final Map, List>> htmlGeneratorsByType; @Context private volatile UriInfo uriInfo; @@ -105,7 +104,7 @@ public HtmlFhirAdapter(FhirContext fhirContext, Supplier serverBaseProvi if (htmlGenerators != null) htmlGeneratorsByType = htmlGenerators.stream() - .collect(Collectors.toMap(HtmlGenerator::getResourceType, Function.identity())); + .collect(Collectors.groupingBy(HtmlGenerator::getResourceType)); else htmlGeneratorsByType = Collections.emptyMap(); } @@ -126,17 +125,17 @@ protected IParser getParser(MediaType mediaType, Supplier parser) @Override public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return type != null && BaseResource.class.isAssignableFrom(type); + return type != null && Resource.class.isAssignableFrom(type); } @Override - public void writeTo(BaseResource resource, Class type, Type genericType, Annotation[] annotations, + public void writeTo(Resource resource, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { final String basePath = uriInfo.getBaseUri().getRawPath(); - - OutputStreamWriter out = new OutputStreamWriter(entityStream); + final boolean htmlEnabled = isHtmlEnabled(type, basePath, resource); + final OutputStreamWriter out = new OutputStreamWriter(entityStream); out.write(""" @@ -159,8 +158,8 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno out.write("DSF" + (uriInfo.getPath() == null || uriInfo.getPath().isEmpty() ? "" : ": ") + uriInfo.getPath() + "\n"); out.write("\n"); - out.write("\n"); + out.write("\n"); out.write("
\n"); @@ -252,7 +251,7 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno
"""); - if (isHtmlEnabled(type)) + if (htmlEnabled) out.write("\n"); out.write(""" @@ -264,16 +263,16 @@ public void writeTo(BaseResource resource, Class type, Type genericType, Anno writeXml(mediaType, basePath, resource, out); writeJson(mediaType, basePath, resource, out); - if (isHtmlEnabled(type)) + if (htmlEnabled) writeHtml(type, basePath, resource, out); out.write(""); out.flush(); } - private String getUrlHeading(BaseResource resource) throws MalformedURLException + private String getUrlHeading(Resource resource) throws MalformedURLException { - URI uri = getResourceUrl(resource).map(this::toURI).orElse(uriInfo.getRequestUri()); + URI uri = getResourceUri(resource); String[] pathSegments = uri.getPath().split("/"); String u = serverBaseProvider.get(); @@ -302,6 +301,12 @@ else if (uriInfo.getQueryParameters().containsKey("_summary")) return heading.toString(); } + + private URI getResourceUri(Resource resource) throws MalformedURLException + { + return getResourceUrlString(resource).map(this::toURI).orElse(uriInfo.getRequestUri()); + } + private URI toURI(String str) { try @@ -314,7 +319,7 @@ private URI toURI(String str) } } - private Optional getResourceUrl(BaseResource resource) throws MalformedURLException + private Optional getResourceUrlString(Resource resource) throws MalformedURLException { if (resource instanceof Resource && resource.getIdElement().hasIdPart()) { @@ -333,7 +338,7 @@ else if (resource instanceof Bundle && !resource.getIdElement().hasIdPart()) return Optional.empty(); } - private void writeXml(MediaType mediaType, String basePath, BaseResource resource, OutputStreamWriter out) + private void writeXml(MediaType mediaType, String basePath, Resource resource, OutputStreamWriter out) throws IOException { IParser parser = getParser(mediaType, fhirContext::newXmlParser); @@ -397,7 +402,7 @@ private String simplifyXml(String xml) } } - private void writeJson(MediaType mediaType, String basePath, BaseResource resource, OutputStreamWriter out) + private void writeJson(MediaType mediaType, String basePath, Resource resource, OutputStreamWriter out) throws IOException { IParser parser = getParser(mediaType, fhirContext::newJsonParser); @@ -427,23 +432,33 @@ private void writeJson(MediaType mediaType, String basePath, BaseResource resour } @SuppressWarnings("unchecked") - private void writeHtml(Class resourceType, String basePath, BaseResource resource, OutputStreamWriter out) + private void writeHtml(Class resourceType, String basePath, Resource resource, OutputStreamWriter out) throws IOException { out.write("
\n"); - HtmlGenerator generator = (HtmlGenerator) htmlGeneratorsByType.get(resourceType); - generator.writeHtml(basePath, resource, out); + URI resourceUri = getResourceUri(resource); + + HtmlGenerator generator = (HtmlGenerator) htmlGeneratorsByType.get(resourceType).stream() + .filter(g -> g.isResourceSupported(basePath, resourceUri, resource)).findFirst().get(); + generator.writeHtml(basePath, resourceUri, resource, out); out.write("
\n"); } - private boolean isHtmlEnabled(Class resourceType) + private boolean isHtmlEnabled(Class resourceType, String basePath, Resource resource) + throws MalformedURLException { - return htmlGeneratorsByType.containsKey(resourceType); + URI resourceUri = getResourceUri(resource); + + if (htmlGeneratorsByType.containsKey(resourceType)) + return uriInfo != null && htmlGeneratorsByType.get(resourceType).stream() + .anyMatch(g -> g.isResourceSupported(basePath, resourceUri, resource)); + else + return false; } - private String adaptFormInputsIfTask(BaseResource resource) + private String adaptFormInputsIfTask(Resource resource) { if (resource instanceof Task task) return Task.TaskStatus.DRAFT.equals(task.getStatus()) ? "adaptTaskFormInputs();" : ""; @@ -451,7 +466,7 @@ private String adaptFormInputsIfTask(BaseResource resource) return ""; } - private Optional getResourceName(BaseResource resource, String uuid) + private Optional getResourceName(Resource resource, String uuid) { if (resource instanceof Bundle) { diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java index 65e34aa7b..f27b2ca44 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java @@ -2,10 +2,11 @@ import java.io.IOException; import java.io.OutputStreamWriter; +import java.net.URI; -import org.hl7.fhir.r4.model.BaseResource; +import org.hl7.fhir.r4.model.Resource; -public interface HtmlGenerator +public interface HtmlGenerator { /** * @return the resource type supported by this generator @@ -15,11 +16,24 @@ public interface HtmlGenerator /** * @param basePath * the applications base base, e.g. /fhir/ + * @param resourceUri + * not null * @param resource * the resource, not null * @param out * the outputStreamWriter, not null * @throws IOException */ - void writeHtml(String basePath, R resource, OutputStreamWriter out) throws IOException; + void writeHtml(String basePath, URI resourceUri, R resource, OutputStreamWriter out) throws IOException; + + /** + * @param basePath + * the applications base base, e.g. /fhir/ + * @param resourceUri + * not null + * @param resource + * not null + * @return true if this HtmlGenerator supports the given resource for the given uri + */ + boolean isResourceSupported(String basePath, URI resourceUri, Resource resource); } diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java index 446986f29..4e819336d 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java @@ -2,11 +2,13 @@ import java.io.IOException; import java.io.OutputStreamWriter; +import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.hl7.fhir.r4.model.QuestionnaireResponse; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; public class QuestionnaireResponseHtmlGenerator extends InputHtmlGenerator @@ -22,49 +24,52 @@ public Class getResourceType() } @Override - public void writeHtml(String basePath, QuestionnaireResponse questionnaireResponse, OutputStreamWriter out) - throws IOException + public boolean isResourceSupported(String basePath, URI resourceUri, Resource resource) { - boolean completed = QuestionnaireResponse.QuestionnaireResponseStatus.COMPLETED + return resource != null && resource instanceof QuestionnaireResponse; + } + + @Override + public void writeHtml(String basePath, URI resourceUri, QuestionnaireResponse questionnaireResponse, + OutputStreamWriter out) throws IOException + { + final boolean completed = QuestionnaireResponse.QuestionnaireResponseStatus.COMPLETED .equals(questionnaireResponse.getStatus()); out.write("
"); - out.write("
\n"); - out.write("
\n"); + out.write("
\n"); out.write("
"); out.write(""); out.write("Info\n"); - out.write(""); + out.write( + ""); out.write(""); out.write("
\n"); - - String urlVersion = questionnaireResponse.getQuestionnaire(); - String[] urlVersionSplit = urlVersion.split("\\|"); - String href = basePath + "Questionnaire?url=" + urlVersionSplit[0] + "&version=" + urlVersionSplit[1]; - out.write("
"); - out.write("

\n"); - out.write("This QuestionnaireResponse answers the Questionnaire:
" - + urlVersion + ""); - out.write("

\n"); out.write("
\n"); out.write("
\n"); out.write("
\n"); @@ -89,39 +94,6 @@ public void writeHtml(String basePath, QuestionnaireResponse questionnaireRespon out.write("\n"); } - private String getColorClass(QuestionnaireResponse.QuestionnaireResponseStatus status, String elementType) - { - switch (status) - { - case INPROGRESS: - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-progress"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-progress"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-progress"; - case COMPLETED: - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-completed"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-completed"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-completed"; - case STOPPED: - case ENTEREDINERROR: - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-stopped-failed"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-stopped-failed"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-stopped-failed"; - case AMENDED: - case NULL: - default: - return ""; - } - } - private String getProcessInstanceId(QuestionnaireResponse questionnaireResponse) { return questionnaireResponse.getItem().stream() diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java new file mode 100644 index 000000000..56ee273f8 --- /dev/null +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java @@ -0,0 +1,242 @@ +package dev.dsf.fhir.adapter; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URI; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.glassfish.jersey.uri.UriComponent; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent; +import org.hl7.fhir.r4.model.Bundle.SearchEntryMode; +import org.hl7.fhir.r4.model.QuestionnaireResponse; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Task; +import org.hl7.fhir.r4.model.Task.ParameterComponent; + +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.PathSegment; + +public class SearchBundleHtmlGenerator extends InputHtmlGenerator implements HtmlGenerator +{ + private static final SimpleDateFormat DATE_TIME_DISPLAY_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); + private static final String INSTANTIATES_CANONICAL_PATTERN_STRING = "(?http[s]{0,1}://(?(?:(?:[a-zA-Z0-9]{1,63}|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])\\.)+(?:[a-zA-Z0-9]{1,63}))" + + "/bpe/Process/(?[a-zA-Z0-9-]+))\\|(?\\d+\\.\\d+)$"; + private static final Pattern INSTANTIATES_CANONICAL_PATTERN = Pattern + .compile(INSTANTIATES_CANONICAL_PATTERN_STRING); + private static final String CODE_SYSTEM_BPMN_MESSAGE = "http://dsf.dev/fhir/CodeSystem/bpmn-message"; + private static final String CODE_SYSTEM_BPMN_MESSAGE_MESSAGE_NAME = "message-name"; + private static final String CODE_SYSTEM_BPMN_MESSAGE_BUSINESS_KEY = "business-key"; + + private final int defaultPageCount; + + public SearchBundleHtmlGenerator(int defaultPageCount) + { + this.defaultPageCount = defaultPageCount; + } + + @Override + public Class getResourceType() + { + return Bundle.class; + } + + @Override + public boolean isResourceSupported(String basePath, URI resourceUri, Resource resource) + { + List segments = UriComponent.decodePath(resourceUri, false); + + return resource != null && resource instanceof Bundle && segments.size() == 2 + && basePath.equals("/" + segments.get(0).getPath() + "/") && switch (segments.get(1).getPath()) + { + case "Task", "QuestionnaireResponse" -> true; + default -> false; + }; + } + + @Override + public void writeHtml(String basePath, URI resourceUri, Bundle resource, OutputStreamWriter out) throws IOException + { + out.write("
"); + out.write("
"); + out.write("
"); + + Optional first = resource.getLink().stream().filter(l -> "first".equals(l.getRelation())).findFirst() + .map(BundleLinkComponent::getUrl); + + if (first.isPresent()) + out.write(""); + out.write(""); + if (first.isPresent()) + out.write(""); + + Optional previous = resource.getLink().stream().filter(l -> "previous".equals(l.getRelation())) + .findFirst().map(BundleLinkComponent::getUrl); + if (previous.isPresent()) + out.write(""); + out.write(""); + if (previous.isPresent()) + out.write(""); + + out.write(""); + int page = getPage(resourceUri); + int count = getCount(resourceUri); + int max = (int) Math.ceil((double) resource.getTotal() / count); + int firstResource = ((page - 1) * count) + 1; + int lastResource = ((page - 1) * count) + resource.getEntry().size(); + if (page > 0 && page <= max) + out.write("Resources " + firstResource + " - " + lastResource + " / " + + resource.getTotal() + "Page " + page + " / " + max + ""); + out.write(""); + + Optional next = resource.getLink().stream().filter(l -> "next".equals(l.getRelation())).findFirst() + .map(BundleLinkComponent::getUrl); + if (next.isPresent()) + out.write(""); + out.write(""); + if (next.isPresent()) + out.write(""); + + Optional last = resource.getLink().stream().filter(l -> "last".equals(l.getRelation())).findFirst() + .map(BundleLinkComponent::getUrl); + if (last.isPresent()) + out.write(""); + out.write(""); + if (last.isPresent()) + out.write(""); + + out.write("
"); + + out.write(""); + out.write(getHeader(resourceUri)); + out.write(resource.getEntry().stream() + .filter(e -> e.hasResource() && e.hasSearch() && e.getSearch().hasMode() + && SearchEntryMode.MATCH.equals(e.getSearch().getMode())) + .map(BundleEntryComponent::getResource) + .map(r -> "" + getRow(r) + "\n") + .collect(Collectors.joining())); + out.write("
"); + + long includeResources = resource.getEntry().stream().filter( + e -> e.hasResource() && e.hasSearch() && SearchEntryMode.INCLUDE.equals(e.getSearch().getMode())) + .count(); + if (includeResources > 0) + out.write("

" + includeResources + " include " + + (includeResources == 1 ? "resource" : "resources") + " hidden.

"); + + out.write("
"); + } + + private int getPage(URI uri) + { + MultivaluedMap params = UriComponent.decodeQuery(uri, false); + String p = params.getFirst("_page"); + if (p != null && !p.isBlank() && p.matches("-{0,1}[0-9]+")) + return Integer.parseInt(p); + else + return 1; + } + + private int getCount(URI uri) + { + MultivaluedMap params = UriComponent.decodeQuery(uri, false); + String p = params.getFirst("_count"); + if (p != null && !p.isBlank() && p.matches("-{0,1}[0-9]+")) + return Integer.parseInt(p); + else + return defaultPageCount; + } + + private String getHeader(URI uri) + { + List segments = UriComponent.decodePath(uri, false); + return switch (segments.get(1).getPath()) + { + case "Task" -> getTaskHeader(); + case "QuestionnaireResponse" -> getQuestionnaireResponseHeader(); + default -> throw new IllegalArgumentException("Unexpected resource path: " + segments.get(1).getPath()); + }; + } + + private String getTaskHeader() + { + return "IDStatusProcessMessage-NameRequesterBusiness-KeyLast Updated"; + } + + private String getQuestionnaireResponseHeader() + { + return "IDStatusQuestionnaireBusiness-KeyLast Updated"; + } + + private String getRow(Resource resource) + { + if (resource instanceof Task) + return getTaskRow((Task) resource); + else if (resource instanceof QuestionnaireResponse) + return getQuestionnaireResponseRow((QuestionnaireResponse) resource); + else + throw new IllegalArgumentException("Unexpected resource type: " + resource.getResourceType().name()); + } + + private String getTaskRow(Task resource) + { + String domain = "", processName = "", processVersion = ""; + if (resource.getInstantiatesCanonical() != null && !resource.getInstantiatesCanonical().isBlank()) + { + Matcher matcher = INSTANTIATES_CANONICAL_PATTERN.matcher(resource.getInstantiatesCanonical()); + if (matcher.matches()) + { + domain = matcher.group("domain"); + processName = matcher.group("processName"); + processVersion = matcher.group("processVersion"); + } + } + + String businessKey = resource.getInput().stream() + .filter(isStringParam(CODE_SYSTEM_BPMN_MESSAGE, CODE_SYSTEM_BPMN_MESSAGE_BUSINESS_KEY)).findFirst() + .map(c -> ((StringType) c.getValue()).getValue()).orElse(""); + String messageName = resource.getInput().stream() + .filter(isStringParam(CODE_SYSTEM_BPMN_MESSAGE, CODE_SYSTEM_BPMN_MESSAGE_MESSAGE_NAME)).findFirst() + .map(c -> ((StringType) c.getValue()).getValue()).orElse(""); + + return "" + + resource.getIdElement().getIdPart() + "" + resource.getStatus().toCode() + "" + + domain + " | " + processName + " | " + processVersion + "" + messageName + "" + + resource.getRequester().getIdentifier().getValue() + "" + businessKey + + "" + DATE_TIME_DISPLAY_FORMAT.format(resource.getMeta().getLastUpdated()) + ""; + } + + private Predicate isStringParam(String system, String code) + { + return p -> p.hasType() && p.getType().hasCoding() + && p.getType().getCoding().stream() + .anyMatch(c -> system.equals(c.getSystem()) && code.equals(c.getCode())) + && p.hasValue() && p.getValue() instanceof StringType; + } + + private String getQuestionnaireResponseRow(QuestionnaireResponse resource) + { + String businessKey = resource.getItem().stream() + .filter(i -> "business-key".equals(i.getLinkId()) && i.hasAnswer() && i.getAnswer().size() == 1 + && i.getAnswerFirstRep().hasValueStringType()) + .map(i -> i.getAnswerFirstRep().getValueStringType().getValue()).findFirst().orElse(""); + + return "" + + resource.getIdElement().getIdPart() + "" + resource.getStatus().toCode() + "" + + resource.getQuestionnaire().replaceAll("\\|", " \\| ") + "" + businessKey + + "" + DATE_TIME_DISPLAY_FORMAT.format(resource.getMeta().getLastUpdated()) + ""; + } +} diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java index bc904a9c1..0ca30c161 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; +import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -13,6 +14,7 @@ import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Task; import org.hl7.fhir.r4.model.Task.ParameterComponent; @@ -32,42 +34,47 @@ public Class getResourceType() } @Override - public void writeHtml(String basePath, Task task, OutputStreamWriter out) throws IOException + public boolean isResourceSupported(String basePath, URI resourceUri, Resource resource) + { + return resource != null && resource instanceof Task; + } + + @Override + public void writeHtml(String basePath, URI resourceUri, Task task, OutputStreamWriter out) throws IOException { boolean draft = Task.TaskStatus.DRAFT.equals(task.getStatus()); out.write("
"); - out.write("
\n"); - out.write("
\n"); + out.write("\n"); + out.write("
\n"); out.write("
"); out.write(""); out.write("Info\n"); - out.write(""); + out.write( + ""); out.write(""); out.write("
\n"); - String[] taskCanonicalSplit = task.getInstantiatesCanonical().split("\\|"); - String href = basePath + "ActivityDefinition?url=" + taskCanonicalSplit[0] + "&version=" - + taskCanonicalSplit[1]; - out.write("
"); - out.write("

\n"); - out.write("This Task resource " + (draft ? "can be used" : "was used") - + " to instantiate the following process:"); - out.write("

\n"); out.write("
    \n"); - out.write("
  • Process URL: " - + taskCanonicalSplit[0] + "
  • \n"); - out.write("
  • Process Version: " - + taskCanonicalSplit[1] + "
  • \n"); - out.write("
  • Task Profile: " - + task.getMeta().getProfile().stream().map(CanonicalType::getValue).collect(Collectors.joining(", ")) - + "
  • \n"); - out.write("
  • Task Status: " + task.getStatus().toCode() + "
  • \n"); + out.write("
  • ID / Version: " + (task.getIdElement() == null ? "" : task.getIdElement().getIdPart()) + + " / " + (task.getIdElement() == null ? "" : task.getIdElement().getVersionIdPart()) + "
  • \n"); + out.write("
  • Last Updated: " + (task.getMeta().getLastUpdated() == null ? "" + : DATE_TIME_DISPLAY_FORMAT.format(task.getMeta().getLastUpdated())) + "
  • \n"); + out.write("
  • Status: " + (task.getStatus() == null ? "" : task.getStatus().toCode()) + "
  • \n"); + out.write("
  • Process: " + + (task.getInstantiatesCanonical() == null ? "" + : task.getInstantiatesCanonical().replaceAll("\\|", " | ")) + + "
  • \n"); + out.write( + "
  • Task Profile: " + + task.getMeta().getProfile().stream().map(CanonicalType::getValue) + .map(v -> "" + + v.replaceAll("\\|", " | ") + "") + .collect(Collectors.joining(", ")) + + "
  • \n"); getInput(task, isMessageName()).ifPresent(m -> silentWrite(out, "
  • Message-Name: " + m + "
  • \n")); getInput(task, isBusinessKey()).ifPresent(k -> silentWrite(out, "
  • Business-Key: " + k + "
  • \n")); getInput(task, isCorrelationKey()) @@ -182,64 +189,10 @@ private Predicate isCorrelationKey() && CODESYSTEM_BPMN_MESSAGE_CORRELATION_KEY.equals(c.getCode())); } - private String getColorClass(Task.TaskStatus status, String elementType) - { - switch (status) - { - case DRAFT: - case REQUESTED: - { - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-draft-requested"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-draft-requested"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-draft-requested"; - } - case INPROGRESS: - { - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-progress"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-progress"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-progress"; - } - case COMPLETED: - { - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-completed"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-completed"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-completed"; - } - case ENTEREDINERROR: - case REJECTED: - case CANCELLED: - case FAILED: - { - if (ELEMENT_TYPE_ROW.equals(elementType)) - return "info-color-stopped-failed"; - else if (ELEMENT_TYPE_LINK.equals(elementType)) - return "info-link-stopped-failed"; - else if (ELEMENT_TYPE_PATH.equals(elementType)) - return "info-path-stopped-failed"; - } - case RECEIVED: - case ACCEPTED: - case READY: - case ONHOLD: - case NULL: - default: - return ""; - } - } - private void writeInput(Task.ParameterComponent input, Map elemenIndexMap, boolean draft, OutputStreamWriter out) throws IOException { - String typeCode = getTypeCode(input); + String typeCode = getCode(input.getType()); boolean display = display(draft, typeCode); if (input.hasValue()) @@ -252,7 +205,7 @@ private void writeInput(Task.ParameterComponent input, Map elem private void writeOutput(Task.TaskOutputComponent output, Map elemenIndexMap, OutputStreamWriter out) throws IOException { - String typeCode = getTypeCode(output); + String typeCode = getCode(output.getType()); if (output.hasValue()) { writeInputRow(output.getValue(), output.getExtension(), typeCode, elemenIndexMap, typeCode, true, false, @@ -270,16 +223,6 @@ private boolean display(boolean draft, String typeCode) return true; } - private String getTypeCode(Task.ParameterComponent input) - { - return getCode(input.getType()); - } - - private String getTypeCode(Task.TaskOutputComponent output) - { - return getCode(output.getType()); - } - private String getCode(CodeableConcept codeableConcept) { return codeableConcept.getCoding().stream().findFirst() diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/AdapterConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/AdapterConfig.java index 990641421..b231e3a2e 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/AdapterConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/AdapterConfig.java @@ -9,6 +9,7 @@ import dev.dsf.fhir.adapter.FhirAdapter; import dev.dsf.fhir.adapter.HtmlFhirAdapter; import dev.dsf.fhir.adapter.QuestionnaireResponseHtmlGenerator; +import dev.dsf.fhir.adapter.SearchBundleHtmlGenerator; import dev.dsf.fhir.adapter.TaskHtmlGenerator; @Configuration @@ -30,6 +31,7 @@ public FhirAdapter fhirAdapter() public HtmlFhirAdapter htmlFhirAdapter() { return new HtmlFhirAdapter(fhirConfig.fhirContext(), () -> propertiesConfig.getServerBaseUrl(), - List.of(new QuestionnaireResponseHtmlGenerator(), new TaskHtmlGenerator())); + List.of(new QuestionnaireResponseHtmlGenerator(), new TaskHtmlGenerator(), + new SearchBundleHtmlGenerator(propertiesConfig.getDefaultPageCount()))); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/bookmarks.js b/dsf-fhir/dsf-fhir-server/src/main/resources/static/bookmarks.js index ac633ff12..63ef2ade1 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/bookmarks.js +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/bookmarks.js @@ -114,9 +114,9 @@ function getInitialBookmarks() { 'NamingSystem': ['/fhir/NamingSystem'], 'Organization': ['/fhir/Organization', '/fhir/Organization?identifier=highmed.org', '/fhir/Organization?identifier=medizininformatik-initiative.de', '/fhir/Organization?identifier=netzwerk-universitaetsmedizin.de'], 'OrganizationAffiliation': ['/fhir/OrganizationAffiliation'], - 'QuestionnaireResponse': ['/fhir/QuestionnaireResponse?status=in-progress'], + 'QuestionnaireResponse': ['/fhir/QuestionnaireResponse?_sort=-_lastUpdated', '/fhir/QuestionnaireResponse?_sort=-_lastUpdated&status=in-progress'], 'Subscription': ['/fhir/Subscription'], - 'Task': ['/fhir/Task', '/fhir/Task?_sort=-_lastUpdated', '/fhir/Task?_sort=-_lastUpdated&_count=1', '/fhir/Task?status=draft'], + 'Task': ['/fhir/Task', '/fhir/Task?_sort=-_lastUpdated', '/fhir/Task?_sort=_profile&status=draft'], 'ValueSet': ['/fhir/ValueSet'] }; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css index 71c71281f..6e6863131 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css @@ -14,18 +14,28 @@ --color-input-border: #ccc; --color-input-radio-border: #000; --color-input-placeholder: #aaa; - --color-info-background-draft-requested: #deecf9; - --color-info-text-draft-requested: #326F95; - --color-info-background-progress: #fff7c7; - --color-info-text-progress: #a25b36; - --color-info-background-completed: #e4fde1; - --color-info-text-completed: #14452f; - --color-info-background-stopped-failed: #ffe1e1; - --color-info-text-stopped-failed: #761137; - --color-error-background: #ffe1e1; - --color-error-text: #761137; - --color-disabled-background: #f2f2f2; - --color-disabled-text: #545454; + --color-info-background-draft: #deecf9; + --color-info-text-draft: #326F95; + --color-info-background-requested: #deecf9; + --color-info-text-requested: #326F95; + --color-info-background-progress: #fff7c7; + --color-info-text-progress: #a25b36; + --color-info-background-completed: #e4fde1; + --color-info-text-completed: #14452f; + --color-info-background-failed: #ffe1e1; + --color-info-text-failed: #761137; + --color-info-background-stopped: #ffe1e1; + --color-info-text-stopped: #761137; + --color-error-background: #ffe1e1; + --color-error-text: #761137; + --color-disabled-background: #f2f2f2; + --color-disabled-text: #545454; + --color-row-border-draft: #326F95; + --color-row-border-requested: #888; + --color-row-border-progress: #a25b36; + --color-row-border-completed: #14452f; + --color-row-border-failed: #761137; + --color-row-border-stopped: #761137; } [theme="dark"] { @@ -44,18 +54,28 @@ --color-input-border: #666; --color-input-radio-border: #fff; --color-input-placeholder: #666; - --color-info-background-draft-requested: #326F95; - --color-info-text-draft-requested: #deecf9; - --color-info-background-progress: #a25b36; - --color-info-text-progress: #fff7c7; - --color-info-background-completed: #14452f; - --color-info-text-completed: #e4fde1; - --color-info-background-stopped-failed: #5e0101; - --color-info-text-stopped-failed: #ffe1e1; - --color-error-background: #5e0101; - --color-error-text: #ffe1e1; - --color-disabled-background: #333; - --color-disabled-text: #aaa; + --color-info-background-draft: #326F95; + --color-info-text-draft: #deecf9; + --color-info-background-requested: #326F95; + --color-info-text-requested: #deecf9; + --color-info-background-progress: #a25b36; + --color-info-text-progress: #fff7c7; + --color-info-background-completed: #14452f; + --color-info-text-completed: #e4fde1; + --color-info-background-failed: #761137; + --color-info-text-failed: #ffe1e1; + --color-info-background-stopped: #761137; + --color-info-text-stopped: #ffe1e1; + --color-error-background: #761137; + --color-error-text: #ffe1e1; + --color-disabled-background: #333; + --color-disabled-text: #aaa; + --color-row-border-draft: #326F95; + --color-row-border-requested: #888; + --color-row-border-progress: #a25b36; + --color-row-border-completed: #14452f; + --color-row-border-failed: #761137; + --color-row-border-stopped: #761137; } body { @@ -85,7 +105,9 @@ td#url h1 { word-break: break-word; } -td#url h1 a:link, td#url h1 a:visited, td#url h1 a:active { +td#url h1 a:link, +td#url h1 a:visited, +td#url h1 a:active { color: var(--color-prime); text-decoration: none; } @@ -129,10 +151,6 @@ pre.lang-html { font-family: monospace; } -li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { - list-style-type: decimal !important; -} - #icons { position: absolute; top: 2em; @@ -161,6 +179,15 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { fill: var(--color-prime); } +.icon[disabled] { + height: 1.4em; + cursor: auto; +} + +.icon[disabled]:hover>path { + fill: #aaa; +} + #help { position: absolute; top: 1em; @@ -228,7 +255,9 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { font-family: sans-serif; } -#bookmarks a:link, #bookmarks a:visited, #bookmarks a:active { +#bookmarks a:link, +#bookmarks a:visited, +#bookmarks a:active { color: var(--color-prime); text-decoration: none; vertical-align: super; @@ -267,36 +296,154 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { right: 1em; } -.bookmarks-list-entry-removed>a:link, .bookmarks-list-entry-removed>a:visited, - .bookmarks-list-entry-removed>a:active { +.bookmarks-list-entry-removed>a:link, +.bookmarks-list-entry-removed>a:visited, +.bookmarks-list-entry-removed>a:active { color: #aaa !important; } +.bundle>#header #resources { + padding-right: 1em; + color: #aaa +} + +.bundle>#header #page { + padding-left: 1em; + color: #aaa +} + +.bundle { + border: 1px solid #ccc; + padding: 20px 20px 10px 20px; + color: var(--color-prime); + overflow-x: auto; +} + +.bundle>#list { + border-collapse: separate; + border-spacing: 0 1rem; + width: 100%; +} + +.bundle>#list tr:not(:first-child) { + cursor: pointer; +} + +.bundle>#list th:first-child { + border-left: 0.5rem solid var(--color-row-background-deep); +} + +.bundle>#list td:first-child { + border-left: 0.5rem solid var(--color-row-background); +} + +.bundle>#list th { + color: var(--color-prime); + background-color: var(--color-row-background-deep); + padding: 1rem 0 1rem 1rem; + white-space: nowrap; + text-align: left; +} + +.bundle>#list td { + background-color: var(--color-row-background); + padding: 1rem 0 1rem 1rem; + white-space: nowrap; + text-align: left; +} + +.bundle>#list th:last-child { + padding-right: 1rem; +} + +.bundle>#list td:last-child { + padding-right: 1rem; +} + +.bundle>#list td[status="draft"]:first-child { + border-left-color: var(--color-row-border-draft); +} + +.bundle>#list td[status="requested"]:first-child { + border-left-color: var(--color-row-border-requested); +} + +.bundle>#list td[status="in-progress"]:first-child { + border-left-color: var(--color-row-border-progress); +} + +.bundle>#list td[status="completed"]:first-child { + border-left-color: var(--color-row-border-completed); +} + +.bundle>#list td[status="failed"]:first-child { + border-left-color: var(--color-row-border-failed); +} + +.bundle>#list td[status="stopped"]:first-child { + border-left-color: var(--color-row-border-stopped); +} + +.bundle>#list td.id-value { + font-family: monospace; +} + +.bundle a:link, +.bundle a:visited, +.bundle a:active { + color: var(--color-prime); + text-decoration: none; +} + +.bundle a:hover { + text-decoration: underline; +} + +.bundle>#list td:first-child, +.bundle>#list th:first-child { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +.bundle>#list td:last-child, +.bundle>#list th:last-child { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + @media print { body { margin: 0; } + table#header { margin-bottom: 2em; } + table#header img { height: 3em; } + td#url h1 { font-size: 1.5em; } + .tab { display: none; } + pre.prettyprint { border: 0 !important; } + #icons { display: none; } + #bookmarks { display: none; } + #help { display: none; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css index aba141675..47e192c0d 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/form.css @@ -1,383 +1,421 @@ form { - border: 1px solid #ccc; - padding: 20px 20px 10px 20px; - font-family: Epilogue, sans-serif; + border: 1px solid #ccc; + padding: 20px 20px 10px 20px; + font-family: Epilogue, sans-serif; } fieldset#form-fieldset { - display: block; - margin: 0; - padding: 0; - min-inline-size: min-content; - border: none; + display: block; + margin: 0; + padding: 0; + min-inline-size: min-content; + border: none; } .row { - border-radius: 5px; - padding: 0 1em 1em 1em; - margin-bottom: 0.7em; - background-color: var(--color-row-background); + border-radius: 5px; + padding: 0 1em 1em 1em; + margin-bottom: 0.7em; + background-color: var(--color-row-background); } .row-extension-0 { - margin-top: 12px; - padding: 0 0 12px 12px; - border-radius: 5px; - background-color: var(--color-row-background-deep); + margin-top: 12px; + padding: 0 0 12px 12px; + border-radius: 5px; + background-color: var(--color-row-background-deep); } .row-label-extension-no-value { - margin-bottom: -10px; + margin-bottom: -10px; } .row-extension { - padding: 10px 15px 0 15px; + padding: 10px 15px 0 15px; } .row-display { - padding-top: 15px; + padding-top: 15px; } .p-display { - margin: 0; - color: var(--color-input-text) + margin: 0; + color: var(--color-input-text) } .error { - background-color: var(--color-error-background); + background-color: var(--color-error-background); } .error-list-not-visible { - display: none; + display: none; } .error-list-visible { - display: block; - font-size: small; - color: var(--color-error-text); - padding-left: 20px; - margin-bottom: 0; + display: block; + font-size: small; + color: var(--color-error-text); + padding-left: 20px; + margin-bottom: 0; } .row-info { - display: flex; - padding-top: 15px; + display: flex; + padding-top: 15px; } -.info-color-draft-requested { - background-color: var(--color-info-background-draft-requested); - color: var(--color-info-text-draft-requested); +form[status=draft] .row-info { + background-color: var(--color-info-background-draft); + color: var(--color-info-text-draft); } -.info-color-progress { - background-color: var(--color-info-background-progress); - color: var(--color-info-text-progress); +form[status=requested] .row-info { + background-color: var(--color-info-background-requested); + color: var(--color-info-text-requested); } -.info-color-completed { - background-color: var(--color-info-background-completed); - color: var(--color-info-text-completed); +form[status=in-progress] .row-info { + background-color: var(--color-info-background-progress); + color: var(--color-info-text-progress); } -.info-color-stopped-failed { - background-color: var(--color-info-background-stopped-failed); - color: var(--color-info-text-stopped-failed); +form[status=completed] .row-info { + background-color: var(--color-info-background-completed); + color: var(--color-info-text-completed); } -.info-link { - font-family: monospace; - font-size: 130%; +form[status=failed] .row-info { + background-color: var(--color-info-background-failed); + color: var(--color-info-text-failed); } -.info-link-task { - color: #326F95; +form[status=stopped] .row-info { + background-color: var(--color-info-background-stopped); + color: var(--color-info-text-stopped); } -.info-link-draft-requested { - color: var(--color-info-text-draft-requested); +form[status=draft] .row-info .info-icon>path { + fill: var(--color-info-text-draft); } -.info-link-progress { - color: var(--color-info-text-progress); +form[status=requested] .row-info .info-icon>path { + fill: var(--color-info-text-requested); } -.info-link-completed { - color: var(--color-info-text-completed); +form[status=in-progress] .row-info .info-icon>path { + fill: var(--color-info-text-progress); } -.info-link-stopped-failed { - color: var(--color-info-text-stopped-failed); +form[status=completed] .row-info .info-icon>path { + fill: var(--color-info-text-completed); } -.info-link:active { - font-family: monospace; - font-size: 130%; +form[status=failed] .row-info .info-icon>path { + fill: var(--color-info-text-failed); } -.info-link-draft-requested:active { - color: var(--color-info-text-draft-requested); +form[status=stopped] .row-info .info-icon>path { + fill: var(--color-info-text-stopped); } -.info-link-progress:active { - color: var(--color-info-text-progress); +form[status=draft] .row-info a:link, +form[status=draft] .row-info a:visited, +form[status=draft] .row-info a:active { + color: var(--color-info-text-draft); } -.info-link-completed:active { - color: var(--color-info-text-completed); +form[status=requested] .row-info a:link, +form[status=requested] .row-info a:visited, +form[status=requested] .row-info a:active { + color: var(--color-info-text-requested); } -.info-link-stopped-failed:active { - color: var(--color-info-text-stopped-failed); +form[status=in-progress] .row-info a:link, +form[status=in-progress] .row-info a:visited, +form[status=in-progress] .row-info a:active { + color: var(--color-info-text-progress); } -.info-icon { - padding-top: 15px; - padding-right: 15px; - height: 35px; - width: 35px; +form[status=completed] .row-info a:link, +form[status=completed] .row-info a:visited, +form[status=completed] .row-info a:active { + color: var(--color-info-text-completed); +} + +form[status=failed] .row-info a:link, +form[status=failed] .row-info a:visited, +form[status=failed] .row-info a:active { + color: var(--color-info-text-failed); } -.info-icon > path.info-path-draft-requested { - fill: var(--color-info-text-draft-requested); +form[status=stopped] .row-info a:link, +form[status=stopped] .row-info a:visited, +form[status=stopped] .row-info a:active { + color: var(--color-info-text-stopped); } -.info-icon > path.info-path-progress { - fill: var(--color-info-text-progress); +form .row-info a:link, +form .row-info a:visited, +form .row-info a:active { + text-decoration: none; } -.info-icon > path.info-path-completed { - fill: var(--color-info-text-completed); +form .row-info a:hover { + text-decoration: underline; } -.info-icon > path.info-path-stopped-failed { - fill: var(--color-info-text-stopped-failed); +.info-icon { + padding-top: 15px; + padding-right: 15px; + height: 35px; + width: 35px; } .info-list { - padding-left: 20px; + padding-left: 20px; } -.info-list > li:not(:last-child) { - margin-bottom: 0.3em; +.info-list>li:not(:last-child) { + margin-bottom: 0.3em; } .row:after { - content: ""; - display: table; - clear: both; + content: ""; + display: table; + clear: both; } .row-submit { - background-color: transparent; - padding: 0; + background-color: transparent; + padding: 0; } label { - display: block; - padding: 12px 12px 12px 0; - font-weight: regular; + display: block; + padding: 12px 12px 12px 0; + font-weight: regular; } label.radio { - padding: 5px 12px 5px 0; - font-weight: 100; - color: var(--color-input-text) + padding: 5px 12px 5px 0; + font-weight: 100; + color: var(--color-input-text) } input { - background-color: var(--color-input-background); - color: var(--color-input-text); + background-color: var(--color-input-background); + color: var(--color-input-text); } input[type=radio] { - margin-right: 7px; + margin-right: 7px; } input[type="radio"] { - border: 0.1em solid var(--color-input-radio-border); - border-radius: 9px; - height: 18px; - width: 18px; - margin-bottom: -0.2em; - -webkit-appearance: none; + border: 0.1em solid var(--color-input-radio-border); + border-radius: 9px; + height: 18px; + width: 18px; + margin-bottom: -0.2em; + -webkit-appearance: none; + appearance: none; } -input[type="radio"][disabled], fieldset#form-fieldset[disabled] input[type="radio"] { - border: 1px solid var(--color-disabled-text); +input[type="radio"][disabled], +fieldset#form-fieldset[disabled] input[type="radio"] { + border: 1px solid var(--color-disabled-text); } input[type="radio"]:checked { - border: 0.1em solid var(--color-prime); - background-color: var(--color-prime); - box-shadow: inset 0 0 0 0.2em var(--color-background); -} - -input[type="radio"][disabled]:checked, fieldset#form-fieldset[disabled] input[type="radio"]:checked { - border: 0.1em solid var(--color-disabled-text); - background-color: var(--color-disabled-text); - box-shadow: inset 0 0 0 0.2em var(--color-disabled-background); -} - -input[disabled], fieldset#form-fieldset[disabled] input { - cursor: default; - background-color: var(--color-disabled-background); - color: var(--color-disabled-text); - border-color: rgba(118, 118, 118, 0.5); -} - -input[type=text], input[type=url], select, textarea { - width: 100%; - min-width: 200px; - padding: 12px; - border: 1px solid var(--color-input-border); - border-radius: 4px; - box-sizing: border-box; - resize: vertical; - display: block; -} - -input[type=date], input[type=time], input[type=datetime-local], input[type=number] { - width: 100%; - min-width: 200px; - padding: 12px; - border: 1px solid var(--color-input-border); - border-radius: 4px; - box-sizing: border-box; - resize: none; - display: block; + border: 0.1em solid var(--color-prime); + background-color: var(--color-prime); + box-shadow: inset 0 0 0 0.2em var(--color-background); +} + +input[type="radio"][disabled]:checked, +fieldset#form-fieldset[disabled] input[type="radio"]:checked { + border: 0.1em solid var(--color-disabled-text); + background-color: var(--color-disabled-text); + box-shadow: inset 0 0 0 0.2em var(--color-disabled-background); +} + +input[disabled], +fieldset#form-fieldset[disabled] input { + cursor: default; + background-color: var(--color-disabled-background); + color: var(--color-disabled-text); + border-color: rgba(118, 118, 118, 0.5); +} + +input[type=text], +input[type=url], +select, +textarea { + width: 100%; + min-width: 200px; + padding: 12px; + border: 1px solid var(--color-input-border); + border-radius: 4px; + box-sizing: border-box; + resize: vertical; + display: block; +} + +input[type=date], +input[type=time], +input[type=datetime-local], +input[type=number] { + width: 100%; + min-width: 200px; + padding: 12px; + border: 1px solid var(--color-input-border); + border-radius: 4px; + box-sizing: border-box; + resize: none; + display: block; } input[type=number] { - -webkit-appearance: textfield; - -moz-appearance: textfield; - -o-appearance: textfield; - appearance: textfield; + -webkit-appearance: textfield; + -moz-appearance: textfield; + -o-appearance: textfield; + appearance: textfield; } .input-group { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } .input-group-svg { - cursor: pointer; - align-self: center; - margin-left: 0.46em; - rotate: 180deg; + cursor: pointer; + align-self: center; + margin-left: 0.46em; + rotate: 180deg; } -.input-group-svg > path { - fill: #aaa; +.input-group-svg>path { + fill: #aaa; } -.input-group-svg:hover > path { +.input-group-svg:hover>path { fill: #326F95; } input.identifier-coding-code { - margin-top: 6px; + margin-top: 6px; } .input-output-header { - font-family: monospace; - font-size: 1.75em; - color: #326F95; - padding-left: 5px; - margin-bottom: 12px; + font-family: monospace; + font-size: 1.75em; + color: #326F95; + padding-left: 5px; + margin-bottom: 12px; } .invisible { - display: none; + display: none; } button.submit { - background-color: #326F95; - color: #fff; - padding: 12px 60px; - border: none; - border-radius: 4px; - cursor: pointer; - float: left; + background-color: #326F95; + color: #fff; + padding: 12px 60px; + border: none; + border-radius: 4px; + cursor: pointer; + float: left; } .spinner-enabled { - display: block; + display: block; } .spinner-disabled { - display: none; + display: none; } .spinner { - position: fixed; - width: 100%; - left: 0; - right: 0; - top: 0; - bottom: 0; - background-color: rgba(255,255,255,0.7); - z-index: 9999; + position: fixed; + width: 100%; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.7); + z-index: 9999; } @-webkit-keyframes spin { - from { -webkit-transform: rotate(0deg); } - to { -webkit-transform: rotate(360deg); } + from { + -webkit-transform: rotate(0deg); + } + + to { + -webkit-transform: rotate(360deg); + } } @keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } } .spinner::after { - content: ''; - position: absolute; - left: 48%; - top: 40%; - width: 40px; - height: 40px; - border-style: solid; - border-color: #29235c; - border-top-color: transparent; - border-width: 4px; - border-radius: 50%; - -webkit-animation: spin .8s linear infinite; - animation: spin .8s linear infinite; + content: ''; + position: absolute; + left: 48%; + top: 40%; + width: 40px; + height: 40px; + border-style: solid; + border-color: #29235c; + border-top-color: transparent; + border-width: 4px; + border-radius: 50%; + -webkit-animation: spin .8s linear infinite; + animation: spin .8s linear infinite; } .cardinalities { - font-weight: normal; - font-size: x-small; - color: #aaa; + font-weight: normal; + font-size: x-small; + color: #aaa; } .row-label { - color: var(--color-input-text); - padding: 12px 0 6px 6px; - font-weight: bold; - font-size: small; - position: relative; + color: var(--color-input-text); + padding: 12px 0 6px 6px; + font-weight: bold; + font-size: small; + position: relative; } .plus-minus-icon { - position: absolute; - bottom: 0.2em; - right: 0.1em; - cursor: pointer; + position: absolute; + bottom: 0.2em; + right: 0.1em; + cursor: pointer; } -.plus-minus-icon:hover > svg > path { +.plus-minus-icon:hover>svg>path { fill: #326F95; } -.plus-minus-icon > svg > path { - fill: #aaa; +.plus-minus-icon>svg>path { + fill: #aaa; } ::placeholder { - color: var(--color-input-placeholder); + color: var(--color-input-placeholder); } ::-ms-input-placeholder { - color: var(--color-input-placeholder); + color: var(--color-input-placeholder); } \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css index 421a37d53..50e8167ef 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/prettify.css @@ -60,10 +60,14 @@ li.L0, li.L1, li.L2, li.L3, +li.L4, li.L5, li.L6, li.L7, -li.L8 { list-style-type: none; color: var(--color-prime) } +li.L8, +li.L9 { + list-style-type: decimal; color: var(--color-prime); +} /* Alternate shading for lines */ li.L1, li.L3, From 55ff53259b35017b145b9723342b26534e90dcc4 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 10:10:03 +0200 Subject: [PATCH 29/39] HTML adapters to FHIR server module, removing now not needed dependency --- dsf-fhir/dsf-fhir-rest-adapter/pom.xml | 4 ---- .../src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 0 .../src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java | 0 .../main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java | 0 .../dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java | 0 .../java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java | 0 .../src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java | 0 7 files changed, 4 deletions(-) rename dsf-fhir/{dsf-fhir-rest-adapter => dsf-fhir-server}/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java (100%) rename dsf-fhir/{dsf-fhir-rest-adapter => dsf-fhir-server}/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java (100%) rename dsf-fhir/{dsf-fhir-rest-adapter => dsf-fhir-server}/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java (100%) rename dsf-fhir/{dsf-fhir-rest-adapter => dsf-fhir-server}/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java (100%) rename dsf-fhir/{dsf-fhir-rest-adapter => dsf-fhir-server}/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java (100%) rename dsf-fhir/{dsf-fhir-rest-adapter => dsf-fhir-server}/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java (100%) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml index 363bcb437..5f3025bb3 100755 --- a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml +++ b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml @@ -14,10 +14,6 @@ dev.dsf dsf-common-auth - - org.glassfish.jersey.core - jersey-common - ca.uhn.hapi.fhir diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java similarity index 100% rename from dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java similarity index 100% rename from dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlGenerator.java diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java similarity index 100% rename from dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/InputHtmlGenerator.java diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java similarity index 100% rename from dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/QuestionnaireResponseHtmlGenerator.java diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java similarity index 100% rename from dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java similarity index 100% rename from dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/TaskHtmlGenerator.java From 0fa0215b9e42bfde9ef868aaf609430d29d1efe8 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 21 Aug 2023 10:59:10 +0200 Subject: [PATCH 30/39] adapt color for requested to gray --- .../src/main/resources/static/dsf.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css index 6e6863131..f7b217a92 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css @@ -16,8 +16,8 @@ --color-input-placeholder: #aaa; --color-info-background-draft: #deecf9; --color-info-text-draft: #326F95; - --color-info-background-requested: #deecf9; - --color-info-text-requested: #326F95; + --color-info-background-requested: #f2f2f2; + --color-info-text-requested: #545454; --color-info-background-progress: #fff7c7; --color-info-text-progress: #a25b36; --color-info-background-completed: #e4fde1; @@ -31,7 +31,7 @@ --color-disabled-background: #f2f2f2; --color-disabled-text: #545454; --color-row-border-draft: #326F95; - --color-row-border-requested: #888; + --color-row-border-requested: #545454; --color-row-border-progress: #a25b36; --color-row-border-completed: #14452f; --color-row-border-failed: #761137; @@ -56,8 +56,8 @@ --color-input-placeholder: #666; --color-info-background-draft: #326F95; --color-info-text-draft: #deecf9; - --color-info-background-requested: #326F95; - --color-info-text-requested: #deecf9; + --color-info-background-requested: #333; + --color-info-text-requested: #aaa; --color-info-background-progress: #a25b36; --color-info-text-progress: #fff7c7; --color-info-background-completed: #14452f; @@ -71,7 +71,7 @@ --color-disabled-background: #333; --color-disabled-text: #aaa; --color-row-border-draft: #326F95; - --color-row-border-requested: #888; + --color-row-border-requested: #aaa; --color-row-border-progress: #a25b36; --color-row-border-completed: #14452f; --color-row-border-failed: #761137; From 2eb11e7ace6bd3798f92a05462c4b6d1eddf292f Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 11:04:03 +0200 Subject: [PATCH 31/39] row links now starting with basePath --- .../fhir/adapter/SearchBundleHtmlGenerator.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java index 56ee273f8..d28d2278f 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java @@ -121,13 +121,15 @@ public void writeHtml(String basePath, URI resourceUri, Bundle resource, OutputS out.write(""); out.write(getHeader(resourceUri)); - out.write(resource.getEntry().stream() - .filter(e -> e.hasResource() && e.hasSearch() && e.getSearch().hasMode() - && SearchEntryMode.MATCH.equals(e.getSearch().getMode())) - .map(BundleEntryComponent::getResource) - .map(r -> "" + getRow(r) + "\n") - .collect(Collectors.joining())); + out.write( + resource.getEntry().stream() + .filter(e -> e.hasResource() && e.hasSearch() && e.getSearch().hasMode() + && SearchEntryMode.MATCH.equals(e.getSearch().getMode())) + .map(BundleEntryComponent::getResource) + .map(r -> "" + + getRow(r) + "\n") + .collect(Collectors.joining())); out.write("
    "); long includeResources = resource.getEntry().stream().filter( From 04203c4aa6a4087c792b023f04279d5296ac4308 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 11:05:16 +0200 Subject: [PATCH 32/39] different background color on row hover for search bundle ui --- dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css index f7b217a92..79335143b 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css @@ -9,6 +9,7 @@ --color-tab-button-background-active: #ccc; --color-row-background: #f2f2f2; --color-row-background-deep: #e2e2e2; + --color-row-background-hover: #ddd; --color-input-text: #000; --color-input-background: #fff; --color-input-border: #ccc; @@ -49,6 +50,7 @@ --color-tab-button-background-active: #888; --color-row-background: #333; --color-row-background-deep: #444; + --color-row-background-hover: #777; --color-input-text: #fff; --color-input-background: #181818; --color-input-border: #666; @@ -352,6 +354,10 @@ pre.lang-html { text-align: left; } +.bundle>#list tr:hover td { + background-color: var(--color-row-background-hover); +} + .bundle>#list th:last-child { padding-right: 1rem; } From b8d7892c673ebffca8a5da162f60838369cbb575 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 21 Aug 2023 11:24:59 +0200 Subject: [PATCH 33/39] lighter completed text/border color --- dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css index 79335143b..423c02e1e 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css +++ b/dsf-fhir/dsf-fhir-server/src/main/resources/static/dsf.css @@ -22,7 +22,7 @@ --color-info-background-progress: #fff7c7; --color-info-text-progress: #a25b36; --color-info-background-completed: #e4fde1; - --color-info-text-completed: #14452f; + --color-info-text-completed: #357a38; --color-info-background-failed: #ffe1e1; --color-info-text-failed: #761137; --color-info-background-stopped: #ffe1e1; @@ -34,7 +34,7 @@ --color-row-border-draft: #326F95; --color-row-border-requested: #545454; --color-row-border-progress: #a25b36; - --color-row-border-completed: #14452f; + --color-row-border-completed: #357a38; --color-row-border-failed: #761137; --color-row-border-stopped: #761137; } From ca806e2adb770b0acb70cb4527c0b609dbebff8e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 11:43:25 +0200 Subject: [PATCH 34/39] remove trailing slash from HTML title value for `fhir/Foo/` URLs --- .../java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java index c520498a6..bcc90bb71 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java @@ -155,8 +155,7 @@ public void writeTo(Resource resource, Class type, Type genericType, Annotati """.replace("${basePath}", basePath)); - out.write("DSF" + (uriInfo.getPath() == null || uriInfo.getPath().isEmpty() ? "" : ": ") - + uriInfo.getPath() + "\n"); + out.write("" + getTitle(uriInfo) + "\n"); out.write("\n"); out.write("\n"); @@ -270,6 +269,16 @@ public void writeTo(Resource resource, Class type, Type genericType, Annotati out.flush(); } + private String getTitle(UriInfo uri) + { + if (uri == null || uri.getPath() == null || uriInfo.getPath().isBlank()) + return "DSF"; + else if (uriInfo.getPath().endsWith("/")) + return "DSF: " + uriInfo.getPath().substring(0, uriInfo.getPath().length() - 1); + else + return "DSF: " + uriInfo.getPath(); + } + private String getUrlHeading(Resource resource) throws MalformedURLException { URI uri = getResourceUri(resource); From 0d1c2388dbaca221af3264e383c82cc53e972c4b Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 12:36:01 +0200 Subject: [PATCH 35/39] only showing resource/page calculation if bundle contains resources --- .../fhir/adapter/SearchBundleHtmlGenerator.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java index d28d2278f..49bedeb46 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java @@ -89,14 +89,16 @@ public void writeHtml(String basePath, URI resourceUri, Bundle resource, OutputS out.write(""); out.write(""); - int page = getPage(resourceUri); - int count = getCount(resourceUri); - int max = (int) Math.ceil((double) resource.getTotal() / count); - int firstResource = ((page - 1) * count) + 1; - int lastResource = ((page - 1) * count) + resource.getEntry().size(); - if (page > 0 && page <= max) + if (resource.getEntry().size() > 0) + { + int page = getPage(resourceUri); + int count = getCount(resourceUri); + int max = (int) Math.ceil((double) resource.getTotal() / count); + int firstResource = ((page - 1) * count) + 1; + int lastResource = ((page - 1) * count) + resource.getEntry().size(); out.write("Resources " + firstResource + " - " + lastResource + " / " + resource.getTotal() + "Page " + page + " / " + max + ""); + } out.write(""); Optional next = resource.getLink().stream().filter(l -> "next".equals(l.getRelation())).findFirst() From d0826b77509b5d5a7093e6ac0f3da03f40567a6f Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 12:53:46 +0200 Subject: [PATCH 36/39] OperationOutcome warnings now shown underneath table --- .../dsf/fhir/adapter/SearchBundleHtmlGenerator.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java index 49bedeb46..81b4528f1 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java @@ -16,6 +16,8 @@ import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent; import org.hl7.fhir.r4.model.Bundle.SearchEntryMode; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; @@ -141,6 +143,16 @@ public void writeHtml(String basePath, URI resourceUri, Bundle resource, OutputS out.write("

    " + includeResources + " include " + (includeResources == 1 ? "resource" : "resources") + " hidden.

    "); + List diagnostics = resource.getEntry().stream().filter(BundleEntryComponent::hasResource) + .map(BundleEntryComponent::getResource).filter(r -> r instanceof OperationOutcome) + .map(r -> (OperationOutcome) r).map(OperationOutcome::getIssue).flatMap(List::stream) + .filter(OperationOutcomeIssueComponent::hasSeverity) + .filter(OperationOutcomeIssueComponent::hasDiagnostics) + .map(i -> i.getSeverity().getDisplay() + ": " + i.getDiagnostics()).toList(); + for (String diag : diagnostics) + out.write("

    " + diag.replaceAll("&", "&") + .replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">") + "

    "); + out.write("
"); } From 469e45c86b4c3947f415aa3b878c51ed1cfc62eb Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 21 Aug 2023 15:31:37 +0200 Subject: [PATCH 37/39] dependency version upgrades --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 857aded68..0f812ca95 100755 --- a/pom.xml +++ b/pom.xml @@ -24,9 +24,9 @@ 2.0.7 2.20.0 11.0.15 - 3.1.2 + 3.1.3 2.1.3 - 6.0.10 + 6.0.11 2.15.2 7.19.0 5.1.0 @@ -122,7 +122,7 @@ org.bouncycastle bcmail-jdk18on - 1.75 + 1.76 @@ -139,7 +139,7 @@ org.bouncycastle bcprov-jdk18on - 1.75 + 1.76 @@ -151,7 +151,7 @@ org.liquibase liquibase-core - 4.22.0 + 4.23.1 org.postgresql @@ -163,12 +163,12 @@ de.hs-heilbronn.mi crypto-utils - 3.7.0 + 3.8.0 de.hs-heilbronn.mi db-test-utils - 0.21.0 + 0.22.0 @@ -396,7 +396,7 @@ com.google.guava guava - 32.0.1-jre + 32.1.2-jre com.google.code.gson @@ -441,19 +441,19 @@ org.yaml snakeyaml - 2.0 + 2.1 org.apache.maven maven-core - 3.9.3 + 3.9.4 org.apache.maven maven-plugin-api - 3.9.3 + 3.9.4 org.apache.maven.plugin-tools @@ -563,7 +563,7 @@ io.fabric8 docker-maven-plugin - 0.43.0 + 0.43.4 org.apache.maven.plugins From c93d775a1f837d733398bde26f85f126b2c675c7 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 22 Aug 2023 18:38:42 +0200 Subject: [PATCH 38/39] improved data sanitation --- .../dev/dsf/fhir/adapter/HtmlFhirAdapter.java | 34 +++++++++---------- .../adapter/SearchBundleHtmlGenerator.java | 4 +-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java index bcc90bb71..6cb2ab0ee 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/HtmlFhirAdapter.java @@ -34,6 +34,7 @@ import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Task; +import org.springframework.web.util.HtmlUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.annotation.ResourceDef; @@ -155,7 +156,7 @@ public void writeTo(Resource resource, Class type, Type genericType, Annotati """.replace("${basePath}", basePath)); - out.write("" + getTitle(uriInfo) + "\n"); + out.write("" + getTitle() + "\n"); out.write("\n"); out.write("\n"); @@ -269,14 +270,14 @@ public void writeTo(Resource resource, Class type, Type genericType, Annotati out.flush(); } - private String getTitle(UriInfo uri) + private String getTitle() { - if (uri == null || uri.getPath() == null || uriInfo.getPath().isBlank()) + if (uriInfo == null || uriInfo.getPath() == null || uriInfo.getPath().isBlank()) return "DSF"; else if (uriInfo.getPath().endsWith("/")) - return "DSF: " + uriInfo.getPath().substring(0, uriInfo.getPath().length() - 1); + return "DSF: " + HtmlUtils.htmlEscape(uriInfo.getPath().substring(0, uriInfo.getPath().length() - 1)); else - return "DSF: " + uriInfo.getPath(); + return "DSF: " + HtmlUtils.htmlEscape(uriInfo.getPath()); } private String getUrlHeading(Resource resource) throws MalformedURLException @@ -289,20 +290,22 @@ private String getUrlHeading(Resource resource) throws MalformedURLException for (int i = 2; i < pathSegments.length; i++) { - u += "/" + pathSegments[i]; - heading.append("/" + pathSegments[i] + ""); + String pathSegment = HtmlUtils.htmlEscape(pathSegments[i]); + u += "/" + pathSegment; + heading.append("/" + pathSegment + ""); } if (uri.getQuery() != null) { - u += "?" + uri.getQuery(); - heading.append("?" + uri.getQuery() + ""); + String querValue = HtmlUtils.htmlEscape(uri.getQuery()); + u += "?" + querValue; + heading.append("?" + querValue + ""); } else if (uriInfo.getQueryParameters().containsKey("_summary")) { - u += "?_summary=" + uriInfo.getQueryParameters().getFirst("_summary"); - heading.append("?_summary=" - + uriInfo.getQueryParameters().getFirst("_summary") + ""); + String summaryValue = HtmlUtils.htmlEscape(uriInfo.getQueryParameters().getFirst("_summary")); + u += "?_summary=" + summaryValue; + heading.append("?_summary=" + summaryValue + ""); } heading.append('\n'); @@ -460,11 +463,8 @@ private boolean isHtmlEnabled(Class resourceType, String basePath, Resource r { URI resourceUri = getResourceUri(resource); - if (htmlGeneratorsByType.containsKey(resourceType)) - return uriInfo != null && htmlGeneratorsByType.get(resourceType).stream() - .anyMatch(g -> g.isResourceSupported(basePath, resourceUri, resource)); - else - return false; + return htmlGeneratorsByType.containsKey(resourceType) && htmlGeneratorsByType.get(resourceType).stream() + .anyMatch(g -> g.isResourceSupported(basePath, resourceUri, resource)); } private String adaptFormInputsIfTask(Resource resource) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java index 81b4528f1..f94d84109 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/SearchBundleHtmlGenerator.java @@ -23,6 +23,7 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Task; import org.hl7.fhir.r4.model.Task.ParameterComponent; +import org.springframework.web.util.HtmlUtils; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.PathSegment; @@ -150,8 +151,7 @@ public void writeHtml(String basePath, URI resourceUri, Bundle resource, OutputS .filter(OperationOutcomeIssueComponent::hasDiagnostics) .map(i -> i.getSeverity().getDisplay() + ": " + i.getDiagnostics()).toList(); for (String diag : diagnostics) - out.write("

" + diag.replaceAll("&", "&") - .replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">") + "

"); + out.write("

" + HtmlUtils.htmlEscape(diag) + "

"); out.write("
"); } From 0a9f033660ab7ee4079b28adf02fe339cf82a94e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 22 Aug 2023 20:06:25 +0200 Subject: [PATCH 39/39] 1.1.0 release --- CITATION.cff | 2 +- README.md | 14 +++++++++++--- dsf-bpe/dsf-bpe-process-api-v1/pom.xml | 2 +- dsf-bpe/dsf-bpe-server-jetty/pom.xml | 2 +- dsf-bpe/dsf-bpe-server/pom.xml | 2 +- dsf-bpe/pom.xml | 2 +- dsf-common/dsf-common-auth/pom.xml | 2 +- dsf-common/dsf-common-config/pom.xml | 2 +- dsf-common/dsf-common-documentation/pom.xml | 2 +- dsf-common/dsf-common-jetty/pom.xml | 2 +- dsf-common/dsf-common-status/pom.xml | 2 +- dsf-common/pom.xml | 2 +- dsf-fhir/dsf-fhir-auth/pom.xml | 2 +- dsf-fhir/dsf-fhir-rest-adapter/pom.xml | 2 +- dsf-fhir/dsf-fhir-server-jetty/pom.xml | 2 +- dsf-fhir/dsf-fhir-server/pom.xml | 2 +- dsf-fhir/dsf-fhir-validation/pom.xml | 4 ++-- dsf-fhir/dsf-fhir-webservice-client/pom.xml | 2 +- dsf-fhir/dsf-fhir-websocket-client/pom.xml | 2 +- dsf-fhir/pom.xml | 2 +- dsf-tools/dsf-tools-build-info-reader/pom.xml | 2 +- dsf-tools/dsf-tools-bundle-generator/pom.xml | 2 +- dsf-tools/dsf-tools-db-migration/pom.xml | 2 +- dsf-tools/dsf-tools-docker-secrets-reader/pom.xml | 2 +- .../dsf-tools-documentation-generator/pom.xml | 2 +- dsf-tools/dsf-tools-proxy-test/pom.xml | 2 +- dsf-tools/dsf-tools-test-data-generator/pom.xml | 2 +- dsf-tools/pom.xml | 2 +- pom.xml | 2 +- 29 files changed, 40 insertions(+), 32 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index f94d29176..23ca64817 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -25,7 +25,7 @@ preferred-citation: type: proceedings title: "Data Sharing Framework (DSF)" version: 1.1.0 -date-released: 2023-06-28 +date-released: 2023-08-22 url: https://dsf.dev repository-code: https://github.com/datasharingframework/dsf repository-artifact: https://github.com/datasharingframework/dsf/releases diff --git a/README.md b/README.md index 09dae070a..152d3b978 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ -# Data Sharing Framework (DSF) +![Data Sharing Framework (DSF) logo.](dsf-fhir/dsf-fhir-server/src/main/resources/static/logo.svg) -The Data Sharing Framework (DSF) implements a distributed process engine based on the BPMN 2.0 and FHIR R4 standards. The DSF is used to support biomedical research with real-world data. Every participating site runs a FHIR endpoint (dsf-fhir) accessible by other sites and a business process engine (dsf-bpe) in the local secured network. Authentication between sites is handled using X.509 client/server certificates. The process engines execute BPMN processes in order to coordinate local and remote steps necessary to enable cross-site data sharing and feasibility analyses. This includes access to local data repositories, use-and-access-committee decision support, consent filtering, and privacy preserving record-linkage and pseudonymization. +The Data Sharing Framework (DSF) implements a distributed process engine based on the BPMN 2.0 and FHIR R4 standards. The DSF is used to support biomedical research with real-world data. Every participating site runs a FHIR endpoint (dsf-fhir) accessible by other sites and a business process engine (dsf-bpe) in the local secured network. Authentication between sites is handled using X.509 client/server certificates. The process engines execute BPMN processes in order to coordinate local and remote steps necessary to enable cross-site data sharing and feasibility analyses. This includes access to local data repositories, use-and-access-committee decision support, consent filtering, and privacy preserving record-linkage and pseudonymization. + +For [installation instructions](https://dsf.dev/stable/maintain/install.html), tutorials, publications, and other information about the DSF, visit [https://dsf.dev](https://dsf.dev). ## Development Branching follows the git-flow model, for the latest development version see branch [develop](https://github.com/datasharingframework/dsf/tree/develop). ## License -All code from the Data Sharing Framework is published under the [Apache-2.0 License](LICENSE). \ No newline at end of file +All code from the Data Sharing Framework is published under the [Apache-2.0 License](LICENSE). + +## Public Funding +The DSF is funded by the German Federal Ministry of Education and Research (BMBF) within the [Data Sharing Framework Community](https://www.gesundheitsforschung-bmbf.de/de/dsf-medizininformatik-struktur-data-sharing-framework-community-16133.php) project of the [German Medical Informatics Initiative](https://www.medizininformatik-initiative.de/en/start), grant IDs 01ZZ2307A, 01ZZ2307B and 01ZZ2307C. + +## Earlier Versions +Earlier versions of the DSF, developed as part of the HiGHmed research consortium, can be found at [highmed/highmed-dsf](https://github.com/highmed/highmed-dsf). \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v1/pom.xml b/dsf-bpe/dsf-bpe-process-api-v1/pom.xml index dd7edf878..ae9b67b13 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1/pom.xml +++ b/dsf-bpe/dsf-bpe-process-api-v1/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-bpe-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-bpe/dsf-bpe-server-jetty/pom.xml b/dsf-bpe/dsf-bpe-server-jetty/pom.xml index 299d2e7d8..170072931 100755 --- a/dsf-bpe/dsf-bpe-server-jetty/pom.xml +++ b/dsf-bpe/dsf-bpe-server-jetty/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-bpe-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-bpe/dsf-bpe-server/pom.xml b/dsf-bpe/dsf-bpe-server/pom.xml index 900c03637..4d6eb2310 100755 --- a/dsf-bpe/dsf-bpe-server/pom.xml +++ b/dsf-bpe/dsf-bpe-server/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-bpe-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-bpe/pom.xml b/dsf-bpe/pom.xml index 8193deec6..a1680293f 100755 --- a/dsf-bpe/pom.xml +++ b/dsf-bpe/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-common/dsf-common-auth/pom.xml b/dsf-common/dsf-common-auth/pom.xml index a1d4a944f..49ea910db 100644 --- a/dsf-common/dsf-common-auth/pom.xml +++ b/dsf-common/dsf-common-auth/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-common-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-common/dsf-common-config/pom.xml b/dsf-common/dsf-common-config/pom.xml index c5c11e5d8..5eefe95cc 100644 --- a/dsf-common/dsf-common-config/pom.xml +++ b/dsf-common/dsf-common-config/pom.xml @@ -8,7 +8,7 @@ dev.dsf dsf-common-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-common/dsf-common-documentation/pom.xml b/dsf-common/dsf-common-documentation/pom.xml index c59ad2711..d7fe7f8e9 100644 --- a/dsf-common/dsf-common-documentation/pom.xml +++ b/dsf-common/dsf-common-documentation/pom.xml @@ -6,6 +6,6 @@ dev.dsf dsf-common-pom - 1.1.0-SNAPSHOT + 1.1.0 \ No newline at end of file diff --git a/dsf-common/dsf-common-jetty/pom.xml b/dsf-common/dsf-common-jetty/pom.xml index 06cbdfd7a..4b3b875fa 100644 --- a/dsf-common/dsf-common-jetty/pom.xml +++ b/dsf-common/dsf-common-jetty/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-common-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-common/dsf-common-status/pom.xml b/dsf-common/dsf-common-status/pom.xml index 01d8ff2cf..44a7577dc 100644 --- a/dsf-common/dsf-common-status/pom.xml +++ b/dsf-common/dsf-common-status/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-common-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-common/pom.xml b/dsf-common/pom.xml index 75b5e6aef..ceda4256e 100644 --- a/dsf-common/pom.xml +++ b/dsf-common/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/dsf-fhir-auth/pom.xml b/dsf-fhir/dsf-fhir-auth/pom.xml index dac4b4e8f..07651595e 100644 --- a/dsf-fhir/dsf-fhir-auth/pom.xml +++ b/dsf-fhir/dsf-fhir-auth/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml index 5f3025bb3..62701736e 100755 --- a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml +++ b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/dsf-fhir-server-jetty/pom.xml b/dsf-fhir/dsf-fhir-server-jetty/pom.xml index f7a0243ce..9a74944af 100755 --- a/dsf-fhir/dsf-fhir-server-jetty/pom.xml +++ b/dsf-fhir/dsf-fhir-server-jetty/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/dsf-fhir-server/pom.xml b/dsf-fhir/dsf-fhir-server/pom.xml index ca3fd9e9d..5083a68fe 100755 --- a/dsf-fhir/dsf-fhir-server/pom.xml +++ b/dsf-fhir/dsf-fhir-server/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/dsf-fhir-validation/pom.xml b/dsf-fhir/dsf-fhir-validation/pom.xml index 0abaa0a61..dc656e045 100644 --- a/dsf-fhir/dsf-fhir-validation/pom.xml +++ b/dsf-fhir/dsf-fhir-validation/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 @@ -95,7 +95,7 @@ - diff --git a/dsf-fhir/dsf-fhir-webservice-client/pom.xml b/dsf-fhir/dsf-fhir-webservice-client/pom.xml index 97c674748..eabea2e37 100755 --- a/dsf-fhir/dsf-fhir-webservice-client/pom.xml +++ b/dsf-fhir/dsf-fhir-webservice-client/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/dsf-fhir-websocket-client/pom.xml b/dsf-fhir/dsf-fhir-websocket-client/pom.xml index 475c35d61..c2c054490 100755 --- a/dsf-fhir/dsf-fhir-websocket-client/pom.xml +++ b/dsf-fhir/dsf-fhir-websocket-client/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-fhir-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-fhir/pom.xml b/dsf-fhir/pom.xml index 75b412c0f..381924d37 100755 --- a/dsf-fhir/pom.xml +++ b/dsf-fhir/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-build-info-reader/pom.xml b/dsf-tools/dsf-tools-build-info-reader/pom.xml index 3a1da819a..03622647a 100644 --- a/dsf-tools/dsf-tools-build-info-reader/pom.xml +++ b/dsf-tools/dsf-tools-build-info-reader/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-bundle-generator/pom.xml b/dsf-tools/dsf-tools-bundle-generator/pom.xml index 1a55240df..3d86f5cfb 100755 --- a/dsf-tools/dsf-tools-bundle-generator/pom.xml +++ b/dsf-tools/dsf-tools-bundle-generator/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-db-migration/pom.xml b/dsf-tools/dsf-tools-db-migration/pom.xml index af0e4e763..dbdaa650c 100755 --- a/dsf-tools/dsf-tools-db-migration/pom.xml +++ b/dsf-tools/dsf-tools-db-migration/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml b/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml index 493096376..e11a49e39 100644 --- a/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml +++ b/dsf-tools/dsf-tools-docker-secrets-reader/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-documentation-generator/pom.xml b/dsf-tools/dsf-tools-documentation-generator/pom.xml index 0c5300f42..7c407a3ce 100644 --- a/dsf-tools/dsf-tools-documentation-generator/pom.xml +++ b/dsf-tools/dsf-tools-documentation-generator/pom.xml @@ -8,7 +8,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-proxy-test/pom.xml b/dsf-tools/dsf-tools-proxy-test/pom.xml index 9593b7eb1..eb794c755 100755 --- a/dsf-tools/dsf-tools-proxy-test/pom.xml +++ b/dsf-tools/dsf-tools-proxy-test/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/dsf-tools-test-data-generator/pom.xml b/dsf-tools/dsf-tools-test-data-generator/pom.xml index cbaf9db83..b9583af0c 100755 --- a/dsf-tools/dsf-tools-test-data-generator/pom.xml +++ b/dsf-tools/dsf-tools-test-data-generator/pom.xml @@ -6,7 +6,7 @@ dev.dsf dsf-tools-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/dsf-tools/pom.xml b/dsf-tools/pom.xml index 4c69237db..3842d16c1 100755 --- a/dsf-tools/pom.xml +++ b/dsf-tools/pom.xml @@ -7,7 +7,7 @@ dev.dsf dsf-pom - 1.1.0-SNAPSHOT + 1.1.0 diff --git a/pom.xml b/pom.xml index 0f812ca95..9fb57a2c0 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ dev.dsf dsf-pom - 1.1.0-SNAPSHOT + 1.1.0 pom