mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-16 18:05:19 +00:00
Add ability for resource source to be specified by header
This commit is contained in:
parent
0d3f7d4a03
commit
385a885abf
@ -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;
|
||||||
|
@ -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");
|
||||||
|
@ -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()
|
||||||
|
@ -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();
|
||||||
|
@ -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]+"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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">
|
||||||
|
@ -1,385 +1,397 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<title>Server Interceptors</title>
|
<title>Server Interceptors</title>
|
||||||
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<!-- The body of the document contains a number of sections -->
|
<!-- The body of the document contains a number of sections -->
|
||||||
<section name="Server Interceptors">
|
<section name="Server Interceptors">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The RESTful server provides a powerful mechanism for adding cross-cutting behaviour
|
The RESTful server provides a powerful mechanism for adding cross-cutting behaviour
|
||||||
(e.g. requests, such as authnorization, auditing, fancy output, logging, etc.)
|
(e.g. requests, such as authnorization, auditing, fancy output, logging, etc.)
|
||||||
to each incoming request that it processes. This mechanism consists of defining one or
|
to each incoming request that it processes. This mechanism consists of defining one or
|
||||||
more <b>interceptors</b> that will be invoked at defined points in the processing of
|
more <b>interceptors</b> that will be invoked at defined points in the processing of
|
||||||
each incoming request.
|
each incoming request.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<img src="svg/restful-server-interceptors.svg" alt="Interceptors"/>
|
<img src="svg/restful-server-interceptors.svg" alt="Interceptors"/>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Interceptors will intercept the incoming request, and can take action such as
|
Interceptors will intercept the incoming request, and can take action such as
|
||||||
logging or auditing it, or examining/injecting headers. They can optionally choose
|
logging or auditing it, or examining/injecting headers. They can optionally choose
|
||||||
to handle the request directly and the cancel any subsequent processing (in other words,
|
to handle the request directly and the cancel any subsequent processing (in other words,
|
||||||
the interceptor can choose to supply a response to the client, and can then signal
|
the interceptor can choose to supply a response to the client, and can then signal
|
||||||
to the server that it does not need to do so).
|
to the server that it does not need to do so).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Interceptors
|
Interceptors
|
||||||
may also be notified of responses prior to those responses being served to a client,
|
may also be notified of responses prior to those responses being served to a client,
|
||||||
and may audit or even cancel the response. The diagram on the right shows the
|
and may audit or even cancel the response. The diagram on the right shows the
|
||||||
lifecycle of a normal (non failing) request which is subject to an interceptor.
|
lifecycle of a normal (non failing) request which is subject to an interceptor.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Interceptors must implement the
|
Interceptors must implement the
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html">IServerInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html">IServerInterceptor</a>
|
||||||
interface (or extend the convenience
|
interface (or extend the convenience
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.html">InterceptorAdapter</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.html">InterceptorAdapter</a>
|
||||||
class provided). The RESTful server will normally invoke the interceptor at several
|
class provided). The RESTful server will normally invoke the interceptor at several
|
||||||
points in the execution of the client request.
|
points in the execution of the client request.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Before any processing at all is performed on the request,
|
Before any processing at all is performed on the request,
|
||||||
<b>incomingRequestPreProcessed</b> will be invoked. This can be useful
|
<b>incomingRequestPreProcessed</b> will be invoked. This can be useful
|
||||||
if you wish to handle some requests completely outside of HAPI's processing
|
if you wish to handle some requests completely outside of HAPI's processing
|
||||||
mechanism.
|
mechanism.
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
If this method returns <code>true</code>, processing continues to the
|
If this method returns <code>true</code>, processing continues to the
|
||||||
next interceptor, and ultimately to the next phase of processing.
|
next interceptor, and ultimately to the next phase of processing.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If this method returns <code>false</code>, processing stops immediately.
|
If this method returns <code>false</code>, processing stops immediately.
|
||||||
This is useful if the interceptor wishes to supply its own response
|
This is useful if the interceptor wishes to supply its own response
|
||||||
by directly calling methods on the <code>HttpServletResponse</code>
|
by directly calling methods on the <code>HttpServletResponse</code>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If this method throws any subclass of
|
If this method throws any subclass of
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>,
|
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>,
|
||||||
processing is stopped immedicately and the corresponding status is returned to the client.
|
processing is stopped immedicately and the corresponding status is returned to the client.
|
||||||
This is useful if an interceptor wishes to abort the request (e.g. because
|
This is useful if an interceptor wishes to abort the request (e.g. because
|
||||||
it did not detect valid credentials)
|
it did not detect valid credentials)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Once the request is classified (meaning that the URL and request headers are
|
Once the request is classified (meaning that the URL and request headers are
|
||||||
examined to determine exactly what kind of request is being made),
|
examined to determine exactly what kind of request is being made),
|
||||||
<b>incomingRequestPostProcessed</b> will be invoked. This method has
|
<b>incomingRequestPostProcessed</b> will be invoked. This method has
|
||||||
an additional parameter, the
|
an additional parameter, the
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/method/RequestDetails.html">RequestDetails</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/method/RequestDetails.html">RequestDetails</a>
|
||||||
object which contains details about what operation is about to be
|
object which contains details about what operation is about to be
|
||||||
called, and what request parameters were receievd with that request.
|
called, and what request parameters were receievd with that request.
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
If this method returns <code>true</code>, processing continues to the
|
If this method returns <code>true</code>, processing continues to the
|
||||||
next interceptor, and ultimately to the next phase of processing.
|
next interceptor, and ultimately to the next phase of processing.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If this method returns <code>false</code>, processing stops immediately.
|
If this method returns <code>false</code>, processing stops immediately.
|
||||||
This is useful if the interceptor wishes to supply its own response
|
This is useful if the interceptor wishes to supply its own response
|
||||||
by directly calling methods on the <code>HttpServletResponse</code>
|
by directly calling methods on the <code>HttpServletResponse</code>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If this method throws any subclass of
|
If this method throws any subclass of
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>,
|
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>,
|
||||||
processing is stopped immedicately and the corresponding status is returned to the client.
|
processing is stopped immedicately and the corresponding status is returned to the client.
|
||||||
This is useful if an interceptor wishes to abort the request (e.g. because
|
This is useful if an interceptor wishes to abort the request (e.g. because
|
||||||
it did not detect valid credentials)
|
it did not detect valid credentials)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Once the request is being handled,
|
Once the request is being handled,
|
||||||
<b>incomingRequestPreHandled</b> will be invoked. This method is useful in that
|
<b>incomingRequestPreHandled</b> will be invoked. This method is useful in that
|
||||||
it provides details about the FHIR operation being invoked (e.g. is this a "read" or a "create"? what
|
it provides details about the FHIR operation being invoked (e.g. is this a "read" or a "create"? what
|
||||||
is the resource type and ID of the resource being accessed, etc.). This method can be
|
is the resource type and ID of the resource being accessed, etc.). This method can be
|
||||||
useful for adding finer grained access controls. Note that <code>incomingRequestPreHandled</code>
|
useful for adding finer grained access controls. Note that <code>incomingRequestPreHandled</code>
|
||||||
is not able to directly supply a response, but it may throw a
|
is not able to directly supply a response, but it may throw a
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>
|
||||||
to abort processing.
|
to abort processing.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method),
|
After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method),
|
||||||
but before the actual response is returned to the client,
|
but before the actual response is returned to the client,
|
||||||
the <b>outgoingResponse</b> method is invoked.
|
the <b>outgoingResponse</b> method is invoked.
|
||||||
This method also has details about the request in its parameters, but also
|
This method also has details about the request in its parameters, but also
|
||||||
receives a copy of the response that is about to be returned. Note that
|
receives a copy of the response that is about to be returned. Note that
|
||||||
there are three implementations of <b>outgoingResponse</b>: The server
|
there are three implementations of <b>outgoingResponse</b>: The server
|
||||||
will invoke the one which corresponds to the response type
|
will invoke the one which corresponds to the response type
|
||||||
of the operation being invoked (resource, bundle, etc.)
|
of the operation being invoked (resource, bundle, etc.)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<br clear="all"/>
|
<br clear="all"/>
|
||||||
<subsection name="Exception Handling">
|
<subsection name="Exception Handling">
|
||||||
|
|
||||||
<img src="svg/restful-server-interceptors-exception.svg" alt="Interceptors" align="right"/>
|
<img src="svg/restful-server-interceptors-exception.svg" alt="Interceptors" align="right"/>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
In the event of an exception being thrown within the server, the interceptor
|
In the event of an exception being thrown within the server, the interceptor
|
||||||
method
|
method
|
||||||
<code><a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html#handleException(ca.uhn.fhir.rest.method.RequestDetails,%20java.lang.Throwable,%20javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse)">handleException</a></code>
|
<code><a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html#handleException(ca.uhn.fhir.rest.method.RequestDetails,%20java.lang.Throwable,%20javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse)">handleException</a></code>
|
||||||
will be called. This applies both to HAPI-FHIR defined exceptions thrown within resource provider methods
|
will be called. This applies both to HAPI-FHIR defined exceptions thrown within resource provider methods
|
||||||
you have created as well as unexpected exceptions such as NullPointerException thrown
|
you have created as well as unexpected exceptions such as NullPointerException thrown
|
||||||
at any point in the handling chain.
|
at any point in the handling chain.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
In general, you will want to return <code>true</code> from the <code>handleException</code>
|
In general, you will want to return <code>true</code> from the <code>handleException</code>
|
||||||
method, which means that processing continues normally (RestfulServer will return an
|
method, which means that processing continues normally (RestfulServer will return an
|
||||||
HTTP 4xx or 5xx response automatically depending on the specific exception which was thrown).
|
HTTP 4xx or 5xx response automatically depending on the specific exception which was thrown).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
However, you may override the server's built-in exception handling by returning
|
However, you may override the server's built-in exception handling by returning
|
||||||
<code>false</code>. In this case, you must provide your own response by
|
<code>false</code>. In this case, you must provide your own response by
|
||||||
interacting with the <code>HttpServletResponse</code> object which is
|
interacting with the <code>HttpServletResponse</code> object which is
|
||||||
passed in.
|
passed in.
|
||||||
</p>
|
</p>
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<br clear="all"/>
|
<br clear="all"/>
|
||||||
<subsection name="Registering Interceptors">
|
<subsection name="Registering Interceptors">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To register an interceptor to the server, simply call
|
To register an interceptor to the server, simply call
|
||||||
either <code>registerInterceptor</code> or <code>setInterceptors</code>
|
either <code>registerInterceptor</code> or <code>setInterceptors</code>
|
||||||
on your RestfulServer instance.
|
on your RestfulServer instance.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Note that order is important: The server will invoke
|
Note that order is important: The server will invoke
|
||||||
<code>incomingRequestPreProcessed</code> and <code>incomingRequestPostProcessed</code>
|
<code>incomingRequestPreProcessed</code> and <code>incomingRequestPostProcessed</code>
|
||||||
in the same order that they are registered to the server. The server will
|
in the same order that they are registered to the server. The server will
|
||||||
invoke <code>outgoingResponse</code> in the <b>reverse</b> order to the
|
invoke <code>outgoingResponse</code> in the <b>reverse</b> order to the
|
||||||
order in which the interceptors were registered. This means that interceptors
|
order in which the interceptors were registered. This means that interceptors
|
||||||
can be thought of as "wrapping" the request.
|
can be thought of as "wrapping" the request.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section name="Built In Interceptors">
|
<section name="Built In Interceptors">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
HAPI also provides built-in interceptors which may be useful. Links to the code for each interceptor
|
HAPI also provides built-in interceptors which may be useful. Links to the code for each interceptor
|
||||||
is also provided, to give examples of how interceptors are written.
|
is also provided, to give examples of how interceptors are written.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a name="Logging"/>
|
<a name="Logging"/>
|
||||||
<subsection name="Logging Server Requests">
|
<subsection name="Logging Server Requests">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The
|
The
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.html">LoggingInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.html">LoggingInterceptor</a>
|
||||||
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.html">code</a>)
|
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.html">code</a>)
|
||||||
can be used to generate a new log line (via SLF4j) for each incoming request. LoggingInterceptor
|
can be used to generate a new log line (via SLF4j) for each incoming request. LoggingInterceptor
|
||||||
provides a flexible message format that can be used to provide a customized level
|
provides a flexible message format that can be used to provide a customized level
|
||||||
of detail about each incoming request.
|
of detail about each incoming request.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The following example shows how to register a logging interceptor within
|
The following example shows how to register a logging interceptor within
|
||||||
a FHIR RESTful server.
|
a FHIR RESTful server.
|
||||||
</p>
|
</p>
|
||||||
<macro name="snippet">
|
<macro name="snippet">
|
||||||
<param name="id" value="loggingInterceptor" />
|
<param name="id" value="loggingInterceptor" />
|
||||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This interceptor will then produce output similar to the following:
|
This interceptor will then produce output similar to the following:
|
||||||
</p>
|
</p>
|
||||||
<source><![CDATA[2014-09-04 02:37:30.030 Source[127.0.0.1] Operation[vread Patient/1667/_history/1] UA[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.94 Safari/537.36] Params[?_format=json]
|
<source><![CDATA[2014-09-04 02:37:30.030 Source[127.0.0.1] Operation[vread Patient/1667/_history/1] UA[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.94 Safari/537.36] Params[?_format=json]
|
||||||
2014-09-04 03:30:00.443 Source[127.0.0.1] Operation[search-type Organization] UA[Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)] Params[]]]></source>
|
2014-09-04 03:30:00.443 Source[127.0.0.1] Operation[search-type Organization] UA[Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)] Params[]]]></source>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<a name="ExceptionHandlingInterceptor"/>
|
<a name="ExceptionHandlingInterceptor"/>
|
||||||
<subsection name="Exception Handling">
|
<subsection name="Exception Handling">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The
|
The
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.html">ExceptionHandlingInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.html">ExceptionHandlingInterceptor</a>
|
||||||
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.html">code</a>)
|
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.html">code</a>)
|
||||||
can be used to customize what is returned to the client and what is logged when the server throws an
|
can be used to customize what is returned to the client and what is logged when the server throws an
|
||||||
exception for any reason (including routine things like UnprocessableEntityExceptions thrown as a matter of
|
exception for any reason (including routine things like UnprocessableEntityExceptions thrown as a matter of
|
||||||
normal processing in a create method, but also including unexpected NullPointerExceptions thrown by client code).
|
normal processing in a create method, but also including unexpected NullPointerExceptions thrown by client code).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The following example shows how to register an exception handling interceptor within
|
The following example shows how to register an exception handling interceptor within
|
||||||
a FHIR RESTful server.
|
a FHIR RESTful server.
|
||||||
</p>
|
</p>
|
||||||
<macro name="snippet">
|
<macro name="snippet">
|
||||||
<param name="id" value="exceptionInterceptor" />
|
<param name="id" value="exceptionInterceptor" />
|
||||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Response Syntax Highlighting">
|
<subsection name="Response Syntax Highlighting">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The
|
The
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html">ResponseHighlighterInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html">ResponseHighlighterInterceptor</a>
|
||||||
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html">code</a>)
|
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html">code</a>)
|
||||||
detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead
|
detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead
|
||||||
of just the raw text. In other words, if a user uses a browser to request "http://foo/Patient/1" by typing
|
of just the raw text. In other words, if a user uses a browser to request "http://foo/Patient/1" by typing
|
||||||
this address into their URL bar, they will get nice formatted HTML back with a human readable version
|
this address into their URL bar, they will get nice formatted HTML back with a human readable version
|
||||||
of the content. This is helpful for testers.
|
of the content. This is helpful for testers.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
To see an example of how this looks, see our demo server using the following example
|
To see an example of how this looks, see our demo server using the following example
|
||||||
query:
|
query:
|
||||||
<a href="http://fhirtest.uhn.ca/baseDstu2/Patient/">http://fhirtest.uhn.ca/baseDstu2/Patient</a>
|
<a href="http://fhirtest.uhn.ca/baseDstu2/Patient/">http://fhirtest.uhn.ca/baseDstu2/Patient</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The following example shows how to register this interceptor within
|
The following example shows how to register this interceptor within
|
||||||
a FHIR RESTful server.
|
a FHIR RESTful server.
|
||||||
</p>
|
</p>
|
||||||
<macro name="snippet">
|
<macro name="snippet">
|
||||||
<param name="id" value="responseHighlighterInterceptor" />
|
<param name="id" value="responseHighlighterInterceptor" />
|
||||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Request/Response Validation">
|
<subsection name="Request/Response Validation">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The
|
The
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.html">RequestValidatingInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.html">RequestValidatingInterceptor</a>
|
||||||
and
|
and
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/ResponseValidatingInterceptor.html">ResponseValidatingInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/ResponseValidatingInterceptor.html">ResponseValidatingInterceptor</a>
|
||||||
can be used to perform validation of resources on their way into and out of the server respectively.
|
can be used to perform validation of resources on their way into and out of the server respectively.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The RequestValidatingInterceptor looks at resources coming into the server (e.g. for create,
|
The RequestValidatingInterceptor looks at resources coming into the server (e.g. for create,
|
||||||
update, $operations, transactions, etc.) and validates them. The ResponseValidatingInterceptor
|
update, $operations, transactions, etc.) and validates them. The ResponseValidatingInterceptor
|
||||||
looks at resources being returned by the server (e.g. for read, search, $operations, etc.) and
|
looks at resources being returned by the server (e.g. for read, search, $operations, etc.) and
|
||||||
validates them.
|
validates them.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
These interceptors can be configured to add headers to the response, fail the response
|
These interceptors can be configured to add headers to the response, fail the response
|
||||||
(returning an HTTP 422 and throwing an exception in the process), or to add to the
|
(returning an HTTP 422 and throwing an exception in the process), or to add to the
|
||||||
OperationOutcome returned by the server.
|
OperationOutcome returned by the server.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
See the <a href="./doc_validation.html">Validation Page</a> for information on
|
See the <a href="./doc_validation.html">Validation Page</a> for information on
|
||||||
available
|
available
|
||||||
<a href="./apidocs/ca/uhn/fhir/validation/IValidatorModule.html">IValidatorModule</a>
|
<a href="./apidocs/ca/uhn/fhir/validation/IValidatorModule.html">IValidatorModule</a>
|
||||||
validation modules. Any of the <b>Resource Validators</b>
|
validation modules. Any of the <b>Resource Validators</b>
|
||||||
listed on that page can be enabled in these interceptors (note that the <b>Parser Validators</b>
|
listed on that page can be enabled in these interceptors (note that the <b>Parser Validators</b>
|
||||||
can not).
|
can not).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The following example shows how to register this interceptor within
|
The following example shows how to register this interceptor within
|
||||||
a FHIR RESTful server.
|
a FHIR RESTful server.
|
||||||
</p>
|
</p>
|
||||||
<macro name="snippet">
|
<macro name="snippet">
|
||||||
<param name="id" value="validatingInterceptor" />
|
<param name="id" value="validatingInterceptor" />
|
||||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="CORS (Cross-Origin Resource Sharing)">
|
<subsection name="CORS (Cross-Origin Resource Sharing)">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
HAPI FHIR includes an interceptor which can be used to
|
HAPI FHIR includes an interceptor which can be used to
|
||||||
implement CORS support on your server. See HAPI's
|
implement CORS support on your server. See HAPI's
|
||||||
<a href="./doc_cors.html">CORS Documentation</a> for information
|
<a href="./doc_cors.html">CORS Documentation</a> for information
|
||||||
on how to use this interceptor.
|
on how to use this interceptor.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Rejecting Unsupported HTTP Verbs">
|
<subsection name="Rejecting Unsupported HTTP Verbs">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Some security audit tools require that servers return an HTTP 405 if
|
Some security audit tools require that servers return an HTTP 405 if
|
||||||
an unsupported HTTP verb is received (e.g. TRACE). The
|
an unsupported HTTP verb is received (e.g. TRACE). The
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/BanUnsupprtedHttpMethodsInterceptor.html">BanUnsupprtedHttpMethodsInterceptor</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/BanUnsupprtedHttpMethodsInterceptor.html">BanUnsupprtedHttpMethodsInterceptor</a>
|
||||||
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/BanUnsupprtedHttpMethodsInterceptor.html">code</a>)
|
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/BanUnsupprtedHttpMethodsInterceptor.html">code</a>)
|
||||||
can be used to accomplish this.
|
can be used to accomplish this.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
</section>
|
<subsection name="Capturing Meta.source from an HTTP Header">
|
||||||
|
|
||||||
<section name="Creating Interceptors">
|
<p>
|
||||||
|
If you wish to override the value of <code>Resource.meta.source</code> using the value
|
||||||
<p>
|
supplied in an HTTP header, you can use the
|
||||||
Creating your own interceptors is easy. HAPI-FHIR provides a class called
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html">CaptureResourceSourceFromHeaderInterceptor</a>
|
||||||
<code>InterceptorAdapter</code> which you can extend and then override any
|
(<a href="./xref/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html">code</a>)
|
||||||
methods you wish. The following example shows a simple request counter.
|
to accomplish this.
|
||||||
</p>
|
</p>
|
||||||
<macro name="snippet">
|
|
||||||
<param name="id" value="interceptor" />
|
</subsection>
|
||||||
<param name="file" value="examples/src/main/java/example/RequestCounterInterceptor.java" />
|
|
||||||
</macro>
|
</section>
|
||||||
|
|
||||||
<p>
|
<section name="Creating Interceptors">
|
||||||
The following example shows an exception handling interceptor which
|
|
||||||
overrides the built-in exception handling by providing a custom response.
|
<p>
|
||||||
</p>
|
Creating your own interceptors is easy. HAPI-FHIR provides a class called
|
||||||
<macro name="snippet">
|
<code>InterceptorAdapter</code> which you can extend and then override any
|
||||||
<param name="id" value="interceptor" />
|
methods you wish. The following example shows a simple request counter.
|
||||||
<param name="file" value="examples/src/main/java/example/RequestExceptionInterceptor.java" />
|
</p>
|
||||||
</macro>
|
<macro name="snippet">
|
||||||
|
<param name="id" value="interceptor" />
|
||||||
</section>
|
<param name="file" value="examples/src/main/java/example/RequestCounterInterceptor.java" />
|
||||||
|
</macro>
|
||||||
<section name="JPA Server Interceptors">
|
|
||||||
|
<p>
|
||||||
<p>
|
The following example shows an exception handling interceptor which
|
||||||
The HAPI <a href="./doc_jpa.html">JPA Server</a> is an added layer on top of the HAPI
|
overrides the built-in exception handling by providing a custom response.
|
||||||
REST server framework. When you
|
</p>
|
||||||
</p>
|
<macro name="snippet">
|
||||||
|
<param name="id" value="interceptor" />
|
||||||
<p>
|
<param name="file" value="examples/src/main/java/example/RequestExceptionInterceptor.java" />
|
||||||
When an interceptor is registered against a RestfulServer which is backed by the
|
</macro>
|
||||||
HAPI JPA Server,
|
|
||||||
the <code>incomingRequestPreHandled</code> method will be called once for most
|
</section>
|
||||||
operations (e.g. a FHIR <code>create</code>), but in the case where the client
|
|
||||||
performs a FHIR <code>transaction</code> that method might be called multiple
|
<section name="JPA Server Interceptors">
|
||||||
times over the course of a single client invocation. For example if the transaction
|
|
||||||
contained a single create, the <code>incomingRequestPreHandled</code> method will
|
<p>
|
||||||
be called twice: once to indicate the transaction, and once to indicate the create.
|
The HAPI <a href="./doc_jpa.html">JPA Server</a> is an added layer on top of the HAPI
|
||||||
</p>
|
REST server framework. When you
|
||||||
|
</p>
|
||||||
<p>
|
|
||||||
This behaviour can be useful in cases where you want to audit exactly what was done
|
<p>
|
||||||
over the course of a request. Since a transaction can contain creates, updates, and
|
When an interceptor is registered against a RestfulServer which is backed by the
|
||||||
even more nested transactions, this behaviour ensures that you are notified for each
|
HAPI JPA Server,
|
||||||
activity.
|
the <code>incomingRequestPreHandled</code> method will be called once for most
|
||||||
</p>
|
operations (e.g. a FHIR <code>create</code>), but in the case where the client
|
||||||
|
performs a FHIR <code>transaction</code> that method might be called multiple
|
||||||
<p>
|
times over the course of a single client invocation. For example if the transaction
|
||||||
You may also choose to create interceptors which implement the
|
contained a single create, the <code>incomingRequestPreHandled</code> method will
|
||||||
more specialized
|
be called twice: once to indicate the transaction, and once to indicate the create.
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerOperationInterceptor.html">IJpaServerInterceptor</a>
|
</p>
|
||||||
interface, as this interceptor adds additional methods which are called during the JPA
|
|
||||||
lifecycle.
|
<p>
|
||||||
</p>
|
This behaviour can be useful in cases where you want to audit exactly what was done
|
||||||
|
over the course of a request. Since a transaction can contain creates, updates, and
|
||||||
<p>
|
even more nested transactions, this behaviour ensures that you are notified for each
|
||||||
Note that this documentation used to <b>erroneously</b> suggest that in order to achieve
|
activity.
|
||||||
this behaviour you needed to register the interceptor with the DaoConfig. In actual fact this
|
</p>
|
||||||
did not make any difference, and registering interceptors with the DaoConfig has now been
|
|
||||||
deprecated since it does not achieve anything extra.
|
<p>
|
||||||
</p>
|
You may also choose to create interceptors which implement the
|
||||||
|
more specialized
|
||||||
</section>
|
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerOperationInterceptor.html">IJpaServerInterceptor</a>
|
||||||
|
interface, as this interceptor adds additional methods which are called during the JPA
|
||||||
</body>
|
lifecycle.
|
||||||
|
</p>
|
||||||
</document>
|
|
||||||
|
<p>
|
||||||
|
Note that this documentation used to <b>erroneously</b> suggest that in order to achieve
|
||||||
|
this behaviour you needed to register the interceptor with the DaoConfig. In actual fact this
|
||||||
|
did not make any difference, and registering interceptors with the DaoConfig has now been
|
||||||
|
deprecated since it does not achieve anything extra.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</document>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user