Add ability for resource source to be specified by header

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

View File

@ -28,6 +28,7 @@ public class Constants {
public static final String CT_TEXT_CSV = "text/csv";
public static final String 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;

View File

@ -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");

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.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()

View File

@ -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();

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.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]+"));
}
}

View File

@ -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
*/

View File

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

View File

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

View File

@ -126,6 +126,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
return new MethodOutcome()
.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) {

View File

@ -87,9 +87,8 @@ public class SearchR4Test {
* A paging request that incorrectly executes at the type level shouldn't be grabbed by the search method binding
*/
@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);

View File

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

View File

@ -21,6 +21,16 @@
<artifactId>hapi-fhir-base</artifactId>
<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>

View File

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

View File

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

View File

@ -450,6 +450,14 @@
Several issues with HAPI FHIR's annotation scanner that prevented use with Kotlin based
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">

View File

@ -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>