diff --git a/dsf-bpe/dsf-bpe-process-api-v1/pom.xml b/dsf-bpe/dsf-bpe-process-api-v1/pom.xml
index 50343d884..590d5aea3 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-bpe/dsf-bpe-server-jetty/pom.xml b/dsf-bpe/dsf-bpe-server-jetty/pom.xml
index 8e84e18a6..70ba7f1c9 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-bpe/dsf-bpe-server/pom.xml b/dsf-bpe/dsf-bpe-server/pom.xml
index 76a3ff107..65e045807 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/FhirConnectorImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/FhirConnectorImpl.java
index d53a85f7d..c61265a20 100644
--- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/FhirConnectorImpl.java
+++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/FhirConnectorImpl.java
@@ -228,7 +228,7 @@ private void connectWebsocket(Subscription subscription)
{
try
{
- WebsocketClient client = clientProvider.getLocalWebsocketClient(() -> connect(),
+ WebsocketClient client = clientProvider.getLocalWebsocketClient(this::connect,
subscription.getIdElement().getIdPart());
EventType eventType = toEventType(subscription.getChannel().getPayload());
@@ -260,7 +260,7 @@ private Void onError(Throwable t)
{
// no debug log, exception previously logged by retrieveWebsocketSubscription, loadNewResources and
// connectWebsocket methods
- logger.error("Error while loading existing {} resources and connecting websocket: {} - {}", resourceName,
+ logger.error("Error loading existing {} resources and connecting websocket: {} - {}", resourceName,
t.getClass().getName(), t.getMessage());
return null;
diff --git a/dsf-bpe/pom.xml b/dsf-bpe/pom.xml
index 7190b7ac2..58ee7676b 100755
--- a/dsf-bpe/pom.xml
+++ b/dsf-bpe/pom.xml
@@ -7,7 +7,7 @@
dev.dsf
dsf-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/dsf-common-auth/pom.xml b/dsf-common/dsf-common-auth/pom.xml
index fcc61758b..d3e5cdfbb 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/dsf-common-config/pom.xml b/dsf-common/dsf-common-config/pom.xml
index 195d3c4b4..28dae4c4a 100644
--- a/dsf-common/dsf-common-config/pom.xml
+++ b/dsf-common/dsf-common-config/pom.xml
@@ -6,7 +6,7 @@
dev.dsf
dsf-common-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/dsf-common-db/pom.xml b/dsf-common/dsf-common-db/pom.xml
index 87f496fb2..ab0f52280 100644
--- a/dsf-common/dsf-common-db/pom.xml
+++ b/dsf-common/dsf-common-db/pom.xml
@@ -6,7 +6,7 @@
dev.dsf
dsf-common-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/dsf-common-documentation/pom.xml b/dsf-common/dsf-common-documentation/pom.xml
index 637a4bc47..c0ee493fd 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.5.3-SNAPSHOT
+ 1.6.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 975725f9d..121832aa3 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java
index a68f0302f..f401081b5 100644
--- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java
+++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java
@@ -429,7 +429,7 @@ private Duration oidcClientConnectTimeout()
private Proxy oidcClientProxy()
{
ProxyConfig config = new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy);
- if (config.getUrl() != null && config.isNoProxyUrl(oidcProviderRealmBaseUrl))
+ if (config.getUrl() != null && !config.isNoProxyUrl(oidcProviderRealmBaseUrl))
{
try
{
diff --git a/dsf-common/dsf-common-status/pom.xml b/dsf-common/dsf-common-status/pom.xml
index 0dfca7399..b560bb5f0 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/dsf-common-ui/pom.xml b/dsf-common/dsf-common-ui/pom.xml
index 821a7a046..cfd3015e8 100644
--- a/dsf-common/dsf-common-ui/pom.xml
+++ b/dsf-common/dsf-common-ui/pom.xml
@@ -6,7 +6,7 @@
dev.dsf
dsf-common-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-common/pom.xml b/dsf-common/pom.xml
index 4197bced2..b699c5af2 100644
--- a/dsf-common/pom.xml
+++ b/dsf-common/pom.xml
@@ -7,7 +7,7 @@
dev.dsf
dsf-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-docker-test-setup-3dic-ttp/docker-compose.yml b/dsf-docker-test-setup-3dic-ttp/docker-compose.yml
index 3fd3bd014..5895c6bd5 100644
--- a/dsf-docker-test-setup-3dic-ttp/docker-compose.yml
+++ b/dsf-docker-test-setup-3dic-ttp/docker-compose.yml
@@ -1,7 +1,7 @@
version: '3.8'
services:
proxy:
- image: nginx:1.23
+ image: nginx:1.27
restart: "no"
ports:
- 127.0.0.1:443:443
@@ -106,6 +106,15 @@ services:
--spi-truststore-file-password=password
--spi-truststore-file-hostname-verification-policy=STRICT
+ forward-proxy:
+ build: ./forward-proxy
+ restart: "no"
+ environment:
+ TZ: Europe/Berlin
+ networks:
+ forward-proxy:
+ internet:
+
dic1-fhir:
build: ../dsf-fhir/dsf-fhir-server-jetty/docker
image: datasharingframework/fhir
@@ -518,15 +527,21 @@ services:
DEV_DSF_SERVER_AUTH_OIDC_PROVIDER_CLIENT_TRUST_SERVER_CERTIFICATE_CAS: /run/secrets/app_server_trust_certificates.pem
DEV_DSF_SERVER_AUTH_OIDC_CLIENT_ID: dic1-bpe
DEV_DSF_SERVER_AUTH_OIDC_CLIENT_SECRET: ytqFCErw9GfhVUrrM8xc0Grbu4r7qGig
+ DEV_DSF_PROXY_URL: http://forward-proxy:8080
+ DEV_DSF_PROXY_USERNAME: proxy_user
+ DEV_DSF_PROXY_PASSWORD: proxy_password
+ DEV_DSF_PROXY_NOPROXY: keycloak
networks:
dic1-bpe-frontend:
ipv4_address: 172.20.0.35
dic1-bpe-backend:
internet:
+ forward-proxy:
depends_on:
- db
- dic1-fhir
- keycloak
+ - forward-proxy
dic2-bpe:
build: ../dsf-bpe/dsf-bpe-server-jetty/docker
@@ -944,6 +959,7 @@ networks:
- subnet: 172.20.0.56/29
ttp-bpe-backend:
internet:
+ forward-proxy:
volumes:
db-data:
diff --git a/dsf-docker-test-setup-3dic-ttp/forward-proxy/Dockerfile b/dsf-docker-test-setup-3dic-ttp/forward-proxy/Dockerfile
new file mode 100644
index 000000000..bd4f3af20
--- /dev/null
+++ b/dsf-docker-test-setup-3dic-ttp/forward-proxy/Dockerfile
@@ -0,0 +1,4 @@
+FROM alpine:3.20
+RUN apk add --no-cache tinyproxy
+COPY tinyproxy.conf /etc/tinyproxy/tinyproxy.conf
+CMD ["tinyproxy", "-d"]
\ No newline at end of file
diff --git a/dsf-docker-test-setup-3dic-ttp/forward-proxy/tinyproxy.conf b/dsf-docker-test-setup-3dic-ttp/forward-proxy/tinyproxy.conf
new file mode 100644
index 000000000..e08799ca1
--- /dev/null
+++ b/dsf-docker-test-setup-3dic-ttp/forward-proxy/tinyproxy.conf
@@ -0,0 +1,6 @@
+User tinyproxy
+Group tinyproxy
+Port 8080
+Timeout 100
+LogLevel Connect
+BasicAuth proxy_user proxy_password
\ No newline at end of file
diff --git a/dsf-docker-test-setup-3dic-ttp/proxy/conf.d/default.conf b/dsf-docker-test-setup-3dic-ttp/proxy/conf.d/default.conf
deleted file mode 100644
index b460be080..000000000
--- a/dsf-docker-test-setup-3dic-ttp/proxy/conf.d/default.conf
+++ /dev/null
@@ -1,46 +0,0 @@
-server {
- listen 80;
- listen [::]:80;
- server_name localhost;
-
- #charset koi8-r;
- #access_log /var/log/nginx/host.access.log main;
-
- location / {
- root /usr/share/nginx/html;
- index index.html index.htm;
- }
-
- #error_page 404 /404.html;
-
- # redirect server error pages to the static page /50x.html
- #
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- root /usr/share/nginx/html;
- }
-
- # proxy the PHP scripts to Apache listening on 127.0.0.1:80
- #
- #location ~ \.php$ {
- # proxy_pass http://127.0.0.1;
- #}
-
- # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
- #
- #location ~ \.php$ {
- # root html;
- # fastcgi_pass 127.0.0.1:9000;
- # fastcgi_index index.php;
- # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
- # include fastcgi_params;
- #}
-
- # deny access to .htaccess files, if Apache's document root
- # concurs with nginx's one
- #
- #location ~ /\.ht {
- # deny all;
- #}
-}
-
diff --git a/dsf-docker-test-setup-3dic-ttp/proxy/nginx.conf b/dsf-docker-test-setup-3dic-ttp/proxy/nginx.conf
index 04ee80c96..d9aece023 100644
--- a/dsf-docker-test-setup-3dic-ttp/proxy/nginx.conf
+++ b/dsf-docker-test-setup-3dic-ttp/proxy/nginx.conf
@@ -1,16 +1,6 @@
-
-user nginx;
-worker_processes 1;
-
-error_log /var/log/nginx/error.log warn;
-pid /var/run/nginx.pid;
-
-
events {
- worker_connections 1024;
}
-
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
@@ -19,15 +9,6 @@ http {
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
- access_log /var/log/nginx/access.log main;
-
- sendfile on;
- #tcp_nopush on;
-
- keepalive_timeout 65;
-
- #gzip on;
-
ssl_certificate /run/secrets/proxy_certificate_and_int_cas.pem;
ssl_certificate_key /run/secrets/proxy_certificate_private_key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
@@ -45,4 +26,4 @@ http {
}
include /etc/nginx/conf.d/*.conf;
-}
+}
\ No newline at end of file
diff --git a/dsf-fhir/dsf-fhir-auth/pom.xml b/dsf-fhir/dsf-fhir-auth/pom.xml
index f3388a0c0..159d12a0b 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-fhir/dsf-fhir-rest-adapter/pom.xml b/dsf-fhir/dsf-fhir-rest-adapter/pom.xml
index 3b732a35a..9843f2377 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ReferenceExtractorImpl.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ReferenceExtractorImpl.java
index 9af14321d..d6dc077a9 100644
--- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ReferenceExtractorImpl.java
+++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ReferenceExtractorImpl.java
@@ -10,6 +10,7 @@
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.BackboneElement;
import org.hl7.fhir.r4.model.Binary;
+import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.CareTeam;
import org.hl7.fhir.r4.model.ClaimResponse;
@@ -80,18 +81,25 @@ public class ReferenceExtractorImpl implements ReferenceExtractor
private Function toResourceReferenceFromReference(String referenceLocation,
Class extends Resource>... referenceTypes)
{
- return ref -> new ResourceReference(referenceLocation, ref, referenceTypes);
+ return reference -> new ResourceReference(referenceLocation, reference, referenceTypes);
}
private Function toResourceReferenceFromRelatedArtifact(
String relatedArtifactLocation)
{
- return rel -> new ResourceReference(relatedArtifactLocation, rel);
+ return relatedArtifact -> new ResourceReference(relatedArtifactLocation, relatedArtifact);
}
private Function toResourceReferenceFromAttachment(String attachmentLocation)
{
- return rel -> new ResourceReference(attachmentLocation, rel);
+ return attachment -> new ResourceReference(attachmentLocation, attachment);
+ }
+
+ @SafeVarargs
+ private Function toResourceReferenceFromCanonical(String canonicalLocation,
+ Class extends Resource>... referenceTypes)
+ {
+ return canonical -> new ResourceReference(canonicalLocation, canonical, referenceTypes);
}
@SafeVarargs
@@ -260,6 +268,50 @@ private Stream getReferences(E ba
.map(toResourceReferenceFromReference(referenceLocation, referenceTypes)) : Stream.empty();
}
+ @SafeVarargs
+ private Stream getCanonical(R resource, Predicate hasCanonical,
+ Function getCanonical, String canonicalLocation,
+ Class extends Resource>... canonicalTypes)
+ {
+ return hasCanonical.test(resource) ? Stream.of(getCanonical.apply(resource))
+ .map(toResourceReferenceFromCanonical(canonicalLocation, canonicalTypes)) : Stream.empty();
+ }
+
+ @SafeVarargs
+ private Stream getCanonicals(R resource, Predicate hasCanonical,
+ Function> getCanonicals, String canonicalLocation,
+ Class extends Resource>... canonicalTypes)
+ {
+ return hasCanonical.test(resource) ? Stream.of(getCanonicals.apply(resource)).flatMap(List::stream)
+ .map(toResourceReferenceFromCanonical(canonicalLocation, canonicalTypes)) : Stream.empty();
+ }
+
+ @SafeVarargs
+ private Stream getCanonical(E backboneElement,
+ Predicate hasCanonical, Function getCanonical, String canonicalLocation,
+ Class extends Resource>... canonicalTypes)
+ {
+ return hasCanonical.test(backboneElement) ? Stream.of(getCanonical.apply(backboneElement))
+ .map(toResourceReferenceFromCanonical(canonicalLocation, canonicalTypes)) : Stream.empty();
+ }
+
+ @SafeVarargs
+ private Stream getBackboneElementsCanonical(
+ R resource, Predicate hasBackboneElements, Function> getBackboneElements,
+ Predicate hasCanonical, Function getCanonical, String canonicalLocation,
+ Class extends Resource>... canonicalTypes)
+ {
+ if (hasBackboneElements.test(resource))
+ {
+ List backboneElements = getBackboneElements.apply(resource);
+ return backboneElements.stream()
+ .map(e -> getCanonical(e, hasCanonical, getCanonical, canonicalLocation, canonicalTypes))
+ .flatMap(Function.identity());
+ }
+ else
+ return Stream.empty();
+ }
+
private Stream getExtensionReferences(DomainResource resource)
{
var extensions = resource.getExtension().stream().filter(e -> e.getValue() instanceof Reference)
@@ -447,9 +499,14 @@ public Stream getReferences(CodeSystem resource)
if (resource == null)
return Stream.empty();
+ var supplements = getCanonical(resource, CodeSystem::hasSupplementsElement, CodeSystem::getSupplementsElement,
+ "CodeSystem.supplements", CodeSystem.class);
+ var valueSet = getCanonical(resource, CodeSystem::hasValueSetElement, CodeSystem::getValueSetElement,
+ "CodeSystem.valueSet", ValueSet.class);
+
var extensionReferences = getExtensionReferences(resource);
- return extensionReferences;
+ return concat(valueSet, supplements, extensionReferences);
}
@Override
@@ -588,6 +645,8 @@ public Stream getReferences(Measure resource)
if (resource == null)
return Stream.empty();
+ var library = getCanonicals(resource, Measure::hasLibrary, Measure::getLibrary, "Measure.library",
+ Library.class);
var subject = getReference(resource, Measure::hasSubjectReference, Measure::getSubjectReference,
"Measure.subject", Group.class);
var relatedArtifacts = getRelatedArtifacts(resource, Measure::hasRelatedArtifact, Measure::getRelatedArtifact,
@@ -595,7 +654,7 @@ public Stream getReferences(Measure resource)
var extensionReferences = getExtensionReferences(resource);
- return concat(subject, relatedArtifacts, extensionReferences);
+ return concat(library, subject, relatedArtifacts, extensionReferences);
}
@Override
@@ -604,6 +663,8 @@ public Stream getReferences(MeasureReport resource)
if (resource == null)
return Stream.empty();
+ var measure = getCanonical(resource, MeasureReport::hasMeasureElement, MeasureReport::getMeasureElement,
+ "MeasureReport.measure", Measure.class);
var subject = getReference(resource, MeasureReport::hasSubject, MeasureReport::getSubject,
"MeasureReport.subject", Patient.class, Practitioner.class, PractitionerRole.class, Location.class,
Device.class, RelatedPerson.class, Group.class);
@@ -627,7 +688,8 @@ public Stream getReferences(MeasureReport resource)
var extensionReferences = getExtensionReferences(resource);
- return concat(subject, reporter, subjectResults1, subjectResults2, evaluatedResource, extensionReferences);
+ return concat(measure, subject, reporter, subjectResults1, subjectResults2, evaluatedResource,
+ extensionReferences);
}
@Override
@@ -785,20 +847,24 @@ public Stream getReferences(Questionnaire resource)
if (resource == null)
return Stream.empty();
+ var derivedFrom = getCanonicals(resource, Questionnaire::hasDerivedFrom, Questionnaire::getDerivedFrom,
+ "Questionnaire.derivedFrom", Questionnaire.class);
var enableWhen = getBackboneElements2Reference(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasEnableWhen,
Questionnaire.QuestionnaireItemComponent::getEnableWhen,
Questionnaire.QuestionnaireItemEnableWhenComponent::hasAnswerReference,
Questionnaire.QuestionnaireItemEnableWhenComponent::getAnswerReference,
"Questionnaire.item.enableWhen.answerReference");
-
var answerOption = getBackboneElements2Reference(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasAnswerOption,
Questionnaire.QuestionnaireItemComponent::getAnswerOption,
Questionnaire.QuestionnaireItemAnswerOptionComponent::hasValueReference,
Questionnaire.QuestionnaireItemAnswerOptionComponent::getValueReference,
"Questionnaire.item.answerOption.valueReference");
-
+ var answerValueSet = getBackboneElementsCanonical(resource, Questionnaire::hasItem, Questionnaire::getItem,
+ Questionnaire.QuestionnaireItemComponent::hasAnswerValueSetElement,
+ Questionnaire.QuestionnaireItemComponent::getAnswerValueSetElement, "Questionnaire.item.answerValueSet",
+ ValueSet.class);
var initial = getBackboneElements2Reference(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasInitial,
Questionnaire.QuestionnaireItemComponent::getInitial,
@@ -808,7 +874,7 @@ public Stream getReferences(Questionnaire resource)
var extensionReferences = getExtensionReferences(resource);
- return concat(enableWhen, answerOption, initial, extensionReferences);
+ return concat(derivedFrom, enableWhen, answerOption, answerValueSet, initial, extensionReferences);
}
@Override
@@ -820,26 +886,24 @@ public Stream getReferences(QuestionnaireResponse resource)
var author = getReference(resource, QuestionnaireResponse::hasAuthor, QuestionnaireResponse::getAuthor,
"QuestionnaireResponse.author", Device.class, Organization.class, Patient.class, Practitioner.class,
PractitionerRole.class, RelatedPerson.class);
-
var basedOn = getReferences(resource, QuestionnaireResponse::hasBasedOn, QuestionnaireResponse::getBasedOn,
"QuestionnaireResponse.basedOn", CarePlan.class, ServiceRequest.class);
-
var encounter = getReference(resource, QuestionnaireResponse::hasEncounter, QuestionnaireResponse::getEncounter,
"QuestionnaireResponse.encounter", Encounter.class);
-
var partOf = getReferences(resource, QuestionnaireResponse::hasPartOf, QuestionnaireResponse::getPartOf,
"QuestionnaireResponse.partOf", Observation.class, Procedure.class);
-
+ var questionnaire = getCanonical(resource, QuestionnaireResponse::hasQuestionnaireElement,
+ QuestionnaireResponse::getQuestionnaireElement, "QuestionnaireResponse.questionnaire",
+ Questionnaire.class);
var source = getReference(resource, QuestionnaireResponse::hasSource, QuestionnaireResponse::getSource,
"QuestionnaireResponse.source", Patient.class, Practitioner.class, PractitionerRole.class,
RelatedPerson.class);
-
var subject = getReference(resource, QuestionnaireResponse::hasSubject, QuestionnaireResponse::getSubject,
"QuestionnaireResponse.subject");
var extensionReferences = getExtensionReferences(resource);
- return concat(author, basedOn, encounter, partOf, source, subject, extensionReferences);
+ return concat(author, basedOn, encounter, partOf, questionnaire, source, subject, extensionReferences);
}
@Override
@@ -876,9 +940,13 @@ public Stream getReferences(StructureDefinition resource)
if (resource == null)
return Stream.empty();
+ var baseDefinition = getCanonical(resource, StructureDefinition::hasBaseDefinitionElement,
+ StructureDefinition::getBaseDefinitionElement, "StructureDefinition.baseDefinition",
+ StructureDefinition.class);
+
var extensionReferences = getExtensionReferences(resource);
- return extensionReferences;
+ return concat(baseDefinition, extensionReferences);
}
@Override
@@ -899,23 +967,25 @@ public Stream getReferences(Task resource)
return Stream.empty();
var basedOns = getReferences(resource, Task::hasBasedOn, Task::getBasedOn, "Task.basedOn");
- var partOfs = getReferences(resource, Task::hasPartOf, Task::getPartOf, "Task.partOf", Task.class);
- var focus = getReference(resource, Task::hasFocus, Task::getFocus, "Task.focus");
- var forRef = getReference(resource, Task::hasFor, Task::getFor, "Task.for");
var encounter = getReference(resource, Task::hasEncounter, Task::getEncounter, "Task.encounter",
Encounter.class);
- var requester = getReference(resource, Task::hasRequester, Task::getRequester, "Task.requester", Device.class,
- Organization.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class);
+ var focus = getReference(resource, Task::hasFocus, Task::getFocus, "Task.focus");
+ var forRef = getReference(resource, Task::hasFor, Task::getFor, "Task.for");
+ var instantiatesCanonical = getCanonical(resource, Task::hasInstantiatesCanonicalElement,
+ Task::getInstantiatesCanonicalElement, "Task.instantiatesCanonical", ActivityDefinition.class);
+ var insurance = getReferences(resource, Task::hasInsurance, Task::getInsurance, "Task.insurance",
+ Coverage.class, ClaimResponse.class);
+ var location = getReference(resource, Task::hasLocation, Task::getLocation, "Task.location", Location.class);
var owner = getReference(resource, Task::hasOwner, Task::getOwner, "Task.owner", Practitioner.class,
PractitionerRole.class, Organization.class, CareTeam.class, HealthcareService.class, Patient.class,
Device.class, RelatedPerson.class);
- var location = getReference(resource, Task::hasLocation, Task::getLocation, "Task.location", Location.class);
+ var partOfs = getReferences(resource, Task::hasPartOf, Task::getPartOf, "Task.partOf", Task.class);
var reasonReference = getReference(resource, Task::hasReasonReference, Task::getReasonReference,
"Task.reasonReference");
- var insurance = getReferences(resource, Task::hasInsurance, Task::getInsurance, "Task.insurance",
- Coverage.class, ClaimResponse.class);
var relevanteHistories = getReferences(resource, Task::hasRelevantHistory, Task::getRelevantHistory,
"Task.relevantHistory", Provenance.class);
+ var requester = getReference(resource, Task::hasRequester, Task::getRequester, "Task.requester", Device.class,
+ Organization.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class);
var restrictionRecipiets = getBackboneElementReferences(resource, Task::hasRestriction, Task::getRestriction,
Task.TaskRestrictionComponent::hasRecipient, Task.TaskRestrictionComponent::getRecipient,
"Task.restriction.recipient", Patient.class, Practitioner.class, PractitionerRole.class,
@@ -925,8 +995,8 @@ public Stream getReferences(Task resource)
var outputReferences = getOutputReferences(resource);
var extensionReferences = getExtensionReferences(resource);
- return concat(basedOns, partOfs, focus, forRef, encounter, requester, owner, location, reasonReference,
- insurance, relevanteHistories, restrictionRecipiets, inputReferences, outputReferences,
+ return concat(basedOns, encounter, focus, forRef, instantiatesCanonical, insurance, location, owner, partOfs,
+ reasonReference, relevanteHistories, requester, restrictionRecipiets, inputReferences, outputReferences,
extensionReferences);
}
diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java
index 7dccd6930..d4015defd 100644
--- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java
+++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java
@@ -1,7 +1,6 @@
package dev.dsf.fhir.service;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
@@ -11,6 +10,7 @@
import java.util.regex.Pattern;
import org.hl7.fhir.r4.model.Attachment;
+import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.RelatedArtifact;
@@ -143,42 +143,54 @@ public enum ReferenceType
/**
* unknown url in Attachment
*/
- ATTACHMENT_UNKNOWN_URL
+ ATTACHMENT_UNKNOWN_URL,
+ /**
+ * canoncial reference
+ */
+ CANONICAL
}
private final String location;
private final Reference reference;
private final RelatedArtifact relatedArtifact;
private final Attachment attachment;
+ private final CanonicalType canonical;
private final List> referenceTypes = new ArrayList<>();
@SafeVarargs
public ResourceReference(String location, Reference reference, Class extends Resource>... referenceTypes)
{
- this(location, reference, null, null, Arrays.asList(referenceTypes));
+ this(location, reference, null, null, null, List.of(referenceTypes));
}
public ResourceReference(String location, RelatedArtifact relatedArtifact)
{
- this(location, null, relatedArtifact, null, Collections.emptyList());
+ this(location, null, relatedArtifact, null, null, Collections.emptyList());
}
public ResourceReference(String location, Attachment attachment)
{
- this(location, null, null, attachment, Collections.emptyList());
+ this(location, null, null, attachment, null, Collections.emptyList());
+ }
+
+ @SafeVarargs
+ public ResourceReference(String location, CanonicalType canonical, Class extends Resource>... referenceTypes)
+ {
+ this(location, null, null, null, canonical, List.of(referenceTypes));
}
private ResourceReference(String location, Reference reference, RelatedArtifact relatedArtifact,
- Attachment attachment, Collection> referenceTypes)
+ Attachment attachment, CanonicalType canonical, Collection> referenceTypes)
{
this.location = location;
- if (reference == null && relatedArtifact == null && attachment == null)
- throw new IllegalArgumentException("Either reference, relatedArtifact or attachment expected");
+ if (reference == null && relatedArtifact == null && attachment == null && canonical == null)
+ throw new IllegalArgumentException("Either reference, relatedArtifact, attachment or canonical expected");
this.reference = reference;
this.relatedArtifact = relatedArtifact;
this.attachment = attachment;
+ this.canonical = canonical;
if (referenceTypes != null)
this.referenceTypes.addAll(referenceTypes);
@@ -214,6 +226,16 @@ public Attachment getAttachment()
return attachment;
}
+ public boolean hasCanonical()
+ {
+ return canonical != null;
+ }
+
+ public CanonicalType getCanonical()
+ {
+ return canonical;
+ }
+
public String getValue()
{
if (hasReference())
@@ -222,8 +244,10 @@ else if (hasRelatedArtifact())
return relatedArtifact.getUrl();
else if (hasAttachment())
return attachment.getUrl();
+ else if (hasCanonical())
+ return canonical.getValue();
else
- throw new IllegalArgumentException("reference, related artefact or attachment not set");
+ throw new IllegalArgumentException("reference, related artefact, attachment or canonical not set");
}
public List> getReferenceTypes()
@@ -336,8 +360,15 @@ else if (reference.hasType() && reference.hasIdentifier() && reference.getIdenti
return ReferenceType.UNKNOWN;
}
+ else if (canonical != null)
+ {
+ if (canonical.hasValue())
+ return ReferenceType.CANONICAL;
+ else
+ return ReferenceType.UNKNOWN;
+ }
else
- throw new IllegalStateException("Either reference or relatedArtifact expected");
+ throw new IllegalStateException("Either reference, related artefact, attachment or canonical expected");
}
public String getLocation()
diff --git a/dsf-fhir/dsf-fhir-server-jetty/pom.xml b/dsf-fhir/dsf-fhir-server-jetty/pom.xml
index 5340dd4f7..2f4aa481c 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-fhir/dsf-fhir-server/pom.xml b/dsf-fhir/dsf-fhir-server/pom.xml
index c2ab309ba..3a3a95258 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
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 b2343cc27..4e978a6e3 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
@@ -335,12 +335,6 @@ protected final Optional createIfLiteralInternalOrLogicalRefe
return Optional.empty();
}
- protected final Optional resolveReference(Connection connection, Identity identity,
- Optional reference)
- {
- return reference.flatMap(ref -> referenceResolver.resolveReference(identity, ref, connection));
- }
-
@Override
public Optional reasonPermanentDeleteAllowed(Identity identity, R oldResource)
{
diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java
index e0fb25748..a47fb36c4 100644
--- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java
+++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java
@@ -1,7 +1,6 @@
package dev.dsf.fhir.authorization;
import java.sql.Connection;
-import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
@@ -9,7 +8,6 @@
import java.util.Optional;
import java.util.stream.Collectors;
-import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent;
import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
@@ -89,12 +87,8 @@ private Optional newResourceOk(QuestionnaireResponse newResource,
getItemAndValidate(newResource, CODESYSTEM_DSF_BPMN_USER_TASK_VALUE_USER_TASK_ID, errors);
- String questionnaireUrlAndVersion = newResource.getQuestionnaire();
- if (!questionnaireExists(questionnaireUrlAndVersion))
- {
- errors.add(
- "Questionnaire ressource referenced via canonical QuestionnaireResponse.questionnaire does not exist");
- }
+ if (!newResource.hasQuestionnaire())
+ errors.add("QuestionnaireResponse.questionnaire missing");
if (errors.isEmpty())
return Optional.empty();
@@ -149,23 +143,6 @@ private Optional getItemAndValidate(QuestionnaireResponse newResource, S
return Optional.of(value.getValue());
}
- private boolean questionnaireExists(String questionnaireUrlAndVersion)
- {
- try
- {
- Optional questionnaire = daoProvider.getQuestionnaireDao()
- .readByUrlAndVersion(questionnaireUrlAndVersion);
-
- return questionnaire.isPresent();
- }
- catch (SQLException e)
- {
- logger.warn("Could not check questionnaire with url|version '{}' for questionnaire-response - {}",
- questionnaireUrlAndVersion, e.getMessage());
- throw new RuntimeException(e);
- }
- }
-
@Override
public Optional reasonReadAllowed(Connection connection, Identity identity,
QuestionnaireResponse existingResource)
diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java
index 21fea87ae..1890fafb4 100644
--- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java
+++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java
@@ -200,7 +200,7 @@ public void checkReferences(Map idTranslationTable, Connection c
Predicate checkReference) throws WebApplicationException
{
referenceExtractor.getReferences(resource).filter(checkReference)
- .filter(ref -> referenceResolver.referenceCanBeChecked(ref, connection)).forEach(ref ->
+ .filter(ref -> referenceResolver.referenceCanBeResolved(ref, connection)).forEach(ref ->
{
Optional outcome = checkReference(ref, connection);
if (outcome.isPresent())
@@ -224,6 +224,9 @@ private Optional checkReference(ResourceReference reference, C
case LOGICAL -> referenceResolver.checkLogicalReference(identity, resource, reference, connection, index);
+ case CANONICAL ->
+ referenceResolver.checkCanonicalReference(identity, resource, reference, connection, index);
+
// unknown URLs to non FHIR servers in related artifacts must not be checked
case RELATED_ARTEFACT_UNKNOWN_URL, ATTACHMENT_UNKNOWN_URL -> Optional.empty();
diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolver.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolver.java
index d601c234f..048d9e8ca 100755
--- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolver.java
+++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolver.java
@@ -35,15 +35,6 @@ public interface ReferenceResolver
*/
Optional resolveReference(Identity identity, ResourceReference reference, Connection connection);
- /**
- * @param reference
- * not null
- * @param connection
- * not null
- * @return true
if the {@link ResourceReference} can be checked
- */
- boolean referenceCanBeChecked(ResourceReference reference, Connection connection);
-
/**
* @param resource
* not null
@@ -160,4 +151,40 @@ Optional checkLogicalReference(Identity identity, Resource res
Optional checkLogicalReference(Identity identity, Resource resource,
ResourceReference resourceReference, Connection connection, Integer bundleIndex)
throws IllegalArgumentException;
+
+ /**
+ * @param identity
+ * not null
+ * @param resource
+ * not null
+ * @param reference
+ * not null
+ * @param connection
+ * not null
+ * @return {@link Optional#empty()} if the reference check was successful
+ * @throws IllegalArgumentException
+ * if the reference is not of type {@link ResourceReference.ReferenceType#CANONICAL}
+ * @see ResourceReference#getType(String)
+ */
+ Optional checkCanonicalReference(Identity identity, Resource resource,
+ ResourceReference reference, Connection connection) throws IllegalArgumentException;
+
+ /**
+ * @param identity
+ * not null
+ * @param resource
+ * not null
+ * @param reference
+ * not null
+ * @param connection
+ * not null
+ * @param bundleIndex
+ * may be null
+ * @return {@link Optional#empty()} if the reference check was successful
+ * @throws IllegalArgumentException
+ * if the reference is not of type {@link ResourceReference.ReferenceType#CANONICAL}
+ * @see ResourceReference#getType(String)
+ */
+ Optional checkCanonicalReference(Identity identity, Resource resource,
+ ResourceReference reference, Connection connection, Integer bundleIndex) throws IllegalArgumentException;
}
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 b8f59320c..3e5a64795 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
@@ -2,7 +2,6 @@
import java.sql.Connection;
import java.util.Arrays;
-import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
@@ -41,6 +40,9 @@ public class ReferenceResolverImpl implements ReferenceResolver, InitializingBea
{
private static final Logger logger = LoggerFactory.getLogger(ReferenceResolverImpl.class);
+ private static final String QUESTIONNAIRE_RESPONSE_QUESTIONNAIRE = "QuestionnaireResponse.questionnaire";
+ private static final String TASK_INSTANTIATES_CANONICAL = "Task.instantiatesCanonical";
+
private final String serverBase;
private final DaoProvider daoProvider;
private final ResponseGenerator responseGenerator;
@@ -72,12 +74,6 @@ public void afterPropertiesSet() throws Exception
@Override
public boolean referenceCanBeResolved(ResourceReference reference, Connection connection)
- {
- return referenceCanBeChecked(reference, connection);
- }
-
- @Override
- public boolean referenceCanBeChecked(ResourceReference reference, Connection connection)
{
Objects.requireNonNull(reference, "reference");
Objects.requireNonNull(connection, "connection");
@@ -85,36 +81,19 @@ public boolean referenceCanBeChecked(ResourceReference reference, Connection con
return switch (reference.getType(serverBase))
{
case LITERAL_EXTERNAL, RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL ->
- literalExternalReferenceCanBeCheckedAndResolved(reference);
+ clientProvider.endpointExists(reference.getServerBase(serverBase));
- case LOGICAL -> logicalReferenceCanBeCheckedAndResolved(reference, connection);
+ case LOGICAL -> exceptionHandler.handleSqlException(
+ () -> daoProvider.getNamingSystemDao().existsWithUniqueIdUriEntryResolvable(connection,
+ reference.getReference().getIdentifier().getSystem()));
+
+ case CANONICAL -> QUESTIONNAIRE_RESPONSE_QUESTIONNAIRE.equals(reference.getLocation())
+ || TASK_INSTANTIATES_CANONICAL.equals(reference.getLocation());
default -> true;
};
}
- private boolean logicalReferenceCanBeCheckedAndResolved(ResourceReference reference, Connection connection)
- {
- if (!ReferenceType.LOGICAL.equals(reference.getType(serverBase)))
- throw new IllegalArgumentException("Not a logical reference");
-
- return exceptionHandler.handleSqlException(
- () -> daoProvider.getNamingSystemDao().existsWithUniqueIdUriEntryResolvable(connection,
- reference.getReference().getIdentifier().getSystem()));
- }
-
- private boolean literalExternalReferenceCanBeCheckedAndResolved(ResourceReference reference)
- {
- if (!EnumSet.of(ReferenceType.LITERAL_EXTERNAL, ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL,
- ReferenceType.ATTACHMENT_LITERAL_EXTERNAL_URL).contains(reference.getType(serverBase)))
- {
- throw new IllegalArgumentException(
- "Not a literal external reference, related artifact literal external url or attachment literal external url");
- }
-
- return clientProvider.endpointExists(reference.getServerBase(serverBase));
- }
-
@Override
public Optional resolveReference(Identity identity, ResourceReference reference, Connection connection)
{
@@ -131,6 +110,7 @@ public Optional resolveReference(Identity identity, ResourceReference
case CONDITIONAL, RELATED_ARTEFACT_CONDITIONAL_URL, ATTACHMENT_CONDITIONAL_URL ->
resolveConditionalReference(identity, reference, connection);
case LOGICAL -> resolveLogicalReference(identity, reference, connection);
+
default -> throw new IllegalArgumentException("Reference of type " + type + " not supported");
};
}
@@ -240,7 +220,9 @@ private Optional resolveConditionalReference(Identity identity, Resour
Connection connection)
{
Objects.requireNonNull(reference, "reference");
- throwIfReferenceTypeUnexpected(reference.getType(serverBase), ReferenceType.CONDITIONAL,
+
+ ReferenceType referenceType = reference.getType(serverBase);
+ throwIfReferenceTypeUnexpected(referenceType, ReferenceType.CONDITIONAL,
ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL, ReferenceType.ATTACHMENT_CONDITIONAL_URL);
String referenceValue = reference.getValue();
@@ -271,7 +253,7 @@ private Optional resolveConditionalReference(Identity identity, Resour
return Optional.empty();
}
- return search(identity, connection, d, reference, condition.getQueryParams(), true);
+ return search(identity, connection, d, reference, condition.getQueryParams(), referenceType);
}
}
@@ -302,14 +284,14 @@ private Optional resolveLogicalReference(Identity identity, ResourceRe
}
Identifier targetIdentifier = reference.getReference().getIdentifier();
- return search(identity, connection, d, reference, Map.of("identifier",
- Collections.singletonList(targetIdentifier.getSystem() + "|" + targetIdentifier.getValue())), true);
+ return search(identity, connection, d, reference,
+ Map.of("identifier", List.of(targetIdentifier.getSystem() + "|" + targetIdentifier.getValue())),
+ ReferenceType.LOGICAL);
}
}
private Optional search(Identity identity, Connection connection, ResourceDao> referenceTargetDao,
- ResourceReference resourceReference, Map> queryParameters,
- boolean logicalNotConditional)
+ ResourceReference resourceReference, Map> queryParameters, ReferenceType referenceType)
{
if (Arrays.stream(SearchQuery.STANDARD_PARAMETERS).anyMatch(queryParameters::containsKey))
{
@@ -333,11 +315,17 @@ private Optional search(Identity identity, Connection connection, Reso
String unsupportedQueryParametersString = unsupportedQueryParameters.stream()
.map(SearchQueryParameterError::toString).collect(Collectors.joining("; "));
- logger.warn("{} reference {} at {} in resource contains unsupported queryparameter{} {}",
- logicalNotConditional ? "Logical" : "Conditional", queryParameters, resourceReference.getLocation(),
- unsupportedQueryParameters.size() != 1 ? "s" : "", unsupportedQueryParametersString);
-
- return Optional.empty();
+ if (EnumSet.of(ReferenceType.CONDITIONAL, ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL,
+ ReferenceType.ATTACHMENT_CONDITIONAL_URL).contains(referenceType))
+ {
+ logger.warn("Conditional reference {} at {} in resource contains unsupported queryparameter{} {}",
+ queryParameters, resourceReference.getLocation(),
+ unsupportedQueryParameters.size() != 1 ? "s" : "", unsupportedQueryParametersString);
+ return Optional.empty();
+ }
+ else
+ throw new IllegalStateException("Unable to search for " + referenceTargetDao.getResourceTypeName()
+ + ": Unsupported query parameters");
}
PartialResult> result = exceptionHandler.handleSqlException(() ->
@@ -348,39 +336,26 @@ private Optional search(Identity identity, Connection connection, Reso
return referenceTargetDao.searchWithTransaction(connection, query);
});
- if (result.getTotal() <= 0)
- {
- if (logicalNotConditional)
- logger.warn("Reference target by identifier '{}|{}' of reference at {} in resource",
- resourceReference.getReference().getIdentifier().getSystem(),
- resourceReference.getReference().getIdentifier().getValue(), resourceReference.getLocation());
- else
- logger.warn("Reference target by condition '{}' of reference at {} in resource",
- UriComponentsBuilder.newInstance().path(referenceTargetDao.getResourceTypeName())
- .replaceQueryParams(CollectionUtils.toMultiValueMap(queryParameters)).toUriString(),
- resourceReference.getLocation());
-
- return Optional.empty();
- }
- else if (result.getTotal() == 1)
- {
+ if (result.getTotal() == 1)
return Optional.of(result.getPartialResult().get(0));
- }
- else // if (result.getOverallCount() > 1)
+
+ else
{
int overallCount = result.getTotal();
- if (logicalNotConditional)
- logger.warn(
- "Found {} matches for reference target by identifier '{}|{}' of reference at {} in resource",
- overallCount, resourceReference.getReference().getIdentifier().getSystem(),
- resourceReference.getReference().getIdentifier().getValue(), resourceReference.getLocation());
- else
- logger.warn("Found {} matches for reference target by condition '{}' of reference at {} in resource",
- overallCount,
+ if (ReferenceType.LOGICAL.equals(referenceType))
+ logger.warn("Found {} matches for reference at {} with identifier '{}|{}'", overallCount,
+ resourceReference.getLocation(), resourceReference.getReference().getIdentifier().getSystem(),
+ resourceReference.getReference().getIdentifier().getValue());
+ else if (EnumSet.of(ReferenceType.CONDITIONAL, ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL,
+ ReferenceType.ATTACHMENT_CONDITIONAL_URL).contains(referenceType))
+ logger.warn("Found {} matches for reference at {} with condition '{}'", overallCount,
+ resourceReference.getLocation(),
UriComponentsBuilder.newInstance().path(referenceTargetDao.getResourceTypeName())
- .replaceQueryParams(CollectionUtils.toMultiValueMap(queryParameters)).toUriString(),
- resourceReference.getLocation());
+ .replaceQueryParams(CollectionUtils.toMultiValueMap(queryParameters)).toUriString());
+ else if (ReferenceType.CANONICAL.equals(referenceType))
+ logger.warn("Found {} matches for reference at {} with url '{}'", overallCount,
+ resourceReference.getLocation(), resourceReference.getCanonical().getValue());
return Optional.empty();
}
@@ -558,8 +533,9 @@ public Optional checkLogicalReference(Identity identity, Resou
Identifier targetIdentifier = reference.getReference().getIdentifier();
// Resource target =
- return search(identity, resource, bundleIndex, connection, d, reference, Map.of("identifier",
- Collections.singletonList(targetIdentifier.getSystem() + "|" + targetIdentifier.getValue())), true);
+ return search(identity, resource, bundleIndex, connection, d, reference,
+ Map.of("identifier", List.of(targetIdentifier.getSystem() + "|" + targetIdentifier.getValue())),
+ true);
// resourceReference.getReference().setIdentifier(null).setReferenceElement(
// new IdType(target.getResourceType().name(), target.getIdElement().getIdPart()));
@@ -627,4 +603,46 @@ else if (result.getTotal() == 1)
resource, resourceReference, result.getTotal()));
}
}
+
+ @Override
+ public Optional checkCanonicalReference(Identity identity, Resource resource,
+ ResourceReference reference, Connection connection) throws IllegalArgumentException
+ {
+ return checkCanonicalReference(identity, resource, reference, connection, null);
+ }
+
+ @Override
+ public Optional checkCanonicalReference(Identity identity, Resource resource,
+ ResourceReference reference, Connection connection, Integer bundleIndex) throws IllegalArgumentException
+ {
+ Objects.requireNonNull(identity, "identity");
+ Objects.requireNonNull(resource, "resource");
+ Objects.requireNonNull(reference, "reference");
+ Objects.requireNonNull(connection, "connection");
+ throwIfReferenceTypeUnexpected(reference.getType(serverBase), ReferenceType.CANONICAL);
+
+ Optional> referenceDao = switch (reference.getLocation())
+ {
+ case QUESTIONNAIRE_RESPONSE_QUESTIONNAIRE -> Optional.of(daoProvider.getQuestionnaireDao());
+ case TASK_INSTANTIATES_CANONICAL -> Optional.of(daoProvider.getActivityDefinitionDao());
+
+ default -> Optional.empty();
+ };
+
+ if (referenceDao.isEmpty())
+ {
+ logger.debug(
+ "Canonical reference check only implemented for QuestionnaireResponse.questionnaire and Task.instantiatesCanonical, not checking {}",
+ reference.getLocation());
+ return Optional.empty();
+ }
+
+ Optional referencedResource = referenceDao.flatMap(dao -> search(identity, connection, dao, reference,
+ Map.of("url", List.of(reference.getCanonical().getValue())), ReferenceType.CANONICAL));
+
+ if (referencedResource.isPresent())
+ return Optional.empty();
+ else
+ return Optional.of(responseGenerator.referenceTargetNotFoundLocally(bundleIndex, resource, reference));
+ }
}
\ No newline at end of file
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 1778674d7..a97e8c735 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
@@ -243,7 +243,7 @@ private void checkReferences(Resource resource, Connection connection, Predicate
throws WebApplicationException
{
referenceExtractor.getReferences(resource).filter(checkReference)
- .filter(ref -> referenceResolver.referenceCanBeChecked(ref, connection)).forEach(ref ->
+ .filter(ref -> referenceResolver.referenceCanBeResolved(ref, connection)).forEach(ref ->
{
Optional outcome = checkReference(resource, connection, ref);
if (outcome.isPresent())
@@ -268,6 +268,9 @@ private Optional checkReference(Resource resource, Connection
case LOGICAL ->
referenceResolver.checkLogicalReference(getCurrentIdentity(), resource, reference, connection);
+ case CANONICAL ->
+ referenceResolver.checkCanonicalReference(getCurrentIdentity(), resource, reference, connection);
+
// unknown URLs to non FHIR servers in related artifacts must not be checked
case RELATED_ARTEFACT_UNKNOWN_URL, ATTACHMENT_UNKNOWN_URL -> Optional.empty();
diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.changelog.xml b/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.changelog.xml
index 7ab4d051c..90e0f7e40 100644
--- a/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.changelog.xml
+++ b/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.changelog.xml
@@ -38,4 +38,6 @@
+
+
diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.read_access.changelog-1.6.0.xml b/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.read_access.changelog-1.6.0.xml
new file mode 100644
index 000000000..a63509673
--- /dev/null
+++ b/dsf-fhir/dsf-fhir-server/src/main/resources/db/db.read_access.changelog-1.6.0.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+ SELECT
+ id
+ , version
+ , type
+ , resource
+ FROM (
+ SELECT activity_definition_id AS id, version, 'ActivityDefinition'::text AS type, activity_definition AS resource FROM current_activity_definitions
+ UNION
+ SELECT binary_id AS id, version, 'Binary'::text AS type, binary_json AS resource FROM current_binaries
+ UNION
+ SELECT bundle_id AS id, version, 'Bundle'::text AS type, bundle AS resource FROM current_bundles
+ UNION
+ SELECT code_system_id AS id, version, 'CodeSystem'::text AS type, code_system AS resource FROM current_code_systems
+ UNION
+ SELECT document_reference_id AS id, version, 'DocumentReference'::text AS type, document_reference AS resource FROM current_document_references
+ UNION
+ SELECT endpoint_id AS id, version, 'Endpoint'::text AS type, endpoint AS resource FROM current_endpoints
+ UNION
+ SELECT group_id AS id, version, 'Group'::text AS type, group_json AS resource FROM current_groups
+ UNION
+ SELECT healthcare_service_id AS id, version, 'HealthcareService'::text AS type, healthcare_service AS resource FROM current_healthcare_services
+ UNION
+ SELECT library_id AS id, version, 'Library'::text AS type, library AS resource FROM current_libraries
+ UNION
+ SELECT location_id AS id, version, 'Location'::text AS type, location AS resource FROM current_locations
+ UNION
+ SELECT measure_report_id AS id, version, 'MeasureReport'::text AS type, measure_report AS resource FROM current_measure_reports
+ UNION
+ SELECT measure_id AS id, version, 'Measure'::text AS type, measure AS resource FROM current_measures
+ UNION
+ SELECT naming_system_id AS id, version, 'NamingSystem'::text AS type, naming_system AS resource FROM current_naming_systems
+ UNION
+ SELECT organization_id AS id, version, 'Organization'::text AS type, organization AS resource FROM current_organizations
+ UNION
+ SELECT organization_affiliation_id AS id, version, 'OrganizationAffiliation'::text AS type, organization_affiliation AS resource FROM current_organization_affiliations
+ UNION
+ SELECT patient_id AS id, version, 'Patient'::text AS type, patient AS resource FROM current_patients
+ UNION
+ SELECT practitioner_role_id AS id, version, 'PractitionerRole'::text AS type, practitioner_role AS resource FROM current_practitioner_roles
+ UNION
+ SELECT practitioner_id AS id, version, 'Practitioner'::text AS type, practitioner AS resource FROM current_practitioners
+ UNION
+ SELECT provenance_id AS id, version, 'Provenance'::text AS type, provenance AS resource FROM current_provenances
+ UNION
+ SELECT questionnaire_id AS id, version, 'Questionnaire'::text AS type, questionnaire AS resource FROM current_questionnaires
+ UNION
+ SELECT research_study_id AS id, version, 'ResearchStudy'::text AS type, research_study AS resource FROM current_research_studies
+ UNION
+ SELECT structure_definition_id AS id, version, 'StructureDefinition'::text AS type, structure_definition AS resource FROM current_structure_definitions
+ UNION
+ SELECT subscription_id AS id, version, 'Subscription'::text AS type, subscription AS resource FROM current_subscriptions
+ UNION
+ SELECT value_set_id AS id, version, 'ValueSet'::text AS type, value_set AS resource FROM current_value_sets
+ ) AS current_all_read_access
+
+
+ ALTER TABLE all_read_access_resources OWNER TO ${db.liquibase_user};
+ GRANT ALL ON TABLE all_read_access_resources TO ${db.liquibase_user};
+ GRANT SELECT ON TABLE all_read_access_resources TO ${db.server_users_group};
+
+
+
\ No newline at end of file
diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organization_affiliations_insert.sql b/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organization_affiliations_insert.sql
index 5437390a7..80a0d02e3 100644
--- a/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organization_affiliations_insert.sql
+++ b/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organization_affiliations_insert.sql
@@ -1,5 +1,6 @@
CREATE OR REPLACE FUNCTION on_organization_affiliations_insert() RETURNS TRIGGER AS $$
DECLARE
+ organization_affiliation_exists_active_roles JSONB := (SELECT organization_affiliation->'code' FROM organization_affiliations WHERE organization_affiliation_id = NEW.organization_affiliation_id AND version = NEW.version - 1 AND deleted IS NULL AND organization_affiliation->>'active' = 'true');
reference_regex TEXT := '((http|https):\/\/([A-Za-z0-9\-\\\.\:\%\$]*\/)+)?(Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceDefinition|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EffectEvidenceSynthesis|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|InsurancePlan|Invoice|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProduct|MedicinalProductAuthorization|MedicinalProductContraindication|MedicinalProductIndication|MedicinalProductIngredient|MedicinalProductInteraction|MedicinalProductManufactured|MedicinalProductPackaged|MedicinalProductPharmaceutical|MedicinalProductUndesirableEffect|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionOrder|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RelatedPerson|RequestGroup|ResearchDefinition|ResearchElementDefinition|ResearchStudy|ResearchSubject|RiskAssessment|RiskEvidenceSynthesis|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|Substance|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SubstanceSpecification|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestReport|TestScript|ValueSet|VerificationResult|VisionPrescription)\/([A-Za-z0-9\-\.]{1,64})(\/_history\/([A-Za-z0-9\-\.]{1,64}))?';
parent_organization_identifier TEXT;
member_organization_id UUID;
@@ -8,16 +9,22 @@ DECLARE
delete_count INT;
BEGIN
PERFORM on_resources_insert(NEW.organization_affiliation_id, NEW.version, NEW.organization_affiliation);
-
- DELETE FROM read_access
- WHERE access_type = 'ROLE'
- AND organization_affiliation_id = NEW.organization_affiliation_id;
- GET DIAGNOSTICS delete_count = ROW_COUNT;
- RAISE NOTICE 'Existing rows deleted from read_access for created/updated organization-affiliation: %', delete_count;
-
- RAISE NOTICE 'NEW.organization_affiliation->>''active'' = ''%''', NEW.organization_affiliation->>'active';
- IF (NEW.organization_affiliation->>'active' = 'true') THEN
+ IF ((NEW.organization_affiliation->>'active' = 'false') AND organization_affiliation_exists_active_roles IS NOT NULL)
+ OR ((NEW.organization_affiliation->>'active' = 'true') AND organization_affiliation_exists_active_roles IS NOT NULL AND NEW.organization_affiliation->'code' <> organization_affiliation_exists_active_roles) THEN
+ RAISE NOTICE 'new organization_affiliation inactive and old organization_affiliation exists and active -> delete';
+
+ DELETE FROM read_access
+ WHERE access_type = 'ROLE'
+ AND organization_affiliation_id = NEW.organization_affiliation_id;
+
+ GET DIAGNOSTICS delete_count = ROW_COUNT;
+ RAISE NOTICE 'Existing rows deleted from read_access for created/updated organization-affiliation: %', delete_count;
+
+ ELSIF ((NEW.organization_affiliation->>'active' = 'true') AND NOT organization_affiliation_exists_active_roles IS NOT NULL)
+ OR ((NEW.organization_affiliation->>'active' = 'true') AND organization_affiliation_exists_active_roles IS NOT NULL AND NEW.organization_affiliation->'code' <> organization_affiliation_exists_active_roles) THEN
+ RAISE NOTICE 'new organization_affiliation active and old organization_affiliation not exist or inactive -> insert';
+
parent_organization_identifier := jsonb_path_query(organization, '$.identifier[*] ? (@.system == "http://dsf.dev/sid/organization-identifier")')->>'value'
FROM current_organizations
WHERE organization_id = (regexp_match(NEW.organization_affiliation->'organization'->>'reference', reference_regex))[5]::uuid
@@ -43,7 +50,7 @@ BEGIN
id
, version
, resource
- FROM all_resources
+ FROM all_read_access_resources
) AS r
ON r.resource->'meta'->'tag' @>
('[{"extension":[{"url":"http://dsf.dev/fhir/StructureDefinition/extension-read-access-parent-organization-role","extension":[{"url":"parent-organization","valueIdentifier":{"system":"http://dsf.dev/sid/organization-identifier","value":"'
@@ -66,10 +73,8 @@ BEGIN
GET DIAGNOSTICS binary_insert_count = ROW_COUNT;
RAISE NOTICE 'Rows inserted into read_access based on Binary.securityContext: %', binary_insert_count;
END IF;
-
- ELSIF (NEW.organization_affiliation->>'active' = 'false') THEN
- RAISE NOTICE 'Not inserting any entries to read_access, created/updated organization-affiliation is not active';
END IF;
+
RETURN NEW;
END;
$$ LANGUAGE PLPGSQL
\ No newline at end of file
diff --git a/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organizations_insert.sql b/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organizations_insert.sql
index 2eb3218c1..911658f2b 100644
--- a/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organizations_insert.sql
+++ b/dsf-fhir/dsf-fhir-server/src/main/resources/db/trigger_functions/on_organizations_insert.sql
@@ -1,5 +1,6 @@
CREATE OR REPLACE FUNCTION on_organizations_insert() RETURNS TRIGGER AS $$
DECLARE
+ organization_exists_active BOOLEAN := EXISTS (SELECT 1 FROM organizations WHERE organization_id = NEW.organization_id AND version = NEW.version - 1 AND deleted IS NULL AND organization->>'active' = 'true');
reference_regex TEXT := '((http|https):\/\/([A-Za-z0-9\-\\\.\:\%\$]*\/)+)?(Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceDefinition|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EffectEvidenceSynthesis|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|InsurancePlan|Invoice|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProduct|MedicinalProductAuthorization|MedicinalProductContraindication|MedicinalProductIndication|MedicinalProductIngredient|MedicinalProductInteraction|MedicinalProductManufactured|MedicinalProductPackaged|MedicinalProductPharmaceutical|MedicinalProductUndesirableEffect|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionOrder|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RelatedPerson|RequestGroup|ResearchDefinition|ResearchElementDefinition|ResearchStudy|ResearchSubject|RiskAssessment|RiskEvidenceSynthesis|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|Substance|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SubstanceSpecification|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestReport|TestScript|ValueSet|VerificationResult|VisionPrescription)\/([A-Za-z0-9\-\.]{1,64})(\/_history\/([A-Za-z0-9\-\.]{1,64}))?';
organization_identifier TEXT := jsonb_path_query(NEW.organization, '$.identifier[*]?(@.system == "http://dsf.dev/sid/organization-identifier")')->>'value';
organization_insert_count INT;
@@ -9,36 +10,40 @@ DECLARE
roles_delete_count INT;
BEGIN
PERFORM on_resources_insert(NEW.organization_id, NEW.version, NEW.organization);
-
- DELETE FROM read_access
- WHERE access_type = 'ORGANIZATION'
- AND organization_id = NEW.organization_id;
-
- GET DIAGNOSTICS delete_count = ROW_COUNT;
- RAISE NOTICE 'Existing rows deleted from read_access for created/updated organization, ORGANIZATION Tag: %', delete_count;
-
- DELETE FROM read_access
- WHERE access_type = 'ROLE'
- AND organization_affiliation_id IN (
- SELECT organization_affiliation_id FROM current_organization_affiliations
- WHERE NEW.organization_id = (regexp_match(organization_affiliation->'participatingOrganization'->>'reference', reference_regex))[5]::uuid
- OR NEW.organization_id = (regexp_match(organization_affiliation->'organization'->>'reference', reference_regex))[5]::uuid
- );
- GET DIAGNOSTICS roles_delete_count = ROW_COUNT;
- RAISE NOTICE 'Existing rows deleted from read_access for created/updated organization, ROLE Tag: %', roles_delete_count;
-
- RAISE NOTICE 'NEW.organization->>''active'' = ''%''', NEW.organization->>'active';
- IF (NEW.organization->>'active' = 'true') THEN
+ IF (NEW.organization->>'active' = 'false') AND organization_exists_active THEN
+ RAISE NOTICE 'new organization inactive and old organization exists and active -> delete';
+
+ DELETE FROM read_access
+ WHERE access_type = 'ORGANIZATION'
+ AND organization_id = NEW.organization_id;
+
+ GET DIAGNOSTICS delete_count = ROW_COUNT;
+ RAISE NOTICE 'Existing rows deleted from read_access for created/updated organization, ORGANIZATION Tag: %', delete_count;
+
+ DELETE FROM read_access
+ WHERE access_type = 'ROLE'
+ AND organization_affiliation_id IN (
+ SELECT organization_affiliation_id FROM current_organization_affiliations
+ WHERE NEW.organization_id = (regexp_match(organization_affiliation->'participatingOrganization'->>'reference', reference_regex))[5]::uuid
+ OR NEW.organization_id = (regexp_match(organization_affiliation->'organization'->>'reference', reference_regex))[5]::uuid
+ );
+
+ GET DIAGNOSTICS roles_delete_count = ROW_COUNT;
+ RAISE NOTICE 'Existing rows deleted from read_access for created/updated organization, ROLE Tag: %', roles_delete_count;
+
+ ELSIF (NEW.organization->>'active' = 'true') AND NOT organization_exists_active THEN
+ RAISE NOTICE 'new organization active and old organization not exist or inactive -> insert';
+
INSERT INTO read_access
SELECT id, version, 'ORGANIZATION', NEW.organization_id, NULL
- FROM all_resources
+ FROM all_read_access_resources
WHERE jsonb_path_exists(resource,('$.meta.tag[*] ? (@.code == "ORGANIZATION" && @.system == "http://dsf.dev/fhir/CodeSystem/read-access-tag")
.extension[*]?(@.url == "http://dsf.dev/fhir/StructureDefinition/extension-read-access-organization")
.valueIdentifier[*]?(@.system == "http://dsf.dev/sid/organization-identifier" && @.value == "' || organization_identifier || '")')::jsonpath);
-
+
GET DIAGNOSTICS organization_insert_count = ROW_COUNT;
-
+
WITH temp_role_ids AS (
INSERT INTO read_access
SELECT r.id, r.version, 'ROLE', member_organization_id, organization_affiliation_id FROM (
@@ -72,7 +77,7 @@ BEGIN
WHERE parent_organization_organization_id = NEW.organization_id OR member_organization_id = NEW.organization_id
) AS oa
LEFT JOIN (
- SELECT id, version, resource FROM all_resources
+ SELECT id, version, resource FROM all_read_access_resources
) AS r
ON r.resource->'meta'->'tag' @>
('[{"extension":[{"url":"http://dsf.dev/fhir/StructureDefinition/extension-read-access-parent-organization-role","extension":[{"url":"parent-organization","valueIdentifier":{"system":"http://dsf.dev/sid/organization-identifier","value":"'
@@ -101,10 +106,8 @@ BEGIN
GET DIAGNOSTICS binary_insert_count = ROW_COUNT;
RAISE NOTICE 'Rows inserted into read_access based on Binary.securityContext: %', binary_insert_count;
-
- ELSIF (NEW.organization->>'active' = 'false') THEN
- RAISE NOTICE 'Not inserting any entries to read_access, created/updated organization is not active';
END IF;
+
RETURN NEW;
END;
$$ LANGUAGE PLPGSQL
\ No newline at end of file
diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java
index f4b0cb833..134f3ec3a 100644
--- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java
+++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java
@@ -522,6 +522,49 @@ public void testReadAccessTriggerRoleUpdate() throws Exception
assertReadAccessEntryCount(2, 1, v1, READ_ACCESS_TAG_VALUE_ROLE, createdMemberOrg, updatedAff);
}
+ @Test
+ public void testReadAccessTriggerRoleUpdateRoleChange() throws Exception
+ {
+ final OrganizationAffiliationDaoJdbc organizationAffiliationDao = new OrganizationAffiliationDaoJdbc(
+ defaultDataSource, permanentDeleteDataSource, fhirContext);
+
+ Organization parentOrg = new Organization();
+ parentOrg.setActive(true);
+ parentOrg.addIdentifier().setSystem(ORGANIZATION_IDENTIFIER_SYSTEM).setValue("parent.com");
+
+ Organization memberOrg = new Organization();
+ memberOrg.setActive(true);
+ memberOrg.addIdentifier().setSystem(ORGANIZATION_IDENTIFIER_SYSTEM).setValue("member.com");
+
+ OrganizationDao orgDao = new OrganizationDaoJdbc(defaultDataSource, permanentDeleteDataSource, fhirContext);
+ Organization createdParentOrg = orgDao.create(parentOrg);
+ Organization createdMemberOrg = orgDao.create(memberOrg);
+
+ OrganizationAffiliation aff = new OrganizationAffiliation();
+ aff.setActive(true);
+ aff.getCodeFirstRep().getCodingFirstRep().setSystem("http://dsf.dev/fhir/CodeSystem/organization-role")
+ .setCode("DIC");
+ aff.getOrganization().setReference("Organization/" + createdParentOrg.getIdElement().getIdPart());
+ aff.getParticipatingOrganization().setReference("Organization/" + createdMemberOrg.getIdElement().getIdPart());
+
+ OrganizationAffiliation createdAff = organizationAffiliationDao.create(aff);
+
+ D d = createResource();
+ readAccessHelper.addRole(d, "parent.com", "http://dsf.dev/fhir/CodeSystem/organization-role", "DIC");
+
+ D v1 = getDao().create(d);
+ assertEquals(1L, (long) v1.getIdElement().getVersionIdPartAsLong());
+
+ assertReadAccessEntryCount(2, 1, v1, READ_ACCESS_TAG_VALUE_LOCAL);
+ assertReadAccessEntryCount(2, 1, v1, READ_ACCESS_TAG_VALUE_ROLE, createdMemberOrg, createdAff);
+
+ createdAff.getCodeFirstRep().getCodingFirstRep().setCode("HRP");
+ OrganizationAffiliation updatedAff = organizationAffiliationDao.update(createdAff);
+
+ assertReadAccessEntryCount(1, 1, v1, READ_ACCESS_TAG_VALUE_LOCAL);
+ assertReadAccessEntryCount(1, 0, v1, READ_ACCESS_TAG_VALUE_ROLE, createdMemberOrg, updatedAff);
+ }
+
@Test
public void testReadAccessTriggerRoleUpdateMemberOrganizationNonActive() throws Exception
{
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 42bbb1c95..7e1e817c7 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
@@ -6,6 +6,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.io.Reader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
@@ -15,6 +17,10 @@
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.OrganizationAffiliation;
import org.junit.Test;
+import org.postgresql.copy.CopyManager;
+import org.postgresql.core.BaseConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import dev.dsf.fhir.authorization.read.ReadAccessHelper;
import dev.dsf.fhir.authorization.read.ReadAccessHelperImpl;
@@ -25,6 +31,8 @@
public class OrganizationAffiliationDaoTest
extends AbstractReadAccessDaoTest
{
+ private static final Logger logger = LoggerFactory.getLogger(OrganizationAffiliationDaoTest.class);
+
private static final String identifierSystem = "http://dsf.dev/sid/organization-identifier";
private static final String identifierValue = "identifier.test";
private static final boolean active = true;
@@ -315,4 +323,114 @@ public void testExistsNotDeletedByParentOrganizationMemberOrganizationRoleAndNot
assertTrue(exists2);
}
}
+
+ private static class TaskAsCsvGeneratorReader extends Reader
+ {
+ public static final int TASK_ROW_LINE_LENGTH = 1615;
+
+ private final int maxTasks;
+ private int currentTask;
+
+ public TaskAsCsvGeneratorReader(int maxTasks)
+ {
+ this.maxTasks = maxTasks;
+ }
+
+ @Override
+ public int read(char[] cbuf, int off, int len) throws IOException
+ {
+ if (len != TASK_ROW_LINE_LENGTH)
+ throw new IllegalArgumentException("Buffer length " + TASK_ROW_LINE_LENGTH + " expected, not " + len);
+ if (off % TASK_ROW_LINE_LENGTH != 0)
+ throw new IllegalArgumentException("Buffer offset mod " + TASK_ROW_LINE_LENGTH + " == 0 expected, not "
+ + (off & TASK_ROW_LINE_LENGTH) + " (off = " + off + ")");
+
+ if (currentTask < maxTasks)
+ {
+ String line = generateLine();
+
+ if (line.length() != TASK_ROW_LINE_LENGTH)
+ throw new IllegalArgumentException("Line length " + TASK_ROW_LINE_LENGTH + " expected");
+
+ System.arraycopy(line.toCharArray(), 0, cbuf, 0, TASK_ROW_LINE_LENGTH);
+ currentTask++;
+
+ return TASK_ROW_LINE_LENGTH;
+ }
+ else
+ return -1;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ }
+
+ private String generateLine()
+ {
+ String id = UUID.randomUUID().toString();
+ return "${id},3,,\"{\"\"resourceType\"\":\"\"Task\"\",\"\"id\"\":\"\"${id}\"\",\"\"meta\"\":{\"\"versionId\"\":\"\"3\"\",\"\"lastUpdated\"\":\"\"2024-10-01T18:22:06.765+02:00\"\",\"\"profile\"\":[\"\"http://medizininformatik-initiative.de/fhir/StructureDefinition/feasibility-task-execute|1.0\"\"]},\"\"instantiatesCanonical\"\":\"\"http://medizininformatik-initiative.de/bpe/Process/feasibilityExecute|1.0\"\",\"\"status\"\":\"\"completed\"\",\"\"intent\"\":\"\"order\"\",\"\"authoredOn\"\":\"\"2024-10-01T18:22:07+02:00\"\",\"\"requester\"\":{\"\"type\"\":\"\"Organization\"\",\"\"identifier\"\":{\"\"system\"\":\"\"http://dsf.dev/sid/organization-identifier\"\",\"\"value\"\":\"\"organization.com\"\"}},\"\"restriction\"\":{\"\"recipient\"\":[{\"\"type\"\":\"\"Organization\"\",\"\"identifier\"\":{\"\"system\"\":\"\"http://dsf.dev/sid/organization-identifier\"\",\"\"value\"\":\"\"organization.com\"\"}}]},\"\"input\"\":[{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://dsf.dev/fhir/CodeSystem/bpmn-message\"\",\"\"code\"\":\"\"message-name\"\"}]},\"\"valueString\"\":\"\"feasibilityExecuteMessage\"\"},{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://dsf.dev/fhir/CodeSystem/bpmn-message\"\",\"\"code\"\":\"\"business-key\"\"}]},\"\"valueString\"\":\"\"${business-key}\"\"},{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://dsf.dev/fhir/CodeSystem/bpmn-message\"\",\"\"code\"\":\"\"correlation-key\"\"}]},\"\"valueString\"\":\"\"${correlation-key}\"\"},{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://medizininformatik-initiative.de/fhir/CodeSystem/feasibility\"\",\"\"code\"\":\"\"measure-reference\"\"}]},\"\"valueReference\"\":{\"\"reference\"\":\"\"https://dsf.fdpg.test.forschen-fuer-gesundheit.de/fhir/Measure/02bb7540-0d99-4c0e-8764-981e545cf646\"\"}}]}\"\n"
+ .replace("${id}", id).replace("${business-key}", UUID.randomUUID().toString())
+ .replace("${correlation-key}", UUID.randomUUID().toString());
+ }
+ }
+
+ @Test
+ public void testBigUpdate() throws Exception
+ {
+ OrganizationDaoJdbc orgDao = new OrganizationDaoJdbc(defaultDataSource, permanentDeleteDataSource, fhirContext);
+
+ Organization memberOrg = new Organization();
+ memberOrg.setActive(true);
+ memberOrg.getIdentifierFirstRep().setSystem(ReadAccessHelper.ORGANIZATION_IDENTIFIER_SYSTEM)
+ .setValue("organization.com");
+
+ Organization createdMemberOrg = orgDao.create(memberOrg);
+ assertNotNull(createdMemberOrg);
+
+ Organization parentOrg = new Organization();
+ parentOrg.setActive(true);
+ parentOrg.getIdentifierFirstRep().setSystem(ReadAccessHelper.ORGANIZATION_IDENTIFIER_SYSTEM)
+ .setValue("organization.com");
+
+ Organization createdParentOrg = orgDao.create(parentOrg);
+ assertNotNull(createdParentOrg);
+
+ OrganizationAffiliation affiliation = new OrganizationAffiliation();
+ affiliation.setActive(true);
+ affiliation.getParticipatingOrganization()
+ .setReference("Organization/" + createdMemberOrg.getIdElement().getIdPart());
+ affiliation.getOrganization().setReference("Organization/" + createdParentOrg.getIdElement().getIdPart());
+ affiliation.addCode().getCodingFirstRep().setSystem("role-system").setCode("role-code");
+
+ OrganizationAffiliation createdAffiliation = dao.create(affiliation);
+ assertNotNull(createdAffiliation);
+
+ final int taskCount = 500_000;
+
+ logger.info("Inserting {} Task resources ...", taskCount);
+ try (Connection connection = defaultDataSource.getConnection())
+ {
+ CopyManager copyManager = new CopyManager(connection.unwrap(BaseConnection.class));
+ TaskAsCsvGeneratorReader taskGenerator = new TaskAsCsvGeneratorReader(taskCount);
+ long insertedRows = copyManager.copyIn("COPY tasks FROM STDIN (FORMAT csv)", taskGenerator,
+ TaskAsCsvGeneratorReader.TASK_ROW_LINE_LENGTH);
+
+ assertEquals(taskCount, insertedRows);
+ }
+ logger.info("Inserting {} Task resources [Done]", taskCount);
+
+ long t0 = System.currentTimeMillis();
+
+ OrganizationAffiliation updatedAffiliation1 = dao.update(createdAffiliation);
+ assertNotNull(updatedAffiliation1);
+
+ OrganizationAffiliation updatedAffiliation2 = dao.update(updatedAffiliation1);
+ assertNotNull(updatedAffiliation2);
+
+ long t1 = System.currentTimeMillis();
+
+ logger.info("OrganizationAffiliation updates executed in {} ms", t1 - t0);
+ assertTrue("OrganizationAffiliation updates took longer then 200 ms", t1 - t0 <= 200);
+ }
}
diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationDaoTest.java
index 6d89119ac..01c2b37f8 100755
--- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationDaoTest.java
+++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/OrganizationDaoTest.java
@@ -4,6 +4,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -17,7 +19,11 @@
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.StringType;
import org.junit.Test;
+import org.postgresql.copy.CopyManager;
+import org.postgresql.core.BaseConnection;
import org.postgresql.util.PGobject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import dev.dsf.fhir.authorization.read.ReadAccessHelper;
import dev.dsf.fhir.authorization.read.ReadAccessHelperImpl;
@@ -27,6 +33,8 @@
public class OrganizationDaoTest extends AbstractReadAccessDaoTest
{
+ private static final Logger logger = LoggerFactory.getLogger(OrganizationDaoTest.class);
+
private static final String name = "Demo Organization";
private static final boolean active = true;
@@ -234,8 +242,8 @@ public void testUpdateWithExistingBinary() throws Exception
org.getIdentifierFirstRep().setSystem(ReadAccessHelper.ORGANIZATION_IDENTIFIER_SYSTEM)
.setValue("organization.com");
- Organization cretedOrg = dao.create(org);
- assertNotNull(cretedOrg);
+ Organization createdOrg = dao.create(org);
+ assertNotNull(createdOrg);
Binary binary = new Binary();
binary.setContentType("text/plain");
@@ -246,6 +254,96 @@ public void testUpdateWithExistingBinary() throws Exception
Binary createdBinary = binaryDao.create(binary);
assertNotNull(createdBinary);
- dao.update(cretedOrg);
+ dao.update(createdOrg);
+ }
+
+ private static class TaskAsCsvGeneratorReader extends Reader
+ {
+ public static final int TASK_ROW_LINE_LENGTH = 1615;
+
+ private final int maxTasks;
+ private int currentTask;
+
+ public TaskAsCsvGeneratorReader(int maxTasks)
+ {
+ this.maxTasks = maxTasks;
+ }
+
+ @Override
+ public int read(char[] cbuf, int off, int len) throws IOException
+ {
+ if (len != TASK_ROW_LINE_LENGTH)
+ throw new IllegalArgumentException("Buffer length " + TASK_ROW_LINE_LENGTH + " expected, not " + len);
+ if (off % TASK_ROW_LINE_LENGTH != 0)
+ throw new IllegalArgumentException("Buffer offset mod " + TASK_ROW_LINE_LENGTH + " == 0 expected, not "
+ + (off & TASK_ROW_LINE_LENGTH) + " (off = " + off + ")");
+
+ if (currentTask < maxTasks)
+ {
+ String line = generateLine();
+
+ if (line.length() != TASK_ROW_LINE_LENGTH)
+ throw new IllegalArgumentException("Line length " + TASK_ROW_LINE_LENGTH + " expected");
+
+ System.arraycopy(line.toCharArray(), 0, cbuf, 0, TASK_ROW_LINE_LENGTH);
+ currentTask++;
+
+ return TASK_ROW_LINE_LENGTH;
+ }
+ else
+ return -1;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ }
+
+ private String generateLine()
+ {
+ String id = UUID.randomUUID().toString();
+ return "${id},3,,\"{\"\"resourceType\"\":\"\"Task\"\",\"\"id\"\":\"\"${id}\"\",\"\"meta\"\":{\"\"versionId\"\":\"\"3\"\",\"\"lastUpdated\"\":\"\"2024-10-01T18:22:06.765+02:00\"\",\"\"profile\"\":[\"\"http://medizininformatik-initiative.de/fhir/StructureDefinition/feasibility-task-execute|1.0\"\"]},\"\"instantiatesCanonical\"\":\"\"http://medizininformatik-initiative.de/bpe/Process/feasibilityExecute|1.0\"\",\"\"status\"\":\"\"completed\"\",\"\"intent\"\":\"\"order\"\",\"\"authoredOn\"\":\"\"2024-10-01T18:22:07+02:00\"\",\"\"requester\"\":{\"\"type\"\":\"\"Organization\"\",\"\"identifier\"\":{\"\"system\"\":\"\"http://dsf.dev/sid/organization-identifier\"\",\"\"value\"\":\"\"organization.com\"\"}},\"\"restriction\"\":{\"\"recipient\"\":[{\"\"type\"\":\"\"Organization\"\",\"\"identifier\"\":{\"\"system\"\":\"\"http://dsf.dev/sid/organization-identifier\"\",\"\"value\"\":\"\"organization.com\"\"}}]},\"\"input\"\":[{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://dsf.dev/fhir/CodeSystem/bpmn-message\"\",\"\"code\"\":\"\"message-name\"\"}]},\"\"valueString\"\":\"\"feasibilityExecuteMessage\"\"},{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://dsf.dev/fhir/CodeSystem/bpmn-message\"\",\"\"code\"\":\"\"business-key\"\"}]},\"\"valueString\"\":\"\"${business-key}\"\"},{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://dsf.dev/fhir/CodeSystem/bpmn-message\"\",\"\"code\"\":\"\"correlation-key\"\"}]},\"\"valueString\"\":\"\"${correlation-key}\"\"},{\"\"type\"\":{\"\"coding\"\":[{\"\"system\"\":\"\"http://medizininformatik-initiative.de/fhir/CodeSystem/feasibility\"\",\"\"code\"\":\"\"measure-reference\"\"}]},\"\"valueReference\"\":{\"\"reference\"\":\"\"https://dsf.fdpg.test.forschen-fuer-gesundheit.de/fhir/Measure/02bb7540-0d99-4c0e-8764-981e545cf646\"\"}}]}\"\n"
+ .replace("${id}", id).replace("${business-key}", UUID.randomUUID().toString())
+ .replace("${correlation-key}", UUID.randomUUID().toString());
+ }
+ }
+
+ @Test
+ public void testBigUpdate() throws Exception
+ {
+ Organization org = new Organization();
+ org.setActive(true);
+ org.getIdentifierFirstRep().setSystem(ReadAccessHelper.ORGANIZATION_IDENTIFIER_SYSTEM)
+ .setValue("organization.com");
+
+ Organization createdOrg = dao.create(org);
+ assertNotNull(createdOrg);
+
+ final int taskCount = 500_000;
+
+ logger.info("Inserting {} Task resources ...", taskCount);
+ try (Connection connection = defaultDataSource.getConnection())
+ {
+ CopyManager copyManager = new CopyManager(connection.unwrap(BaseConnection.class));
+ TaskAsCsvGeneratorReader taskGenerator = new TaskAsCsvGeneratorReader(taskCount);
+ long insertedRows = copyManager.copyIn("COPY tasks FROM STDIN (FORMAT csv)", taskGenerator,
+ TaskAsCsvGeneratorReader.TASK_ROW_LINE_LENGTH);
+
+ assertEquals(taskCount, insertedRows);
+ }
+ logger.info("Inserting {} Task resources [Done]", taskCount);
+
+ long t0 = System.currentTimeMillis();
+
+ Organization updatedOrg1 = dao.update(createdOrg);
+ assertNotNull(updatedOrg1);
+
+ Organization updatedOrg2 = dao.update(createdOrg);
+ assertNotNull(updatedOrg2);
+
+ long t1 = System.currentTimeMillis();
+
+ logger.info("Organization updates executed in {} ms", t1 - t0);
+ assertTrue("Organization updates took longer then 200 ms", t1 - t0 <= 200);
}
}
diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireResponseIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireResponseIntegrationTest.java
index 84966dddd..d93a490ed 100644
--- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireResponseIntegrationTest.java
+++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireResponseIntegrationTest.java
@@ -41,7 +41,7 @@ public void testCreateValidByLocalUser() throws Exception
}
@Test
- public void testCreateNotAllowedByLocalUser() throws Exception
+ public void testCreateNotAllowedByLocalUserStatusCompleted() throws Exception
{
QuestionnaireResponse questionnaireResponse = createQuestionnaireResponse();
questionnaireResponse.setStatus(QuestionnaireResponseStatus.COMPLETED);
@@ -49,6 +49,14 @@ public void testCreateNotAllowedByLocalUser() throws Exception
expectForbidden(() -> getWebserviceClient().create(questionnaireResponse));
}
+ @Test
+ public void testCreateNotAllowedByLocalUserQuestionnaireDoesNotExists() throws Exception
+ {
+ QuestionnaireResponse questionnaireResponse = createQuestionnaireResponse();
+
+ expectForbidden(() -> getWebserviceClient().create(questionnaireResponse));
+ }
+
@Test
public void testCreateNotAllowedByRemoteUser() throws Exception
{
diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireVsQuestionnaireResponseIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireVsQuestionnaireResponseIntegrationTest.java
index bde13972b..b1dc6d7bb 100644
--- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireVsQuestionnaireResponseIntegrationTest.java
+++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/QuestionnaireVsQuestionnaireResponseIntegrationTest.java
@@ -1,11 +1,15 @@
package dev.dsf.fhir.integration;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import java.util.Optional;
+import java.util.UUID;
+import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
+import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.StringType;
import org.junit.Test;
@@ -146,4 +150,53 @@ public void testQuestionnaireResponseValidatesAgainstQuestionnaireProfileVersion
assertNotNull(updatedQr.getIdElement().getIdPart());
assertNotNull(updatedQr.getIdElement().getVersionIdPart());
}
+
+ @Test
+ public void testPostQuestionnaireAndCorrespondingQuestionnaireResponseInTransactionBundleOrderQuestionnaireBeforeQuestionnaireResponse()
+ throws Exception
+ {
+ Questionnaire questionnaire = createQuestionnaireProfileVersion150("1.5.3");
+ QuestionnaireResponse questionnaireResponse = createQuestionnaireResponse("1.5.3");
+
+ Bundle bundle = new Bundle();
+ bundle.setType(Bundle.BundleType.TRANSACTION);
+ bundle.addEntry().setResource(questionnaire).setFullUrl("urn:uuid:" + UUID.randomUUID().toString()).getRequest()
+ .setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.Questionnaire.name());
+ bundle.addEntry().setResource(questionnaireResponse).setFullUrl("urn:uuid:" + UUID.randomUUID().toString())
+ .getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.QuestionnaireResponse.name());
+
+ assertTrue(getWebserviceClient().postBundle(bundle).getEntry().stream()
+ .allMatch(entry -> entry.getResponse().getStatus().equals("201 Created")));
+ }
+
+ @Test
+ public void testPostQuestionnaireAndCorrespondingQuestionnaireResponseInTransactionBundleOrderQuestionnaireResponseBeforeQuestionnaire()
+ throws Exception
+ {
+ Questionnaire questionnaire = createQuestionnaireProfileVersion150("1.5.3");
+ QuestionnaireResponse questionnaireResponse = createQuestionnaireResponse("1.5.3");
+
+ Bundle bundle = new Bundle();
+ bundle.setType(Bundle.BundleType.TRANSACTION);
+ bundle.addEntry().setResource(questionnaireResponse).setFullUrl("urn:uuid:" + UUID.randomUUID().toString())
+ .getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.QuestionnaireResponse.name());
+ bundle.addEntry().setResource(questionnaire).setFullUrl("urn:uuid:" + UUID.randomUUID().toString()).getRequest()
+ .setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.Questionnaire.name());
+
+ assertTrue(getWebserviceClient().postBundle(bundle).getEntry().stream()
+ .allMatch(entry -> entry.getResponse().getStatus().equals("201 Created")));
+ }
+
+ @Test
+ public void testPostQuestionnaireResponseInTransactionBundleQuestionnaireDoesNotExistForbidden() throws Exception
+ {
+ QuestionnaireResponse questionnaireResponse = createQuestionnaireResponse("1.5.3");
+
+ Bundle bundle = new Bundle();
+ bundle.setType(Bundle.BundleType.TRANSACTION);
+ bundle.addEntry().setResource(questionnaireResponse).setFullUrl("urn:uuid:" + UUID.randomUUID().toString())
+ .getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl(ResourceType.QuestionnaireResponse.name());
+
+ expectForbidden(() -> getWebserviceClient().postBundle(bundle));
+ }
}
\ No newline at end of file
diff --git a/dsf-fhir/dsf-fhir-validation/pom.xml b/dsf-fhir/dsf-fhir-validation/pom.xml
index d31de29c6..74c96a2bb 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
@@ -104,7 +104,7 @@
-
diff --git a/dsf-fhir/dsf-fhir-webservice-client/pom.xml b/dsf-fhir/dsf-fhir-webservice-client/pom.xml
index bf75b2ee5..50ccd3e90 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-fhir/dsf-fhir-websocket-client/pom.xml b/dsf-fhir/dsf-fhir-websocket-client/pom.xml
index be59b1edf..f433481d5 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/ClientEndpoint.java b/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/ClientEndpoint.java
index 9283afe33..842548613 100755
--- a/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/ClientEndpoint.java
+++ b/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/ClientEndpoint.java
@@ -1,5 +1,6 @@
package dev.dsf.fhir.client;
+import java.util.EnumSet;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -80,7 +81,8 @@ public void onClose(Session session, CloseReason closeReason)
logger.warn("Websocket closed, session {}: {} - {}", session.getId(), closeReason.getCloseCode().getCode(),
closeReason.getReasonPhrase());
- if (CloseReason.CloseCodes.CANNOT_ACCEPT.equals(closeReason.getCloseCode()))
+ if (EnumSet.of(CloseReason.CloseCodes.CANNOT_ACCEPT, CloseReason.CloseCodes.CLOSED_ABNORMALLY)
+ .contains(closeReason.getCloseCode()))
{
logger.info("Trying to reconnect websocket");
reconnector.run();
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 04165f981..40fe3f13f 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
@@ -90,8 +90,8 @@ public boolean onDisconnect(CloseReason closeReason)
private final String userAgentValue;
private final ClientEndpoint endpoint;
- private ClientManager manager;
- private Session connection;
+ private volatile ClientManager manager;
+ private volatile Session connection;
private volatile boolean closed;
public WebsocketClientTyrus(Runnable reconnector, URI wsUri, KeyStore trustStore, KeyStore keyStore,
@@ -131,6 +131,8 @@ public void connect()
if (manager != null)
throw new IllegalStateException("Allready connecting/connected");
+ closed = false;
+
manager = ClientManager.createClient();
manager.getProperties().put(ClientProperties.RECONNECT_HANDLER, reconnectHandler);
manager.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, new SslEngineConfigurator(sslContext));
diff --git a/dsf-fhir/pom.xml b/dsf-fhir/pom.xml
index 5f1f43b29..b367ebbaa 100755
--- a/dsf-fhir/pom.xml
+++ b/dsf-fhir/pom.xml
@@ -7,7 +7,7 @@
dev.dsf
dsf-pom
- 1.5.3-SNAPSHOT
+ 1.6.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 2e0984977..2143e2a03 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-tools/dsf-tools-bundle-generator/pom.xml b/dsf-tools/dsf-tools-bundle-generator/pom.xml
index d0c072133..e02a9594a 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-tools/dsf-tools-db-migration/pom.xml b/dsf-tools/dsf-tools-db-migration/pom.xml
index 5990fcee0..ddb0f7807 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.5.3-SNAPSHOT
+ 1.6.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 068e5cb15..563453c40 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-tools/dsf-tools-documentation-generator/pom.xml b/dsf-tools/dsf-tools-documentation-generator/pom.xml
index b0eef4ae4..70f2a7b50 100644
--- a/dsf-tools/dsf-tools-documentation-generator/pom.xml
+++ b/dsf-tools/dsf-tools-documentation-generator/pom.xml
@@ -7,7 +7,7 @@
dev.dsf
dsf-tools-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-tools/dsf-tools-proxy-test/pom.xml b/dsf-tools/dsf-tools-proxy-test/pom.xml
index a6151b8f7..07e0f553c 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.5.3-SNAPSHOT
+ 1.6.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 bcaaf6325..319ee9e5b 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.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/dsf-tools/pom.xml b/dsf-tools/pom.xml
index cfca45ad2..c6dd22454 100755
--- a/dsf-tools/pom.xml
+++ b/dsf-tools/pom.xml
@@ -7,7 +7,7 @@
dev.dsf
dsf-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index 5590e8953..034b8289c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
dev.dsf
dsf-pom
- 1.5.3-SNAPSHOT
+ 1.6.0-SNAPSHOT
pom