Add ability for resource source to be specified by header

This commit is contained in:
James Agnew 2019-10-23 21:02:07 -05:00
parent 0d3f7d4a03
commit 385a885abf
16 changed files with 807 additions and 434 deletions

View File

@ -28,6 +28,7 @@ public class Constants {
public static final String CT_TEXT_CSV = "text/csv"; public static final String CT_TEXT_CSV = "text/csv";
public static final String HEADER_REQUEST_ID = "X-Request-ID"; public static final String HEADER_REQUEST_ID = "X-Request-ID";
public static final String HEADER_REQUEST_SOURCE = "X-Request-Source";
public static final String CACHE_CONTROL_MAX_RESULTS = "max-results"; public static final String CACHE_CONTROL_MAX_RESULTS = "max-results";
public static final String CACHE_CONTROL_NO_CACHE = "no-cache"; public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
public static final String CACHE_CONTROL_NO_STORE = "no-store"; public static final String CACHE_CONTROL_NO_STORE = "no-store";
@ -242,6 +243,15 @@ public class Constants {
* Operation name for the $lastn operation * Operation name for the $lastn operation
*/ */
public static final String OPERATION_LASTN = "$lastn"; public static final String OPERATION_LASTN = "$lastn";
/**
* <p>
* This extension represents the equivalent of the
* <code>Resource.meta.source</code> field within R4+ resources, and is for
* use in DSTU3 resources. It should contain a value of type <code>uri</code>
* and will be located on the Resource.meta
* </p>
*/
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
static { static {
CHARSET_UTF8 = StandardCharsets.UTF_8; CHARSET_UTF8 = StandardCharsets.UTF_8;

View File

@ -23,9 +23,9 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBase; import ca.uhn.fhir.context.FhirVersionEnum;
import org.hl7.fhir.instance.model.api.IBaseMetaType; import ca.uhn.fhir.rest.api.Constants;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.*;
import java.util.List; import java.util.List;
@ -46,6 +46,30 @@ public class MetaUtil {
return retVal; return retVal;
} }
/**
* Sets the value for <code>Resource.meta.source</code> for R4+ resources, and places the value in
* an extension on <code>Resource.meta</code>
* with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> for DSTU3
* and below.
*
* @param theContext The FhirContext object
* @param theResource The resource to modify
* @param theValue The source URI
* @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
*/
@SuppressWarnings("unchecked")
public static void setSource(FhirContext theContext, IBaseResource theResource, String theValue) {
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
MetaUtil.setSource(theContext, theResource.getMeta(), theValue);
} else {
IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) theResource.getMeta()).addExtension();
sourceExtension.setUrl(Constants.EXT_META_SOURCE);
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("uri").newInstance();
value.setValue(theValue);
sourceExtension.setValue(value);
}
}
public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) { public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass()); BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source"); BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.AddRemoveCount; import ca.uhn.fhir.jpa.util.AddRemoveCount;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
@ -900,15 +899,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
+ (isNotBlank(provenanceRequestId) ? "#" : "") + (isNotBlank(provenanceRequestId) ? "#" : "")
+ defaultString(provenanceRequestId); + defaultString(provenanceRequestId);
if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { MetaUtil.setSource(myContext, retVal, sourceString);
IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) retVal.getMeta()).addExtension();
sourceExtension.setUrl(JpaConstants.EXT_META_SOURCE);
IPrimitiveType<String> value = (IPrimitiveType<String>) myContext.getElementDefinition("uri").newInstance();
value.setValue(sourceString);
sourceExtension.setValue(value);
} else if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
MetaUtil.setSource(myContext, retVal.getMeta(), sourceString);
}
} }
return retVal; return retVal;
@ -1078,7 +1070,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
source = ((IBaseHasExtensions) theResource.getMeta()) source = ((IBaseHasExtensions) theResource.getMeta())
.getExtension() .getExtension()
.stream() .stream()
.filter(t -> JpaConstants.EXT_META_SOURCE.equals(t.getUrl())) .filter(t -> Constants.EXT_META_SOURCE.equals(t.getUrl()))
.filter(t -> t.getValue() instanceof IPrimitiveType) .filter(t -> t.getValue() instanceof IPrimitiveType)
.map(t -> ((IPrimitiveType) t.getValue()).getValueAsString()) .map(t -> ((IPrimitiveType) t.getValue()).getValueAsString())
.findFirst() .findFirst()

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
@ -46,12 +45,12 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
when(mySrd.getRequestId()).thenReturn(requestId); when(mySrd.getRequestId()).thenReturn(requestId);
Patient pt0 = new Patient(); Patient pt0 = new Patient();
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:0"));
pt0.setActive(true); pt0.setActive(true);
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless(); IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1")); pt1.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:1"));
pt1.setActive(true); pt1.setActive(true);
IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
@ -62,7 +61,7 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
IBundleProvider result = myPatientDao.search(params); IBundleProvider result = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue())); assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
pt0 = (Patient) result.getResources(0, 1).get(0); pt0 = (Patient) result.getResources(0, 1).get(0);
assertEquals("urn:source:0#a_request_id", pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE)); assertEquals("urn:source:0#a_request_id", pt0.getMeta().getExtensionString(Constants.EXT_META_SOURCE));
// Search by request ID // Search by request ID
params = new SearchParameterMap(); params = new SearchParameterMap();
@ -87,17 +86,17 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
when(mySrd.getRequestId()).thenReturn(requestId); when(mySrd.getRequestId()).thenReturn(requestId);
Patient pt0 = new Patient(); Patient pt0 = new Patient();
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:0"));
pt0.setActive(true); pt0.setActive(true);
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless(); IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1")); pt1.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:1"));
pt1.setActive(true); pt1.setActive(true);
IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient(); Patient pt2 = new Patient();
pt2.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:2")); pt2.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:2"));
pt2.setActive(true); pt2.setActive(true);
myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
@ -118,17 +117,17 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
when(mySrd.getRequestId()).thenReturn(requestId); when(mySrd.getRequestId()).thenReturn(requestId);
Patient pt0 = new Patient(); Patient pt0 = new Patient();
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:0"));
pt0.setActive(true); pt0.setActive(true);
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless(); IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1")); pt1.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:1"));
pt1.setActive(true); pt1.setActive(true);
IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient(); Patient pt2 = new Patient();
pt2.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:2")); pt2.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:2"));
pt2.setActive(true); pt2.setActive(true);
myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
@ -148,7 +147,7 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
when(mySrd.getRequestId()).thenReturn(requestId); when(mySrd.getRequestId()).thenReturn(requestId);
Patient pt0 = new Patient(); Patient pt0 = new Patient();
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:0"));
pt0.setActive(true); pt0.setActive(true);
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless(); IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
@ -166,19 +165,19 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
public void testSourceNotPreservedAcrossUpdate() { public void testSourceNotPreservedAcrossUpdate() {
Patient pt0 = new Patient(); Patient pt0 = new Patient();
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:0"));
pt0.setActive(true); pt0.setActive(true);
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless(); IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
pt0 = myPatientDao.read(pt0id); pt0 = myPatientDao.read(pt0id);
assertEquals("urn:source:0", pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE)); assertEquals("urn:source:0", pt0.getMeta().getExtensionString(Constants.EXT_META_SOURCE));
pt0.getMeta().getExtension().clear(); pt0.getMeta().getExtension().clear();
pt0.setActive(false); pt0.setActive(false);
myPatientDao.update(pt0); myPatientDao.update(pt0);
pt0 = myPatientDao.read(pt0id.withVersion("2")); pt0 = myPatientDao.read(pt0id.withVersion("2"));
assertEquals(null, pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE)); assertEquals(null, pt0.getMeta().getExtensionString(Constants.EXT_META_SOURCE));
} }
@ -188,19 +187,19 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
when(mySrd.getRequestId()).thenReturn("0000000000000000"); when(mySrd.getRequestId()).thenReturn("0000000000000000");
Patient pt0 = new Patient(); Patient pt0 = new Patient();
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:0"));
pt0.setActive(true); pt0.setActive(true);
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless(); IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
pt0 = myPatientDao.read(pt0id); pt0 = myPatientDao.read(pt0id);
assertEquals(null, pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE)); assertEquals(null, pt0.getMeta().getExtensionString(Constants.EXT_META_SOURCE));
pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1")); pt0.getMeta().addExtension(Constants.EXT_META_SOURCE, new StringType("urn:source:1"));
pt0.setActive(false); pt0.setActive(false);
myPatientDao.update(pt0); myPatientDao.update(pt0);
pt0 = myPatientDao.read(pt0id.withVersion("2")); pt0 = myPatientDao.read(pt0id.withVersion("2"));
assertEquals(null, pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE)); assertEquals(null, pt0.getMeta().getExtensionString(Constants.EXT_META_SOURCE));
// Search without source param // Search without source param
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.r4.ResourceProviderR4Test; import ca.uhn.fhir.jpa.provider.r4.ResourceProviderR4Test;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
@ -3952,7 +3951,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
{ {
Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute();
assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+")); assertThat(readPatient.getMeta().getExtensionString(Constants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+"));
} }
patient.setId(patientid); patient.setId(patientid);
@ -3960,12 +3959,12 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
ourClient.update().resource(patient).execute(); ourClient.update().resource(patient).execute();
{ {
Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute();
assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+")); assertThat(readPatient.getMeta().getExtensionString(Constants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+"));
readPatient.addName().setFamily("testUpdateWithSource"); readPatient.addName().setFamily("testUpdateWithSource");
ourClient.update().resource(readPatient).execute(); ourClient.update().resource(readPatient).execute();
readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute();
assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+")); assertThat(readPatient.getMeta().getExtensionString(Constants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+"));
} }
} }

