Skip to content

Commit

Permalink
#458: Possibility to set outgoing HTTP headers in FHIR consumers
Browse files Browse the repository at this point in the history
  • Loading branch information
unixoid committed Dec 23, 2024
1 parent c51651b commit c95af33
Show file tree
Hide file tree
Showing 18 changed files with 179 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,16 +36,18 @@ public abstract class AbstractBundleProvider implements IBundleProvider {
private final Object payload;
private final Map<String, Object> headers;
private final boolean sort;
protected final HttpServletResponse httpServletResponse;

public AbstractBundleProvider(RequestConsumer consumer, Object payload, Map<String, Object> headers) {
this(consumer, false, payload, headers);
public AbstractBundleProvider(RequestConsumer consumer, Object payload, Map<String, Object> headers, HttpServletResponse httpServletResponse) {
this(consumer, false, payload, headers, httpServletResponse);
}

public AbstractBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map<String, Object> headers) {
public AbstractBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map<String, Object> headers, HttpServletResponse httpServletResponse) {
this.consumer = consumer;
this.payload = payload;
this.headers = headers;
this.sort = sort;
this.httpServletResponse = httpServletResponse;
}

@Override
Expand All @@ -57,8 +60,11 @@ public Integer preferredPageSize() {
return null;
}

protected List<IBaseResource> obtainResources(Object payload, Map<String, Object> parameters) {
return consumer.handleBundleRequest(payload, parameters);
protected List<IBaseResource> obtainResources(Object payload, Map<String, Object> inHeaders) {
HashMap<String, Object> outHeaders = new HashMap<>();
List<IBaseResource> resources = consumer.handleBundleRequest(payload, inHeaders, outHeaders);
FhirProvider.processOutHeaders(outHeaders, httpServletResponse);
return resources;
}

protected RequestConsumer getConsumer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -91,8 +93,11 @@ protected final <R extends IBaseResource> 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<String, Object>();
R resource = consumer.handleResourceRequest(payload, inHeaders, outHeaders, resultType);
processOutHeaders(outHeaders, httpServletResponse);
return resource;
}

/**
Expand All @@ -114,17 +119,20 @@ protected final <R extends IBaseResource> List<R> 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<String, Object>();
List<R> 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
Expand All @@ -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;
}

/**
Expand All @@ -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<String, Object>();
MethodOutcome outcome = consumer.handleAction(payload, inHeaders, outHeaders);
processOutHeaders(outHeaders, httpServletResponse);
return outcome;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -33,12 +34,12 @@ public class EagerBundleProvider extends AbstractBundleProvider {

private transient List<IBaseResource> resources;

public EagerBundleProvider(RequestConsumer consumer, Object payload, Map<String, Object> headers) {
this(consumer, false, payload, headers);
public EagerBundleProvider(RequestConsumer consumer, Object payload, Map<String, Object> headers, HttpServletResponse httpServletResponse) {
this(consumer, false, payload, headers, httpServletResponse);
}

public EagerBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map<String, Object> headers) {
super(consumer, sort, payload, headers);
public EagerBundleProvider(RequestConsumer consumer, boolean sort, Object payload, Map<String, Object> headers, HttpServletResponse httpServletResponse) {
super(consumer, sort, payload, headers, httpServletResponse);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand Down Expand Up @@ -140,4 +142,17 @@ private static Map<String, List<String>> extractHttpHeaders(HttpServletRequest h
}
return result;
}

public static void processOutHeaders(Map<String, Object> outHeaders, HttpServletResponse httpServletResponse) {
var httpHeadersObject = outHeaders.get(Constants.HTTP_OUTGOING_HEADERS);
if (httpHeadersObject instanceof Map) {
var headers = (Map<String, List<String>>) httpHeadersObject;
for (var entry : headers.entrySet()) {
for (var value : entry.getValue()) {
httpServletResponse.addHeader(entry.getKey(), value);
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Object> headers) {
this(consumer, cacheResults, false, payload, headers);
public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, Object payload, Map<String, Object> headers, HttpServletResponse httpServletResponse) {
this(consumer, cacheResults, false, payload, headers, httpServletResponse);
}

/**
Expand All @@ -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<String, Object> headers) {
super(consumer, sort, payload, headers);
public LazyBundleProvider(RequestConsumer consumer, boolean cacheResults, boolean sort, Object payload, Map<String, Object> headers, HttpServletResponse httpServletResponse) {
super(consumer, sort, payload, headers, httpServletResponse);
this.cacheResults = cacheResults;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -37,7 +38,7 @@
* on searches.
* <ul>
* <li>{@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.</li>
* <li>{@link Constants#FHIR_FROM_INDEX} and {@link Constants#FHIR_TO_INDEX}: if these entries are present,
Expand Down Expand Up @@ -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<String, Object> headers);
MethodOutcome handleAction(Object payload, Map<String, Object> inHeaders, Map<String, Object> 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 <R> type of the returned resource
* @return resource to be returned
*/
<R extends IBaseResource> R handleResourceRequest(Object payload, Map<String, Object> headers, Class<R> resultType);
<R extends IBaseResource> R handleResourceRequest(Object payload, Map<String, Object> inHeaders, Map<String, Object> outHeaders, Class<R> resultType);

/**
* Handles the (search) request for a bundle, effectively being a list of resources.
Expand All @@ -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.
* </p>
*
* @param payload request payload
* @param headers request parameters, e.g. search parameters or
* @param <R> 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 <R> type of the returned resources contained in the bundle
* @return list of resources to be returned
*/
<R extends IBaseResource> List<R> handleBundleRequest(Object payload, Map<String, Object> headers);
<R extends IBaseResource> List<R> handleBundleRequest(Object payload, Map<String, Object> inHeaders, Map<String, Object> 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<String, Object> headers);
IBundleProvider handleBundleProviderRequest(Object payload, Map<String, Object> 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 extends IBaseBundle> T handleTransactionRequest(Object payload, Map<String, Object> headers, Class<T> bundleClass);
<T extends IBaseBundle> T handleTransactionRequest(Object payload, Map<String, Object> inHeaders, Map<String, Object> outHeaders, Class<T> bundleClass);

/**
* Optional method that request the result size of a bundle request. Only used for lazy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -94,8 +95,11 @@ protected final <T extends IBaseBundle> 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<String, Object>();
T bundle = consumer.handleTransactionRequest(payload, inHeaders, outHeaders, bundleClass);
processOutHeaders(outHeaders, httpServletResponse);
return bundle;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,20 +49,20 @@ public void setup() {
}
var payload = new Object();
var headers = new HashMap<String, Object>();
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);
}

@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);
Expand Down
Loading

0 comments on commit c95af33

Please sign in to comment.