From c95af33c4523f4824333529252adb15fce5de230 Mon Sep 17 00:00:00 2001 From: Dmytro Rud Date: Mon, 23 Dec 2024 10:29:53 +0100 Subject: [PATCH] #458: Possibility to set outgoing HTTP headers in FHIR consumers --- .../ihe/fhir/AbstractBundleProvider.java | 16 ++++--- .../ihe/fhir/AbstractPlainProvider.java | 34 +++++++++----- .../commons/ihe/fhir/EagerBundleProvider.java | 9 ++-- .../ipf/commons/ihe/fhir/FhirProvider.java | 15 +++++++ .../commons/ihe/fhir/LazyBundleProvider.java | 11 +++-- .../ipf/commons/ihe/fhir/RequestConsumer.java | 36 ++++++++------- .../commons/ihe/fhir/SharedFhirProvider.java | 8 +++- .../ihe/fhir/EagerBundleProviderTest.java | 7 +-- .../ihe/fhir/LazyBundleProviderTest.java | 23 +++++----- .../camel/ihe/fhir/core/FhirConsumer.java | 44 +++++++++++-------- .../chppqm/LoggingInterceptorFactory.java | 33 ++++++++++++++ .../ihe/fhir/chppqm/chppq3/ChPpq3Test.java | 5 ++- .../chppqm/chppq3/ChPpq3TestRouteBuilder.java | 4 +- .../ihe/fhir/chppqm/chppq5/ChPpq5Test.java | 2 +- .../chppqm/chppq5/ChPpq5TestRouteBuilder.java | 3 ++ .../r4/chppqm/src/test/resources/ch-ppq5.xml | 2 + .../r4/chppqm/src/test/resources/log4j2.xml | 1 + src/site/changes.xml | 3 ++ 18 files changed, 179 insertions(+), 77 deletions(-) create mode 100644 platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/LoggingInterceptorFactory.java diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractBundleProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractBundleProvider.java index fad921c496..0a146ddf66 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractBundleProvider.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractBundleProvider.java @@ -18,6 +18,7 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import jakarta.servlet.http.HttpServletResponse; import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.HashMap; @@ -35,16 +36,18 @@ public abstract class AbstractBundleProvider implements IBundleProvider { private final Object payload; private final Map headers; private final boolean sort; + protected final HttpServletResponse httpServletResponse; - public AbstractBundleProvider(RequestConsumer consumer, Object payload, Map headers) { - this(consumer, false, payload, headers); + public AbstractBundleProvider(RequestConsumer consumer, Object payload, Map headers, HttpServletResponse httpServletResponse) { + this(consumer, false, payload, headers, httpServletResponse); } - public AbstractBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map headers) { + public AbstractBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map headers, HttpServletResponse httpServletResponse) { this.consumer = consumer; this.payload = payload; this.headers = headers; this.sort = sort; + this.httpServletResponse = httpServletResponse; } @Override @@ -57,8 +60,11 @@ public Integer preferredPageSize() { return null; } - protected List obtainResources(Object payload, Map parameters) { - return consumer.handleBundleRequest(payload, parameters); + protected List obtainResources(Object payload, Map inHeaders) { + HashMap outHeaders = new HashMap<>(); + List resources = consumer.handleBundleRequest(payload, inHeaders, outHeaders); + FhirProvider.processOutHeaders(outHeaders, httpServletResponse); + return resources; } protected RequestConsumer getConsumer() { diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractPlainProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractPlainProvider.java index 90937acf29..520d19dffe 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractPlainProvider.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/AbstractPlainProvider.java @@ -26,6 +26,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -91,8 +93,11 @@ protected final R requestResource( RequestDetails requestDetails) { var consumer = getRequestConsumer(requestDetails).orElseThrow(() -> new IllegalStateException("Consumer is not initialized")); - var headers = enrichParameters(parameters, httpServletRequest, requestDetails); - return consumer.handleResourceRequest(payload, headers, resultType); + var inHeaders = enrichParameters(parameters, httpServletRequest, requestDetails); + var outHeaders = new HashMap(); + R resource = consumer.handleResourceRequest(payload, inHeaders, outHeaders, resultType); + processOutHeaders(outHeaders, httpServletResponse); + return resource; } /** @@ -114,17 +119,20 @@ protected final List requestBundle( RequestDetails requestDetails) { var consumer = getRequestConsumer(requestDetails).orElseThrow(() -> new IllegalStateException("Consumer is not initialized")); - var headers = enrichParameters(parameters, httpServletRequest, requestDetails); + var inHeaders = enrichParameters(parameters, httpServletRequest, requestDetails); if (resourceType != null) { - headers.put(Constants.FHIR_RESOURCE_TYPE_HEADER, resourceType); + inHeaders.put(Constants.FHIR_RESOURCE_TYPE_HEADER, resourceType); } - return consumer.handleBundleRequest(payload, headers); + var outHeaders = new HashMap(); + List resources = consumer.handleBundleRequest(payload, inHeaders, outHeaders); + processOutHeaders(outHeaders, httpServletResponse); + return resources; } /** * Requests a {@link IBundleProvider} that takes over the responsibility to fetch requested * bundles. The type of the returned {@link IBundleProvider} instance is determined - * by the {@link #consumer RequestConsumer} impelmentation. + * by the {@link #consumer RequestConsumer} implementation. * * @param payload FHIR request resource (often null) * @param searchParameters FHIR search parameters @@ -142,11 +150,12 @@ protected final IBundleProvider requestBundleProvider( RequestDetails requestDetails) { var consumer = getRequestConsumer(requestDetails).orElseThrow(() -> new IllegalStateException("Consumer is not initialized")); - var headers = enrichParameters(searchParameters, httpServletRequest, requestDetails); + var inHeaders = enrichParameters(searchParameters, httpServletRequest, requestDetails); if (resourceType != null) { - headers.put(Constants.FHIR_RESOURCE_TYPE_HEADER, resourceType); + inHeaders.put(Constants.FHIR_RESOURCE_TYPE_HEADER, resourceType); } - return consumer.handleBundleProviderRequest(payload, headers); + IBundleProvider bundleProvider = consumer.handleBundleProviderRequest(payload, inHeaders, httpServletResponse); + return bundleProvider; } /** @@ -166,8 +175,11 @@ protected final MethodOutcome requestAction( RequestDetails requestDetails) { var consumer = getRequestConsumer(requestDetails).orElseThrow(() -> new IllegalStateException("Consumer is not initialized")); - var headers = enrichParameters(parameters, httpServletRequest, requestDetails); - return consumer.handleAction(payload, headers); + var inHeaders = enrichParameters(parameters, httpServletRequest, requestDetails); + var outHeaders = new HashMap(); + MethodOutcome outcome = consumer.handleAction(payload, inHeaders, outHeaders); + processOutHeaders(outHeaders, httpServletResponse); + return outcome; } diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProvider.java index f2aac672a8..1749213f02 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProvider.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProvider.java @@ -16,6 +16,7 @@ package org.openehealth.ipf.commons.ihe.fhir; +import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -33,12 +34,12 @@ public class EagerBundleProvider extends AbstractBundleProvider { private transient List resources; - public EagerBundleProvider(RequestConsumer consumer, Object payload, Map headers) { - this(consumer, false, payload, headers); + public EagerBundleProvider(RequestConsumer consumer, Object payload, Map headers, HttpServletResponse httpServletResponse) { + this(consumer, false, payload, headers, httpServletResponse); } - public EagerBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map headers) { - super(consumer, sort, payload, headers); + public EagerBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map headers, HttpServletResponse httpServletResponse) { + super(consumer, sort, payload, headers, httpServletResponse); } @Override diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/FhirProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/FhirProvider.java index 5351d0f82e..ad0b958781 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/FhirProvider.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/FhirProvider.java @@ -20,6 +20,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + import java.io.Serializable; import java.util.*; @@ -140,4 +142,17 @@ private static Map> extractHttpHeaders(HttpServletRequest h } return result; } + + public static void processOutHeaders(Map outHeaders, HttpServletResponse httpServletResponse) { + var httpHeadersObject = outHeaders.get(Constants.HTTP_OUTGOING_HEADERS); + if (httpHeadersObject instanceof Map) { + var headers = (Map>) httpHeadersObject; + for (var entry : headers.entrySet()) { + for (var value : entry.getValue()) { + httpServletResponse.addHeader(entry.getKey(), value); + } + } + } + } + } diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProvider.java index 8d108385ed..55eb15f1b3 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProvider.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProvider.java @@ -20,6 +20,7 @@ import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; +import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; @@ -65,9 +66,10 @@ public class LazyBundleProvider extends AbstractBundleProvider { * @param cacheResults cache results. So far, only the result set size is cached * @param payload incoming payload * @param headers incoming headers + * @param httpServletResponse HTTP servlet response */ - public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, Object payload, Map headers) { - this(consumer, cacheResults, false, payload, headers); + public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, Object payload, Map headers, HttpServletResponse httpServletResponse) { + this(consumer, cacheResults, false, payload, headers, httpServletResponse); } /** @@ -78,9 +80,10 @@ public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, Object * @param sort sort results * @param payload incoming payload * @param headers incoming headers + * @param httpServletResponse HTTP servlet response */ - public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, boolean sort, Object payload, Map headers) { - super(consumer, sort, payload, headers); + public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, boolean sort, Object payload, Map headers, HttpServletResponse httpServletResponse) { + super(consumer, sort, payload, headers, httpServletResponse); this.cacheResults = cacheResults; } diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/RequestConsumer.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/RequestConsumer.java index 138812a897..82951b872c 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/RequestConsumer.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/RequestConsumer.java @@ -20,6 +20,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; +import jakarta.servlet.http.HttpServletResponse; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -37,7 +38,7 @@ * on searches. *
    *
  • {@link Constants#FHIR_REQUEST_SIZE_ONLY}: if this entry is present, the requester expects only - * the result size to be returned in an parameter entry with the same name and calls {@link #handleSizeRequest(Object, Map)} + * the result size to be returned in an parameter entry with the same name and calls {@link #handleSizeRequest(Object, Map, Map)} * to do so. If possible, implementations should only request the result size from the backend rather than * a complete result set.
  • *
  • {@link Constants#FHIR_FROM_INDEX} and {@link Constants#FHIR_TO_INDEX}: if these entries are present, @@ -71,22 +72,24 @@ default boolean test(RequestDetails requestDetails) { /** * Handles a Create / Update / Validate / Delete action request. * - * @param payload request payload - * @param headers request parameters, e.g. search parameters + * @param payload request payload + * @param inHeaders request parameters, e.g. search parameters + * @param outHeaders map where Camel response headers will be copied into * @return result of the action execution */ - MethodOutcome handleAction(Object payload, Map headers); + MethodOutcome handleAction(Object payload, Map inHeaders, Map outHeaders); /** * Handles the request for a resource * * @param payload request payload - * @param headers request parameters, e.g. search parameters + * @param inHeaders request parameters, e.g. search parameters + * @param outHeaders map where Camel response headers will be copied into * @param resultType type of the returned resource * @param type of the returned resource * @return resource to be returned */ - R handleResourceRequest(Object payload, Map headers, Class resultType); + R handleResourceRequest(Object payload, Map inHeaders, Map outHeaders, Class resultType); /** * Handles the (search) request for a bundle, effectively being a list of resources. @@ -96,32 +99,35 @@ default boolean test(RequestDetails requestDetails) { * indicating that only a part of the result is requested. Implementations must return only the requested entries. *

    * - * @param payload request payload - * @param headers request parameters, e.g. search parameters or - * @param type of the returned resources contained in the bundle + * @param payload request payload + * @param inHeaders request parameters, e.g. search parameters or + * @param outHeaders map where Camel response headers will be copied into + * @param type of the returned resources contained in the bundle * @return list of resources to be returned */ - List handleBundleRequest(Object payload, Map headers); + List handleBundleRequest(Object payload, Map inHeaders, Map outHeaders); /** * Handles the request for a bundle provider, effectively constructing a list of resources. The returned * IBundleProvider takes over the responsibility to fetch the required subset of the result, usually - * by indirectly calling {@link #handleBundleRequest(Object, Map)} as required. + * by indirectly calling {@link #handleBundleRequest(Object, Map, Map)} as required. * * @param payload request payload * @param headers request parameters, e.g. search parameters + * @param httpServletResponse HTTP servlet response * @return a bundle provider */ - IBundleProvider handleBundleProviderRequest(Object payload, Map headers); + IBundleProvider handleBundleProviderRequest(Object payload, Map headers, HttpServletResponse httpServletResponse); /** * Handles transaction requests * - * @param payload request payload - * @param headers request parameters + * @param payload request payload + * @param inHeaders request parameters + * @param outHeaders map where Camel response headers will be copied into * @return transaction response bundle */ - T handleTransactionRequest(Object payload, Map headers, Class bundleClass); + T handleTransactionRequest(Object payload, Map inHeaders, Map outHeaders, Class bundleClass); /** * Optional method that request the result size of a bundle request. Only used for lazy diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/SharedFhirProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/SharedFhirProvider.java index 3bb0814b87..841576b3ba 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/SharedFhirProvider.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/SharedFhirProvider.java @@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -94,8 +95,11 @@ protected final T requestTransaction( RequestDetails requestDetails) { var consumer = getRequestConsumer(requestDetails).orElseThrow(() -> new InvalidRequestException("Request does not match any consumer or consumers are not initialized")); - var headers = enrichParameters(null, httpServletRequest, requestDetails); - return consumer.handleTransactionRequest(payload, headers, bundleClass); + var inHeaders = enrichParameters(null, httpServletRequest, requestDetails); + var outHeaders = new HashMap(); + T bundle = consumer.handleTransactionRequest(payload, inHeaders, outHeaders, bundleClass); + processOutHeaders(outHeaders, httpServletResponse); + return bundle; } /** diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProviderTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProviderTest.java index 91dd5573ce..d4304ab971 100644 --- a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProviderTest.java +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/EagerBundleProviderTest.java @@ -16,6 +16,7 @@ package org.openehealth.ipf.commons.ihe.fhir; +import io.undertow.servlet.spec.HttpServletResponseImpl; import org.easymock.EasyMock; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -48,12 +49,12 @@ public void setup() { } var payload = new Object(); var headers = new HashMap(); - bundleProvider = new EagerBundleProvider(requestConsumer, payload, headers); + bundleProvider = new EagerBundleProvider(requestConsumer, payload, headers, new HttpServletResponseImpl(null, null)); } @Test public void testGetSize() { - EasyMock.expect(requestConsumer.handleBundleRequest(bundleProvider.getPayload(), bundleProvider.getHeaders())).andReturn(response); + EasyMock.expect(requestConsumer.handleBundleRequest(bundleProvider.getPayload(), bundleProvider.getHeaders(), new HashMap<>())).andReturn(response); EasyMock.replay(requestConsumer); assertEquals(response.size(), bundleProvider.size().intValue()); EasyMock.verify(requestConsumer); @@ -61,7 +62,7 @@ public void testGetSize() { @Test public void testGetResources() { - EasyMock.expect(requestConsumer.handleBundleRequest(bundleProvider.getPayload(), bundleProvider.getHeaders())).andReturn(response); + EasyMock.expect(requestConsumer.handleBundleRequest(bundleProvider.getPayload(), bundleProvider.getHeaders(), new HashMap<>())).andReturn(response); EasyMock.replay(requestConsumer); var result = bundleProvider.getResources(10, 30); Assertions.assertEquals(response.subList(10, 30), result); diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProviderTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProviderTest.java index 6cc56fdb86..26367a0932 100644 --- a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProviderTest.java +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/LazyBundleProviderTest.java @@ -16,6 +16,7 @@ package org.openehealth.ipf.commons.ihe.fhir; +import io.undertow.servlet.spec.HttpServletResponseImpl; import org.easymock.EasyMock; import org.easymock.IArgumentMatcher; import org.hl7.fhir.dstu3.model.Patient; @@ -50,7 +51,7 @@ public void setup() { } var payload = new Object(); var headers = new HashMap(); - bundleProvider = new LazyBundleProvider(requestConsumer, true, payload, headers); + bundleProvider = new LazyBundleProvider(requestConsumer, true, payload, headers, new HttpServletResponseImpl(null, null)); } @Test @@ -63,7 +64,7 @@ public void testGetSize() { @Test public void testGetResources() { - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30), eq(new HashMap<>()))) .andReturn(response.subList(10, 30)); EasyMock.replay(requestConsumer); var result = bundleProvider.getResources(10, 30); @@ -74,7 +75,7 @@ public void testGetResources() { @Test public void testGetResourcesAlreadyCached() { - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30), eq(new HashMap<>()))) .andReturn(response.subList(10, 30)); EasyMock.replay(requestConsumer); @@ -89,9 +90,9 @@ public void testGetResourcesAlreadyCached() { @Test public void testGetResourcesPartlyCached1() { - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30), eq(new HashMap<>()))) .andReturn(response.subList(10, 30)); - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(30, 40))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(30, 40), eq(new HashMap<>()))) .andReturn(response.subList(30, 40)); EasyMock.replay(requestConsumer); @@ -107,11 +108,11 @@ public void testGetResourcesPartlyCached1() { @Test public void testGetResourcesPartlyCached2() { - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 30), eq(new HashMap<>()))) .andReturn(response.subList(10, 30)); - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(5, 10))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(5, 10), eq(new HashMap<>()))) .andReturn(response.subList(5, 10)); - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(30, 35))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(30, 35), eq(new HashMap<>()))) .andReturn(response.subList(30, 35)); EasyMock.replay(requestConsumer); @@ -127,11 +128,11 @@ public void testGetResourcesPartlyCached2() { @Test public void testGetResourcesPartlyCached3() { - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 20))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(10, 20), eq(new HashMap<>()))) .andReturn(response.subList(10, 20)); - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(30, 40))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(30, 40), eq(new HashMap<>()))) .andReturn(response.subList(30, 40)); - EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(20, 30))) + EasyMock.expect(requestConsumer.handleBundleRequest(eq(bundleProvider.getPayload()), hasRequestSublistParameters(20, 30), eq(new HashMap<>()))) .andReturn(response.subList(20, 30)); EasyMock.replay(requestConsumer); diff --git a/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/FhirConsumer.java b/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/FhirConsumer.java index e11129d887..a909690511 100644 --- a/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/FhirConsumer.java +++ b/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/FhirConsumer.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import jakarta.servlet.http.HttpServletResponse; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.SuspendableService; @@ -94,14 +95,15 @@ public FhirEndpoint> getEndpoi * (and potentially handled) request further down a Camel route. * * @param payload FHIR request content - * @param headers headers + * @param inHeaders request parameters, e.g. search parameters + * @param outHeaders map where Camel response headers will be copied into * @param resultClass class of the result resource * @param Resource type being returned * @return result of processing the FHIR request in Camel */ @Override - public final R handleResourceRequest(Object payload, Map headers, Class resultClass) { - Object result = handleInRoute(payload, headers, resultClass); + public final R handleResourceRequest(Object payload, Map inHeaders, Map outHeaders, Class resultClass) { + Object result = handleInRoute(payload, inHeaders, outHeaders, resultClass); if (result == null) return null; if (resultClass.isAssignableFrom(result.getClass())) { return resultClass.cast(result); @@ -113,28 +115,29 @@ public final R handleResourceRequest(Object payload, M /** * @param payload request payload - * @param headers request parameters, e.g. search parameters + * @param inHeaders request parameters, e.g. search parameters + * @param outHeaders map where Camel response headers will be copied into * @param resource type * @return list of resources to be packaged into a bundle */ @Override - public List handleBundleRequest(Object payload, Map headers) { - return handleInRoute(payload, headers, List.class); + public List handleBundleRequest(Object payload, Map inHeaders, Map outHeaders) { + return handleInRoute(payload, inHeaders, outHeaders, List.class); } @Override - public IBundleProvider handleBundleProviderRequest(Object payload, Map headers) { - return getBundleProvider(payload, headers); + public IBundleProvider handleBundleProviderRequest(Object payload, Map headers, HttpServletResponse httpServletResponse) { + return getBundleProvider(payload, headers, httpServletResponse); } @Override - public T handleTransactionRequest(Object payload, Map headers, Class bundleClass) { - return handleInRoute(payload, headers, bundleClass); + public T handleTransactionRequest(Object payload, Map inHeaders, Map outHeaders, Class bundleClass) { + return handleInRoute(payload, inHeaders, outHeaders, bundleClass); } @Override - public MethodOutcome handleAction(Object payload, Map headers) { - return handleInRoute(payload, headers, MethodOutcome.class); + public MethodOutcome handleAction(Object payload, Map inHeaders, Map outHeaders) { + return handleInRoute(payload, inHeaders, outHeaders, MethodOutcome.class); } @Override @@ -157,12 +160,13 @@ public boolean supportsLazyLoading() { * Forwards the request to be handled into a Camel route * * @param payload request payload, will become the Camel message body - * @param headers request parameters, will be added to the Camel headers + * @param inHeaders request parameters, will be added to the Camel headers + * @param outHeaders map where Camel response headers will be copied into * @param resultClass expected body type to be returned * @return request result, type-converted into the required result class */ - protected T handleInRoute(Object payload, Map headers, Class resultClass) { - var exchange = runRoute(payload, headers); + protected T handleInRoute(Object payload, Map inHeaders, Map outHeaders, Class resultClass) { + var exchange = runRoute(payload, inHeaders); var resultMessage = exchange.getMessage(); if (resultMessage.getBody() instanceof List && IBaseResource.class.isAssignableFrom(resultClass)) { var singletonList = (List)resultMessage.getBody(); @@ -171,6 +175,7 @@ protected T handleInRoute(Object payload, Map headers, Class } resultMessage.setBody(singletonList.isEmpty() ? null : singletonList.get(0)); } + outHeaders.putAll(resultMessage.getHeaders()); return getEndpoint().getCamelContext().getTypeConverter().convertTo(resultClass, exchange, resultMessage.getBody()); } @@ -215,16 +220,19 @@ protected Exchange runRoute(Object payload, Map headers) { * @param headers request headers * @return resulting bundle provider */ - protected IBundleProvider getBundleProvider(Object payload, Map headers) { + protected IBundleProvider getBundleProvider(Object payload, Map headers, HttpServletResponse httpServletResponse) { var endpointConfiguration = getEndpoint().getInterceptableConfiguration(); return supportsLazyLoading() ? new LazyBundleProvider(this, endpointConfiguration.isCacheBundles(), endpointConfiguration.isSort(), payload, - headers) : + headers, + httpServletResponse) : new EagerBundleProvider(this, endpointConfiguration.isSort(), - payload, headers); + payload, + headers, + httpServletResponse); } } diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/LoggingInterceptorFactory.java b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/LoggingInterceptorFactory.java new file mode 100644 index 0000000000..3f66e46449 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/LoggingInterceptorFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 the original author or authors. + * + * 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 org.openehealth.ipf.platform.camel.ihe.fhir.chppqm; + +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.apache.camel.Exchange; +import org.openehealth.ipf.platform.camel.ihe.fhir.core.FhirEndpoint; +import org.openehealth.ipf.platform.camel.ihe.fhir.core.HapiClientInterceptorFactory; + +public class LoggingInterceptorFactory implements HapiClientInterceptorFactory { + + @Override + public IClientInterceptor newInstance(FhirEndpoint endpoint, Exchange exchange) { + LoggingInterceptor interceptor = new LoggingInterceptor(false); + interceptor.setLogResponseHeaders(true); + return interceptor; + } + +} diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3Test.java b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3Test.java index 1fa684e1a3..e5d809d189 100644 --- a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3Test.java +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3Test.java @@ -37,8 +37,7 @@ import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.openehealth.ipf.commons.ihe.fhir.chppqm.ChPpqmConsentCreator.create201Consent; import static org.openehealth.ipf.commons.ihe.fhir.chppqm.ChPpqmConsentCreator.createUuid; @@ -90,6 +89,8 @@ public void testCreate1() throws Exception { MethodOutcome methodOutcome = exchange.getMessage().getMandatoryBody(MethodOutcome.class); assertTrue(methodOutcome.getCreated()); + Map> httpHeaders = methodOutcome.getResponseHeaders(); + assertEquals(List.of("value1", "value2"), httpHeaders.get("responseheader2")); List auditMessages = auditSender.getMessages(); assertEquals(2, auditMessages.size()); diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3TestRouteBuilder.java b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3TestRouteBuilder.java index a60d2fc19d..607e89a0d9 100644 --- a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3TestRouteBuilder.java +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq3/ChPpq3TestRouteBuilder.java @@ -47,7 +47,9 @@ public void configure() { //Consent request = exchange.getMessage().getMandatoryBody(Consent.class); log.info("Method = {}", exchange.getMessage().getHeader(Constants.HTTP_METHOD)); exchange.getMessage().setBody(new MethodOutcome(new IdType(UUID.randomUUID().toString()))); - exchange.getMessage().setHeader(Constants.HTTP_OUTGOING_HEADERS, Map.of("TraceParent", List.of(TRACE_CONTEXT_ID))); + exchange.getMessage().setHeader(Constants.HTTP_OUTGOING_HEADERS, Map.of( + "TraceParent", List.of(TRACE_CONTEXT_ID), + "ResponseHeader2", List.of("value1", "value2"))); }) .process(itiResponseValidator()); } diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5Test.java b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5Test.java index 3f53fe4422..1ac5b5c3f9 100644 --- a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5Test.java +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5Test.java @@ -92,7 +92,7 @@ public void test2() throws Exception { exchange.getMessage().setBody(criteria); exchange.getMessage().setHeader(Constants.HTTP_METHOD, "POST"); exchange.getMessage().setHeader(Constants.HTTP_OUTGOING_HEADERS, Map.of("Connection", List.of("close"))); - exchange = producerTemplate.send("ch-ppq5://localhost:" + DEMO_APP_PORT, exchange); + exchange = producerTemplate.send("ch-ppq5://localhost:" + DEMO_APP_PORT + "?hapiClientInterceptorFactories=#loggingInterceptorFactory", exchange); Exception exception = Exchanges.extractException(exchange); if (exception != null) { throw exception; diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5TestRouteBuilder.java b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5TestRouteBuilder.java index f0c6db1049..20671a72f4 100644 --- a/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5TestRouteBuilder.java +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/chppqm/chppq5/ChPpq5TestRouteBuilder.java @@ -23,6 +23,7 @@ import org.openehealth.ipf.platform.camel.core.adapter.ValidatorAdapter; import java.util.List; +import java.util.Map; import static org.openehealth.ipf.commons.ihe.fhir.chppqm.ChPpqmConsentCreator.*; import static org.openehealth.ipf.platform.camel.ihe.fhir.core.FhirCamelValidators.*; @@ -45,6 +46,8 @@ public void configure() { consents.forEach(consent -> consent.setId(createUuid())); exchange.getMessage().setBody(consents); + exchange.getMessage().setHeader(Constants.HTTP_OUTGOING_HEADERS, Map.of( + "ResponseHeader2", List.of("value1", "value2"))); }) .process(itiResponseValidator()); } diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/ch-ppq5.xml b/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/ch-ppq5.xml index a18d9bcef6..7ad5b2231e 100644 --- a/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/ch-ppq5.xml +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/ch-ppq5.xml @@ -25,4 +25,6 @@ http://www.springframework.org/schema/beans/spring-beans.xsd"> class="org.openehealth.ipf.platform.camel.ihe.fhir.chppqm.chppq5.ChPpq5TestRouteBuilder"> + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/log4j2.xml b/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/log4j2.xml index 8a26fafae2..f0bc1ecbab 100644 --- a/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/log4j2.xml +++ b/platform-camel/ihe/fhir/r4/chppqm/src/test/resources/log4j2.xml @@ -26,6 +26,7 @@ + diff --git a/src/site/changes.xml b/src/site/changes.xml index 38da2ae700..0e891f4655 100644 --- a/src/site/changes.xml +++ b/src/site/changes.xml @@ -34,6 +34,9 @@ Added missing type converter for PDQv3 continuation protocol. + + Added a possibility to set outgoing HTTP headers in FHIR consumers. + Add the possibility to use custom additional URL parameters on a ITI SOAP calls, using the new interceptor type CustomUrlParametersOutInterceptor.