View File

@ -243,16 +243,6 @@ public class JpaConstants {
*/ */
public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED"; public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED";
/**
* <p>
* This extension represents the equivalent of the
* <code>Resource.meta.source</code> field within R4+ resources, and is for
* use in DSTU3 resources. It should contain a value of type <code>uri</code>
* and will be located on the Resource.meta
* </p>
*/
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
/** /**
* Parameter for the $export operation * Parameter for the $export operation
*/ */

View File

@ -370,7 +370,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage(), e); throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage(), e);
} }
if (count == 0) { if (count == 0) {
throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName()); throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getName());
} }
} }

View File

@ -0,0 +1,66 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.util.MetaUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This interceptor examines a header on the incoming request and places it in
* <code>Resource.meta.source</code> (R4 and above) or in an extension on <code>Resource.meta</code>
* with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> (DSTU3 and below).
* <p>
* This interceptor does not support versions of FHIR below DSTU3.
* </p>
*
* @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
*/
@Interceptor
public class CaptureResourceSourceFromHeaderInterceptor {
private static final Logger ourLog = LoggerFactory.getLogger(CaptureResourceSourceFromHeaderInterceptor.class);
private final FhirContext myFhirContext;
private String myHeaderName;
public CaptureResourceSourceFromHeaderInterceptor(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
setHeaderName(Constants.HEADER_REQUEST_SOURCE);
}
/**
* Provides the header name to examine in incoming requests. Default is {@link ca.uhn.fhir.rest.api.Constants#HEADER_REQUEST_SOURCE "X-Request-Source"}.
*/
@SuppressWarnings("WeakerAccess")
public String getHeaderName() {
return myHeaderName;
}
/**
* Provides the header name to examine in incoming requests. Default is {@link ca.uhn.fhir.rest.api.Constants#HEADER_REQUEST_SOURCE "X-Request-Source"}.
*/
@SuppressWarnings("WeakerAccess")
public void setHeaderName(String theHeaderName) {
myHeaderName = theHeaderName;
}
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
public void extractSource(RequestDetails theRequestDetails) {
IBaseResource resource = theRequestDetails.getResource();
if (resource != null) {
String requestSource = theRequestDetails.getHeader(getHeaderName());
if (isNotBlank(requestSource)) {
ourLog.trace("Setting Meta.source to \"{}\" because of header \"{}\"", requestSource, getHeaderName());
MetaUtil.setSource(myFhirContext, resource, requestSource);
}
}
}
}

