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 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_NO_CACHE = "no-cache";
|
||||
public static final String CACHE_CONTROL_NO_STORE = "no-store";
|
||||
|
@ -242,6 +243,15 @@ public class Constants {
|
|||
* Operation name for the $lastn operation
|
||||
*/
|
||||
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 {
|
||||
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.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -46,6 +46,30 @@ public class MetaUtil {
|
|||
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) {
|
||||
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
|
||||
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.registry.ISearchParamRegistry;
|
||||
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.util.AddRemoveCount;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
|
@ -900,15 +899,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
+ (isNotBlank(provenanceRequestId) ? "#" : "")
|
||||
+ defaultString(provenanceRequestId);
|
||||
|
||||
if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
|
||||
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);
|
||||
}
|
||||
MetaUtil.setSource(myContext, retVal, sourceString);
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
@ -1078,7 +1070,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
source = ((IBaseHasExtensions) theResource.getMeta())
|
||||
.getExtension()
|
||||
.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)
|
||||
.map(t -> ((IPrimitiveType) t.getValue()).getValueAsString())
|
||||
.findFirst()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
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.util.TestUtil;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
@ -46,12 +45,12 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
|
|||
|
||||
when(mySrd.getRequestId()).thenReturn(requestId);
|
||||
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);
|
||||
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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);
|
||||
IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
|
@ -62,7 +61,7 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
|
|||
IBundleProvider result = myPatientDao.search(params);
|
||||
assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
|
||||
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
|
||||
params = new SearchParameterMap();
|
||||
|
@ -87,17 +86,17 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
|
|||
|
||||
when(mySrd.getRequestId()).thenReturn(requestId);
|
||||
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);
|
||||
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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);
|
||||
IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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);
|
||||
myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
|
@ -118,17 +117,17 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
|
|||
|
||||
when(mySrd.getRequestId()).thenReturn(requestId);
|
||||
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);
|
||||
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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);
|
||||
IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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);
|
||||
myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
|
@ -148,7 +147,7 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
|
|||
when(mySrd.getRequestId()).thenReturn(requestId);
|
||||
|
||||
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);
|
||||
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
|
@ -166,19 +165,19 @@ public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
|
|||
public void testSourceNotPreservedAcrossUpdate() {
|
||||
|
||||
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);
|
||||
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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.setActive(false);
|
||||
myPatientDao.update(pt0);
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
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);
|
||||
myPatientDao.update(pt0);
|
||||
|
||||
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
|
||||
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.data.ISearchDao;
|
||||
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.search.SearchCoordinatorSvcImpl;
|
||||
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();
|
||||
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);
|
||||
|
@ -3960,12 +3959,12 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
ourClient.update().resource(patient).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");
|
||||
ourClient.update().resource(readPatient).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";
|
||||
|
||||
/**
|
||||
* <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
|
||||
*/
|
||||
|
|
|
@ -370,7 +370,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
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()
|
||||
.setCreated(true)
|
||||
.setResource(theResource)
|
||||
.setId(theResource.getIdElement());
|
||||
}
|
||||
|
||||
|
@ -373,6 +374,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
|
||||
return new MethodOutcome()
|
||||
.setCreated(created)
|
||||
.setResource(theResource)
|
||||
.setId(theResource.getIdElement());
|
||||
}
|
||||
|
||||
|
@ -416,6 +418,19 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
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) {
|
||||
List<T> output = fireInterceptorsAndFilterAsNeeded(Lists.newArrayList(theResource), theRequestDetails);
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
public void tesPageRequestCantTriggerSearchAccidentally() throws Exception {
|
||||
public void testPageRequestCantTriggerSearchAccidentally() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Constants.PARAM_PAGINGACTION + "=12345");
|
||||
Bundle bundle;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
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>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</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 -->
|
||||
<dependency>
|
||||
|
@ -53,6 +63,16 @@
|
|||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</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>
|
||||
|
||||
<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
|
||||
resource providers have been corrected. Thanks to Jelmer ter Wal for the pull request!
|
||||
</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 version="4.0.3" date="2019-09-03" description="Igloo (Point Release)">
|
||||
<action type="fix">
|
||||
|
|
|
@ -1,385 +1,397 @@
|
|||
<?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">
|
||||
|
||||
<properties>
|
||||
<title>Server Interceptors</title>
|
||||
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
||||
</properties>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- The body of the document contains a number of sections -->
|
||||
<section name="Server Interceptors">
|
||||
|
||||
<p>
|
||||
The RESTful server provides a powerful mechanism for adding cross-cutting behaviour
|
||||
(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
|
||||
more <b>interceptors</b> that will be invoked at defined points in the processing of
|
||||
each incoming request.
|
||||
</p>
|
||||
|
||||
<img src="svg/restful-server-interceptors.svg" alt="Interceptors"/>
|
||||
|
||||
<p>
|
||||
Interceptors will intercept the incoming request, and can take action such as
|
||||
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,
|
||||
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).
|
||||
</p>
|
||||
<p>
|
||||
Interceptors
|
||||
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
|
||||
lifecycle of a normal (non failing) request which is subject to an interceptor.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Interceptors must implement the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html">IServerInterceptor</a>
|
||||
interface (or extend the convenience
|
||||
<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
|
||||
points in the execution of the client request.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Before any processing at all is performed on the request,
|
||||
<b>incomingRequestPreProcessed</b> will be invoked. This can be useful
|
||||
if you wish to handle some requests completely outside of HAPI's processing
|
||||
mechanism.
|
||||
<ul>
|
||||
<li>
|
||||
If this method returns <code>true</code>, processing continues to the
|
||||
next interceptor, and ultimately to the next phase of processing.
|
||||
</li>
|
||||
<li>
|
||||
If this method returns <code>false</code>, processing stops immediately.
|
||||
This is useful if the interceptor wishes to supply its own response
|
||||
by directly calling methods on the <code>HttpServletResponse</code>
|
||||
</li>
|
||||
<li>
|
||||
If this method throws any subclass of
|
||||
<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.
|
||||
This is useful if an interceptor wishes to abort the request (e.g. because
|
||||
it did not detect valid credentials)
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Once the request is classified (meaning that the URL and request headers are
|
||||
examined to determine exactly what kind of request is being made),
|
||||
<b>incomingRequestPostProcessed</b> will be invoked. This method has
|
||||
an additional parameter, the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/method/RequestDetails.html">RequestDetails</a>
|
||||
object which contains details about what operation is about to be
|
||||
called, and what request parameters were receievd with that request.
|
||||
<ul>
|
||||
<li>
|
||||
If this method returns <code>true</code>, processing continues to the
|
||||
next interceptor, and ultimately to the next phase of processing.
|
||||
</li>
|
||||
<li>
|
||||
If this method returns <code>false</code>, processing stops immediately.
|
||||
This is useful if the interceptor wishes to supply its own response
|
||||
by directly calling methods on the <code>HttpServletResponse</code>
|
||||
</li>
|
||||
<li>
|
||||
If this method throws any subclass of
|
||||
<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.
|
||||
This is useful if an interceptor wishes to abort the request (e.g. because
|
||||
it did not detect valid credentials)
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Once the request is being handled,
|
||||
<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
|
||||
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>
|
||||
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>
|
||||
to abort processing.
|
||||
</li>
|
||||
<li>
|
||||
After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method),
|
||||
but before the actual response is returned to the client,
|
||||
the <b>outgoingResponse</b> method is invoked.
|
||||
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
|
||||
there are three implementations of <b>outgoingResponse</b>: The server
|
||||
will invoke the one which corresponds to the response type
|
||||
of the operation being invoked (resource, bundle, etc.)
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br clear="all"/>
|
||||
<subsection name="Exception Handling">
|
||||
|
||||
<img src="svg/restful-server-interceptors-exception.svg" alt="Interceptors" align="right"/>
|
||||
|
||||
<p>
|
||||
In the event of an exception being thrown within the server, the interceptor
|
||||
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>
|
||||
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
|
||||
at any point in the handling chain.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
HTTP 4xx or 5xx response automatically depending on the specific exception which was thrown).
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
interacting with the <code>HttpServletResponse</code> object which is
|
||||
passed in.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
<br clear="all"/>
|
||||
<subsection name="Registering Interceptors">
|
||||
|
||||
<p>
|
||||
To register an interceptor to the server, simply call
|
||||
either <code>registerInterceptor</code> or <code>setInterceptors</code>
|
||||
on your RestfulServer instance.
|
||||
</p>
|
||||
<p>
|
||||
Note that order is important: The server will invoke
|
||||
<code>incomingRequestPreProcessed</code> and <code>incomingRequestPostProcessed</code>
|
||||
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
|
||||
order in which the interceptors were registered. This means that interceptors
|
||||
can be thought of as "wrapping" the request.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Built In Interceptors">
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<a name="Logging"/>
|
||||
<subsection name="Logging Server Requests">
|
||||
|
||||
<p>
|
||||
The
|
||||
<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>)
|
||||
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
|
||||
of detail about each incoming request.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following example shows how to register a logging interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="loggingInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
This interceptor will then produce output similar to the following:
|
||||
</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]
|
||||
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>
|
||||
|
||||
<a name="ExceptionHandlingInterceptor"/>
|
||||
<subsection name="Exception Handling">
|
||||
|
||||
<p>
|
||||
The
|
||||
<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>)
|
||||
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
|
||||
normal processing in a create method, but also including unexpected NullPointerExceptions thrown by client code).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following example shows how to register an exception handling interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="exceptionInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Response Syntax Highlighting">
|
||||
|
||||
<p>
|
||||
The
|
||||
<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>)
|
||||
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
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
To see an example of how this looks, see our demo server using the following example
|
||||
query:
|
||||
<a href="http://fhirtest.uhn.ca/baseDstu2/Patient/">http://fhirtest.uhn.ca/baseDstu2/Patient</a>
|
||||
</p>
|
||||
<p>
|
||||
The following example shows how to register this interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="responseHighlighterInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Request/Response Validation">
|
||||
|
||||
<p>
|
||||
The
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.html">RequestValidatingInterceptor</a>
|
||||
and
|
||||
<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.
|
||||
</p>
|
||||
<p>
|
||||
The RequestValidatingInterceptor looks at resources coming into the server (e.g. for create,
|
||||
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
|
||||
validates them.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
OperationOutcome returned by the server.
|
||||
</p>
|
||||
<p>
|
||||
See the <a href="./doc_validation.html">Validation Page</a> for information on
|
||||
available
|
||||
<a href="./apidocs/ca/uhn/fhir/validation/IValidatorModule.html">IValidatorModule</a>
|
||||
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>
|
||||
can not).
|
||||
</p>
|
||||
<p>
|
||||
The following example shows how to register this interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="validatingInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="CORS (Cross-Origin Resource Sharing)">
|
||||
|
||||
<p>
|
||||
HAPI FHIR includes an interceptor which can be used to
|
||||
implement CORS support on your server. See HAPI's
|
||||
<a href="./doc_cors.html">CORS Documentation</a> for information
|
||||
on how to use this interceptor.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Rejecting Unsupported HTTP Verbs">
|
||||
|
||||
<p>
|
||||
Some security audit tools require that servers return an HTTP 405 if
|
||||
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="./xref/ca/uhn/fhir/rest/server/interceptor/BanUnsupprtedHttpMethodsInterceptor.html">code</a>)
|
||||
can be used to accomplish this.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Creating Interceptors">
|
||||
|
||||
<p>
|
||||
Creating your own interceptors is easy. HAPI-FHIR provides a class called
|
||||
<code>InterceptorAdapter</code> which you can extend and then override any
|
||||
methods you wish. The following example shows a simple request counter.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="interceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/RequestCounterInterceptor.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
The following example shows an exception handling interceptor which
|
||||
overrides the built-in exception handling by providing a custom response.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="interceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/RequestExceptionInterceptor.java" />
|
||||
</macro>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="JPA Server Interceptors">
|
||||
|
||||
<p>
|
||||
The HAPI <a href="./doc_jpa.html">JPA Server</a> is an added layer on top of the HAPI
|
||||
REST server framework. When you
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When an interceptor is registered against a RestfulServer which is backed by the
|
||||
HAPI JPA Server,
|
||||
the <code>incomingRequestPreHandled</code> method will be called once for most
|
||||
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
|
||||
times over the course of a single client invocation. For example if the transaction
|
||||
contained a single create, the <code>incomingRequestPreHandled</code> method will
|
||||
be called twice: once to indicate the transaction, and once to indicate the create.
|
||||
</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
|
||||
even more nested transactions, this behaviour ensures that you are notified for each
|
||||
activity.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You may also choose to create interceptors which implement the
|
||||
more specialized
|
||||
<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
|
||||
lifecycle.
|
||||
</p>
|
||||
|
||||
<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>
|
||||
<?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">
|
||||
|
||||
<properties>
|
||||
<title>Server Interceptors</title>
|
||||
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
||||
</properties>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- The body of the document contains a number of sections -->
|
||||
<section name="Server Interceptors">
|
||||
|
||||
<p>
|
||||
The RESTful server provides a powerful mechanism for adding cross-cutting behaviour
|
||||
(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
|
||||
more <b>interceptors</b> that will be invoked at defined points in the processing of
|
||||
each incoming request.
|
||||
</p>
|
||||
|
||||
<img src="svg/restful-server-interceptors.svg" alt="Interceptors"/>
|
||||
|
||||
<p>
|
||||
Interceptors will intercept the incoming request, and can take action such as
|
||||
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,
|
||||
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).
|
||||
</p>
|
||||
<p>
|
||||
Interceptors
|
||||
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
|
||||
lifecycle of a normal (non failing) request which is subject to an interceptor.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Interceptors must implement the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html">IServerInterceptor</a>
|
||||
interface (or extend the convenience
|
||||
<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
|
||||
points in the execution of the client request.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Before any processing at all is performed on the request,
|
||||
<b>incomingRequestPreProcessed</b> will be invoked. This can be useful
|
||||
if you wish to handle some requests completely outside of HAPI's processing
|
||||
mechanism.
|
||||
<ul>
|
||||
<li>
|
||||
If this method returns <code>true</code>, processing continues to the
|
||||
next interceptor, and ultimately to the next phase of processing.
|
||||
</li>
|
||||
<li>
|
||||
If this method returns <code>false</code>, processing stops immediately.
|
||||
This is useful if the interceptor wishes to supply its own response
|
||||
by directly calling methods on the <code>HttpServletResponse</code>
|
||||
</li>
|
||||
<li>
|
||||
If this method throws any subclass of
|
||||
<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.
|
||||
This is useful if an interceptor wishes to abort the request (e.g. because
|
||||
it did not detect valid credentials)
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Once the request is classified (meaning that the URL and request headers are
|
||||
examined to determine exactly what kind of request is being made),
|
||||
<b>incomingRequestPostProcessed</b> will be invoked. This method has
|
||||
an additional parameter, the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/method/RequestDetails.html">RequestDetails</a>
|
||||
object which contains details about what operation is about to be
|
||||
called, and what request parameters were receievd with that request.
|
||||
<ul>
|
||||
<li>
|
||||
If this method returns <code>true</code>, processing continues to the
|
||||
next interceptor, and ultimately to the next phase of processing.
|
||||
</li>
|
||||
<li>
|
||||
If this method returns <code>false</code>, processing stops immediately.
|
||||
This is useful if the interceptor wishes to supply its own response
|
||||
by directly calling methods on the <code>HttpServletResponse</code>
|
||||
</li>
|
||||
<li>
|
||||
If this method throws any subclass of
|
||||
<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.
|
||||
This is useful if an interceptor wishes to abort the request (e.g. because
|
||||
it did not detect valid credentials)
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Once the request is being handled,
|
||||
<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
|
||||
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>
|
||||
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>
|
||||
to abort processing.
|
||||
</li>
|
||||
<li>
|
||||
After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method),
|
||||
but before the actual response is returned to the client,
|
||||
the <b>outgoingResponse</b> method is invoked.
|
||||
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
|
||||
there are three implementations of <b>outgoingResponse</b>: The server
|
||||
will invoke the one which corresponds to the response type
|
||||
of the operation being invoked (resource, bundle, etc.)
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br clear="all"/>
|
||||
<subsection name="Exception Handling">
|
||||
|
||||
<img src="svg/restful-server-interceptors-exception.svg" alt="Interceptors" align="right"/>
|
||||
|
||||
<p>
|
||||
In the event of an exception being thrown within the server, the interceptor
|
||||
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>
|
||||
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
|
||||
at any point in the handling chain.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
HTTP 4xx or 5xx response automatically depending on the specific exception which was thrown).
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
interacting with the <code>HttpServletResponse</code> object which is
|
||||
passed in.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
<br clear="all"/>
|
||||
<subsection name="Registering Interceptors">
|
||||
|
||||
<p>
|
||||
To register an interceptor to the server, simply call
|
||||
either <code>registerInterceptor</code> or <code>setInterceptors</code>
|
||||
on your RestfulServer instance.
|
||||
</p>
|
||||
<p>
|
||||
Note that order is important: The server will invoke
|
||||
<code>incomingRequestPreProcessed</code> and <code>incomingRequestPostProcessed</code>
|
||||
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
|
||||
order in which the interceptors were registered. This means that interceptors
|
||||
can be thought of as "wrapping" the request.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Built In Interceptors">
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<a name="Logging"/>
|
||||
<subsection name="Logging Server Requests">
|
||||
|
||||
<p>
|
||||
The
|
||||
<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>)
|
||||
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
|
||||
of detail about each incoming request.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following example shows how to register a logging interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="loggingInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
This interceptor will then produce output similar to the following:
|
||||
</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]
|
||||
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>
|
||||
|
||||
<a name="ExceptionHandlingInterceptor"/>
|
||||
<subsection name="Exception Handling">
|
||||
|
||||
<p>
|
||||
The
|
||||
<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>)
|
||||
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
|
||||
normal processing in a create method, but also including unexpected NullPointerExceptions thrown by client code).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following example shows how to register an exception handling interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="exceptionInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Response Syntax Highlighting">
|
||||
|
||||
<p>
|
||||
The
|
||||
<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>)
|
||||
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
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
To see an example of how this looks, see our demo server using the following example
|
||||
query:
|
||||
<a href="http://fhirtest.uhn.ca/baseDstu2/Patient/">http://fhirtest.uhn.ca/baseDstu2/Patient</a>
|
||||
</p>
|
||||
<p>
|
||||
The following example shows how to register this interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="responseHighlighterInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Request/Response Validation">
|
||||
|
||||
<p>
|
||||
The
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.html">RequestValidatingInterceptor</a>
|
||||
and
|
||||
<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.
|
||||
</p>
|
||||
<p>
|
||||
The RequestValidatingInterceptor looks at resources coming into the server (e.g. for create,
|
||||
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
|
||||
validates them.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
OperationOutcome returned by the server.
|
||||
</p>
|
||||
<p>
|
||||
See the <a href="./doc_validation.html">Validation Page</a> for information on
|
||||
available
|
||||
<a href="./apidocs/ca/uhn/fhir/validation/IValidatorModule.html">IValidatorModule</a>
|
||||
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>
|
||||
can not).
|
||||
</p>
|
||||
<p>
|
||||
The following example shows how to register this interceptor within
|
||||
a FHIR RESTful server.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="validatingInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="CORS (Cross-Origin Resource Sharing)">
|
||||
|
||||
<p>
|
||||
HAPI FHIR includes an interceptor which can be used to
|
||||
implement CORS support on your server. See HAPI's
|
||||
<a href="./doc_cors.html">CORS Documentation</a> for information
|
||||
on how to use this interceptor.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Rejecting Unsupported HTTP Verbs">
|
||||
|
||||
<p>
|
||||
Some security audit tools require that servers return an HTTP 405 if
|
||||
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="./xref/ca/uhn/fhir/rest/server/interceptor/BanUnsupprtedHttpMethodsInterceptor.html">code</a>)
|
||||
can be used to accomplish this.
|
||||
</p>
|
||||
|
||||
</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 name="Creating Interceptors">
|
||||
|
||||
<p>
|
||||
Creating your own interceptors is easy. HAPI-FHIR provides a class called
|
||||
<code>InterceptorAdapter</code> which you can extend and then override any
|
||||
methods you wish. The following example shows a simple request counter.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="interceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/RequestCounterInterceptor.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
The following example shows an exception handling interceptor which
|
||||
overrides the built-in exception handling by providing a custom response.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="interceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/RequestExceptionInterceptor.java" />
|
||||
</macro>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="JPA Server Interceptors">
|
||||
|
||||
<p>
|
||||
The HAPI <a href="./doc_jpa.html">JPA Server</a> is an added layer on top of the HAPI
|
||||
REST server framework. When you
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When an interceptor is registered against a RestfulServer which is backed by the
|
||||
HAPI JPA Server,
|
||||
the <code>incomingRequestPreHandled</code> method will be called once for most
|
||||
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
|
||||
times over the course of a single client invocation. For example if the transaction
|
||||
contained a single create, the <code>incomingRequestPreHandled</code> method will
|
||||
be called twice: once to indicate the transaction, and once to indicate the create.
|
||||
</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
|
||||
even more nested transactions, this behaviour ensures that you are notified for each
|
||||
activity.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You may also choose to create interceptors which implement the
|
||||
more specialized
|
||||
<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
|
||||
lifecycle.
|
||||
</p>
|
||||
|
||||
<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…
Reference in New Issue