From 6f71b98a71887900da3adb08075ff6ec2ab4bd38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hringer?= Date: Tue, 20 Oct 2026 17:52:27 +0200 Subject: [PATCH 1/3] Added missing "/" to xml content replacement --- .../java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceImpl.java index 3269d739a..306dae3da 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceImpl.java @@ -357,7 +357,7 @@ private String toXml(MediaType mediaType, Resource resource) throws IOException Matcher referenceUuidMatcher = XML_REFERENCE_UUID_PATTERN.matcher(content); content = referenceUuidMatcher.replaceAll(result -> "<reference value=\"" + result.group(1) + "\">"); + + "?_format=html\">" + result.group(1) + "\"/>"); return content; } From 875c139f1899980939343e0bd90dc2b325c20b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hringer?= Date: Wed, 21 Oct 2026 10:35:31 +0200 Subject: [PATCH 2/3] Added test --- .../adapter/ThymeleafTemplateServiceTest.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java new file mode 100644 index 000000000..3f83500db --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java @@ -0,0 +1,124 @@ +package dev.dsf.fhir.adapter; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.OrganizationAffiliation; +import org.hl7.fhir.r4.model.Reference; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import ca.uhn.fhir.context.FhirContext; + +import dev.dsf.common.ui.theme.Theme; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; + +@RunWith(MockitoJUnitRunner.class) +public class ThymeleafTemplateServiceTest +{ + private static final String BASE_URL = "http://test.local"; + private static final MediaType MEDIA_TYPE = MediaType.TEXT_HTML_TYPE; + private static final String SELF_CLOSING_TAG_REGEX = "<reference value=\"\\w+/.*\"/>"; + private static final Pattern SELF_CLOSING_TAG_PATTERN = Pattern.compile(SELF_CLOSING_TAG_REGEX); + private static final String NO_SELF_CLOSING_TAG_REGEX = "\"<reference value=\"\\w+/.*\">\";"; + public static final Pattern NO_SELF_CLOSING_TAG_PATTERN = Pattern.compile(NO_SELF_CLOSING_TAG_REGEX); + + private final FhirContext fhirContext = FhirContext.forR4(); + + @Mock + private Theme theme; + + @Mock + private List contexts; + + @Mock + private UriInfo uriInfo; + + @Mock + private SecurityContext securityContext; + + @Test + public void testSelfClosingReferenceTags() throws Exception + { + Mockito.when(uriInfo.getPath()).thenReturn(""); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>()); + + ThymeleafTemplateService thymeleafTemplateService = new ThymeleafTemplateServiceImpl(BASE_URL, theme, fhirContext, contexts, false, false); + + ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream(); + thymeleafTemplateService.writeTo(getExampleOrganization(), Organization.class, MEDIA_TYPE, uriInfo, securityContext, xmlOutputStream); + String xmlContent = toString(xmlOutputStream.toByteArray()); + + assertEquals(1, getNumSelfClosingReferencesWithSlash(xmlContent)); + assertEquals(0, getNumSelfClosingReferencesMissingSlash(xmlContent)); + xmlOutputStream.close(); + + xmlOutputStream = new ByteArrayOutputStream(); + thymeleafTemplateService.writeTo(getExampleOrganizationAffiliation(), OrganizationAffiliation.class, MEDIA_TYPE, uriInfo, securityContext, xmlOutputStream); + xmlContent = toString(xmlOutputStream.toByteArray()); + + assertEquals(3, getNumSelfClosingReferencesWithSlash(xmlContent)); + assertEquals(0, getNumSelfClosingReferencesMissingSlash(xmlContent)); + } + + private Organization getExampleOrganization() + { + Organization organization = new Organization(); + organization.setId(UUID.randomUUID().toString()); + organization.addEndpoint() + .setReference("Endpoint/" + UUID.randomUUID()); + return organization; + } + + private OrganizationAffiliation getExampleOrganizationAffiliation() + { + OrganizationAffiliation organizationAffiliation = new OrganizationAffiliation(); + organizationAffiliation.setId(UUID.randomUUID().toString()); + organizationAffiliation.setOrganization(new Reference("Organization/" + UUID.randomUUID())); + organizationAffiliation.setParticipatingOrganization(new Reference("Organization/" + UUID.randomUUID())); + organizationAffiliation.addEndpoint() + .setReference("Endpoint/" + UUID.randomUUID()); + + return organizationAffiliation; + } + + private String toString(byte[] content) + { + return new String(content, StandardCharsets.UTF_8); + } + + private int getNumSelfClosingReferencesWithSlash(String content) + { + int matches = 0; + Matcher matcher = SELF_CLOSING_TAG_PATTERN.matcher(content); + while(matcher.find()) + { + matches++; + } + return matches; + } + + private int getNumSelfClosingReferencesMissingSlash(String content) + { + int matches = 0; + Matcher matcher = NO_SELF_CLOSING_TAG_PATTERN.matcher(content); + while(matcher.find()) + { + matches++; + } + return matches; + } +} From e43e5c8f757cc7ab7c0d6859a1a9d454c6581f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hringer?= Date: Wed, 21 Oct 2026 11:03:32 +0200 Subject: [PATCH 3/3] Format and sort and add license --- .../adapter/ThymeleafTemplateServiceTest.java | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java index 3f83500db..297308a70 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/adapter/ThymeleafTemplateServiceTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dev.dsf.fhir.adapter; import static org.junit.Assert.assertEquals; @@ -19,7 +34,6 @@ import org.mockito.junit.MockitoJUnitRunner; import ca.uhn.fhir.context.FhirContext; - import dev.dsf.common.ui.theme.Theme; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedHashMap; @@ -56,10 +70,12 @@ public void testSelfClosingReferenceTags() throws Exception Mockito.when(uriInfo.getPath()).thenReturn(""); Mockito.when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>()); - ThymeleafTemplateService thymeleafTemplateService = new ThymeleafTemplateServiceImpl(BASE_URL, theme, fhirContext, contexts, false, false); + ThymeleafTemplateService thymeleafTemplateService = new ThymeleafTemplateServiceImpl(BASE_URL, theme, + fhirContext, contexts, false, false); ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream(); - thymeleafTemplateService.writeTo(getExampleOrganization(), Organization.class, MEDIA_TYPE, uriInfo, securityContext, xmlOutputStream); + thymeleafTemplateService.writeTo(getExampleOrganization(), Organization.class, MEDIA_TYPE, uriInfo, + securityContext, xmlOutputStream); String xmlContent = toString(xmlOutputStream.toByteArray()); assertEquals(1, getNumSelfClosingReferencesWithSlash(xmlContent)); @@ -67,7 +83,8 @@ public void testSelfClosingReferenceTags() throws Exception xmlOutputStream.close(); xmlOutputStream = new ByteArrayOutputStream(); - thymeleafTemplateService.writeTo(getExampleOrganizationAffiliation(), OrganizationAffiliation.class, MEDIA_TYPE, uriInfo, securityContext, xmlOutputStream); + thymeleafTemplateService.writeTo(getExampleOrganizationAffiliation(), OrganizationAffiliation.class, MEDIA_TYPE, + uriInfo, securityContext, xmlOutputStream); xmlContent = toString(xmlOutputStream.toByteArray()); assertEquals(3, getNumSelfClosingReferencesWithSlash(xmlContent)); @@ -78,8 +95,7 @@ private Organization getExampleOrganization() { Organization organization = new Organization(); organization.setId(UUID.randomUUID().toString()); - organization.addEndpoint() - .setReference("Endpoint/" + UUID.randomUUID()); + organization.addEndpoint().setReference("Endpoint/" + UUID.randomUUID()); return organization; } @@ -87,10 +103,9 @@ private OrganizationAffiliation getExampleOrganizationAffiliation() { OrganizationAffiliation organizationAffiliation = new OrganizationAffiliation(); organizationAffiliation.setId(UUID.randomUUID().toString()); - organizationAffiliation.setOrganization(new Reference("Organization/" + UUID.randomUUID())); - organizationAffiliation.setParticipatingOrganization(new Reference("Organization/" + UUID.randomUUID())); - organizationAffiliation.addEndpoint() - .setReference("Endpoint/" + UUID.randomUUID()); + organizationAffiliation.setOrganization(new Reference("Organization/" + UUID.randomUUID())); + organizationAffiliation.setParticipatingOrganization(new Reference("Organization/" + UUID.randomUUID())); + organizationAffiliation.addEndpoint().setReference("Endpoint/" + UUID.randomUUID()); return organizationAffiliation; } @@ -104,10 +119,10 @@ private int getNumSelfClosingReferencesWithSlash(String content) { int matches = 0; Matcher matcher = SELF_CLOSING_TAG_PATTERN.matcher(content); - while(matcher.find()) - { + + while (matcher.find()) matches++; - } + return matches; } @@ -115,10 +130,10 @@ private int getNumSelfClosingReferencesMissingSlash(String content) { int matches = 0; Matcher matcher = NO_SELF_CLOSING_TAG_PATTERN.matcher(content); - while(matcher.find()) - { + + while (matcher.find()) matches++; - } + return matches; } }