View File

@ -126,6 +126,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
return new MethodOutcome() return new MethodOutcome()
.setCreated(true) .setCreated(true)
.setResource(theResource)
.setId(theResource.getIdElement()); .setId(theResource.getIdElement());
} }
@ -373,6 +374,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
return new MethodOutcome() return new MethodOutcome()
.setCreated(created) .setCreated(created)
.setResource(theResource)
.setId(theResource.getIdElement()); .setId(theResource.getIdElement());
} }
@ -416,6 +418,19 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
return theResource.getIdElement(); return theResource.getIdElement();
} }
/**
* Returns an unmodifiable list containing the current version of all resources stored in this provider
*
* @since 4.1.0
*/
public List<T> getStoredResources() {
List<T> retVal = new ArrayList<>();
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
retVal.add(next.lastEntry().getValue());
}
return Collections.unmodifiableList(retVal);
}
private static <T extends IBaseResource> T fireInterceptorsAndFilterAsNeeded(T theResource, RequestDetails theRequestDetails) { private static <T extends IBaseResource> T fireInterceptorsAndFilterAsNeeded(T theResource, RequestDetails theRequestDetails) {
List<T> output = fireInterceptorsAndFilterAsNeeded(Lists.newArrayList(theResource), theRequestDetails); List<T> output = fireInterceptorsAndFilterAsNeeded(Lists.newArrayList(theResource), theRequestDetails);
if (output.size() == 1) { if (output.size() == 1) {

View File

@ -87,9 +87,8 @@ public class SearchR4Test {
* A paging request that incorrectly executes at the type level shouldn't be grabbed by the search method binding * A paging request that incorrectly executes at the type level shouldn't be grabbed by the search method binding
*/ */
@Test @Test
public void tesPageRequestCantTriggerSearchAccidentally() throws Exception { public void testPageRequestCantTriggerSearchAccidentally() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Constants.PARAM_PAGINGACTION + "=12345"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Constants.PARAM_PAGINGACTION + "=12345");
Bundle bundle;
try (CloseableHttpResponse status = ourClient.execute(httpGet)) { try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent); ourLog.info(responseContent);

View File

@ -0,0 +1,102 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule;
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class CaptureResourceSourceFromHeaderInterceptorTest {
private static FhirContext ourCtx = FhirContext.forR4();
@ClassRule
public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx);
private CaptureResourceSourceFromHeaderInterceptor myInterceptor;
@Rule
public HashMapResourceProviderRule<Patient> myPatientProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class);
@Before
public void before() {
myInterceptor = new CaptureResourceSourceFromHeaderInterceptor(ourCtx);
ourServerRule.getRestfulServer().registerInterceptor(myInterceptor);
}
@After
public void after() {
ourServerRule.getRestfulServer().unregisterInterceptor(myInterceptor);
}
@Test
public void testCreateWithoutAnything() {
Patient resource = new Patient();
resource.setActive(true);
ourServerRule.getFhirClient().create().resource(resource).execute();
Patient stored = myPatientProviderRule.getStoredResources().get(0);
assertNull(stored.getMeta().getSource());
}
@Test
public void testCreateWithSource() {
Patient resource = new Patient();
resource.setActive(true);
resource.getMeta().setSource("http://source");
ourServerRule.getFhirClient().create().resource(resource).execute();
Patient stored = myPatientProviderRule.getStoredResources().get(0);
assertEquals("http://source", stored.getMeta().getSource());
}
@Test
public void testCreateWithHeader() {
Patient resource = new Patient();
resource.setActive(true);
ourServerRule
.getFhirClient()
.create()
.resource(resource)
.withAdditionalHeader(Constants.HEADER_REQUEST_SOURCE, "http://header")
.execute();
Patient stored = myPatientProviderRule.getStoredResources().get(0);
assertEquals("http://header", stored.getMeta().getSource());
}
@Test
public void testCreateWithBoth() {
Patient resource = new Patient();
resource.setActive(true);
resource.getMeta().setSource("http://source");
ourServerRule
.getFhirClient()
.create()
.resource(resource)
.withAdditionalHeader(Constants.HEADER_REQUEST_SOURCE, "http://header")
.execute();
Patient stored = myPatientProviderRule.getStoredResources().get(0);
assertEquals("http://header", stored.getMeta().getSource());
}
@Test
public void testNonCreateShouldntFail() {
Bundle bundle = ourServerRule
.getFhirClient()
.search()
.forResource(Patient.class)
.returnBundle(Bundle.class)
.execute();
assertEquals(0, bundle.getEntry().size());
}
}

View File

@ -21,6 +21,16 @@
<artifactId>hapi-fhir-base</artifactId> <artifactId>hapi-fhir-base</artifactId>
<version>4.1.0-SNAPSHOT</version> <version>4.1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server</artifactId>
<version>4.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>
<version>4.1.0-SNAPSHOT</version>
</dependency>
<!-- General --> <!-- General -->
<dependency> <dependency>
@ -53,6 +63,16 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
</dependency> </dependency>
<!-- Jetty -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,42 @@
package ca.uhn.fhir.test.utilities.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class HashMapResourceProviderRule<T extends IBaseResource> extends HashMapResourceProvider<T> implements TestRule {
private final RestfulServerRule myRestfulServerRule;
/**
* Constructor
*
* @param theFhirContext The FHIR context
* @param theResourceType The resource type to support
*/
public HashMapResourceProviderRule(RestfulServerRule theRestfulServerRule, Class<T> theResourceType) {
super(theRestfulServerRule.getFhirContext(), theResourceType);
myRestfulServerRule = theRestfulServerRule;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
clear();
myRestfulServerRule.getRestfulServer().registerProvider(HashMapResourceProviderRule.this);
try {
base.evaluate();
} finally {
myRestfulServerRule.getRestfulServer().unregisterProvider(HashMapResourceProviderRule.this);
}
}
};
}
}

View File

@ -0,0 +1,95 @@
package ca.uhn.fhir.test.utilities.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.BeforeClass;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
public class RestfulServerRule implements TestRule {
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerRule.class);
private final FhirContext myFhirContext;
private final Object[] myProviders;
private Server myServer;
private RestfulServer myServlet;
private int myPort;
private CloseableHttpClient myHttpClient;
private IGenericClient myFhirClient;
public RestfulServerRule(FhirContext theFhirContext, Object... theProviders) {
myFhirContext = theFhirContext;
myProviders = theProviders;
}
@Override
public Statement apply(Statement theBase, Description theDescription) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
startServer();
theBase.evaluate();
stopServer();
}
};
}
private void stopServer() throws Exception {
JettyUtil.closeServer(myServer);
myHttpClient.close();
}
@BeforeClass
private void startServer() throws Exception {
myServer = new Server(0);
ServletHandler servletHandler = new ServletHandler();
myServlet = new RestfulServer(myFhirContext);
myServlet.setDefaultPrettyPrint(true);
myServlet.registerProviders(myProviders);
ServletHolder servletHolder = new ServletHolder(myServlet);
servletHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(servletHandler);
myServer.start();
myPort = JettyUtil.getPortForStartedServer(myServer);
ourLog.info("Server has started on port {}", myPort);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
myHttpClient = builder.build();
myFhirContext.getRestfulClientFactory().setSocketTimeout((int) (500 * DateUtils.MILLIS_PER_SECOND));
myFhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myFhirClient = myFhirContext.newRestfulGenericClient("http://localhost:" + myPort);
}
public IGenericClient getFhirClient() {
return myFhirClient;
}
public FhirContext getFhirContext() {
return myFhirContext;
}
public RestfulServer getRestfulServer() {
return myServlet;
}
}

View File

@ -450,6 +450,14 @@
Several issues with HAPI FHIR's annotation scanner that prevented use with Kotlin based Several issues with HAPI FHIR's annotation scanner that prevented use with Kotlin based
resource providers have been corrected. Thanks to Jelmer ter Wal for the pull request! resource providers have been corrected. Thanks to Jelmer ter Wal for the pull request!
</action> </action>
<action type="add">
A new built-in server interceptor called
<![CDATA[<code>CaptureResourceSourceFromHeaderInterceptor</code>]]>
has been added.
This interceptor can be used to capture an incoming source system URI in an HTTP Request
Header and automatically place it in
<![CDATA[<code>Resource.meta.source</code>]]>
</action>
</release> </release>
<release version="4.0.3" date="2019-09-03" description="Igloo (Point Release)"> <release version="4.0.3" date="2019-09-03" description="Igloo (Point Release)">
<action type="fix"> <action type="fix">

View File

@ -313,6 +313,18 @@
</subsection> </subsection>
<subsection name="Capturing Meta.source from an HTTP Header">
<p>
If you wish to override the value of <code>Resource.meta.source</code> using the value
supplied in an HTTP header, you can use the
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html">CaptureResourceSourceFromHeaderInterceptor</a>
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html">code</a>)
to accomplish this.
</p>
</subsection>
</section> </section>
<section name="Creating Interceptors"> <section name="Creating Interceptors">