From 794db6a1418ae05b2d24ba4a070f8f730ccd889d Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 26 Aug 2014 18:18:00 -0400 Subject: [PATCH] Document interceptor framework --- .../main/java/example/ServerInterceptors.java | 80 + hapi-fhir-base/src/changes/changes.xml | 7 + .../BaseAddOrDeleteTagsMethodBinding.java | 3 +- .../BaseOutcomeReturningMethodBinding.java | 3 +- .../BaseResourceReturningMethodBinding.java | 6 +- .../rest/method/GetTagsMethodBinding.java | 3 +- .../rest/method/OtherOperationTypeEnum.java | 4 +- .../uhn/fhir/rest/server/RestfulServer.java | 22 +- .../interceptor/IServerInterceptor.java | 35 +- .../interceptor/InterceptorAdapter.java | 4 +- .../interceptor/LoggingInterceptor.java | 4 +- hapi-fhir-base/src/site/resources/hapi.css | 8 + .../svg/restful-server-interceptors.svg | 1 + hapi-fhir-base/src/site/site.xml | 7 +- .../src/site/xdoc/doc_rest_client.xml | 73 +- .../site/xdoc/doc_rest_client_interceptor.xml | 74 + .../site/xdoc/doc_rest_server_interceptor.xml | 107 + .../uhn/fhir/rest/server/InterceptorTest.java | 32 +- .../webapp/WEB-INF/templates/resource.html | 2 +- .../webapp/WEB-INF/templates/tmpl-head.html | 1 + .../main/webapp/js/ClientCodeGeneratorHapi.js | 2 +- .../src/main/webapp/js/RestfulTester.js | 4 +- .../src/main/webapp/js/jquery-2.1.1.js | 9190 +++++++++++++++++ 23 files changed, 9563 insertions(+), 109 deletions(-) create mode 100644 hapi-fhir-base/examples/src/main/java/example/ServerInterceptors.java create mode 100644 hapi-fhir-base/src/site/resources/svg/restful-server-interceptors.svg create mode 100644 hapi-fhir-base/src/site/xdoc/doc_rest_client_interceptor.xml create mode 100644 hapi-fhir-base/src/site/xdoc/doc_rest_server_interceptor.xml create mode 100644 hapi-fhir-testpage-overlay/src/main/webapp/js/jquery-2.1.1.js diff --git a/hapi-fhir-base/examples/src/main/java/example/ServerInterceptors.java b/hapi-fhir-base/examples/src/main/java/example/ServerInterceptors.java new file mode 100644 index 00000000000..640ba8cc8c7 --- /dev/null +++ b/hapi-fhir-base/examples/src/main/java/example/ServerInterceptors.java @@ -0,0 +1,80 @@ +package example; + +import java.io.IOException; +import java.util.List; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.dstu.composite.HumanNameDt; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.parser.DataFormatException; + +public class ServerInterceptors { + +@SuppressWarnings("unused") +public static void main(String[] args) throws DataFormatException, IOException { + + +// START SNIPPET: resourceExtension +// Create an example patient +Patient patient = new Patient(); +patient.addIdentifier(IdentifierUseEnum.OFFICIAL, "urn:example", "7000135", null); + +// Create an extension +ExtensionDt ext = new ExtensionDt(); +ext.setModifier(false); +ext.setUrl("http://example.com/extensions#someext"); +ext.setValue(new DateTimeDt("2011-01-02T11:13:15")); + +// Add the extension to the resource +patient.addUndeclaredExtension(ext); +//END SNIPPET: resourceExtension + + +//START SNIPPET: resourceStringExtension +HumanNameDt name = patient.addName(); +name.addFamily().setValue("Shmoe"); +StringDt given = name.addGiven(); +given.setValue("Joe"); +ExtensionDt ext2 = new ExtensionDt(false, "http://examples.com#moreext", new StringDt("Hello")); +given.addUndeclaredExtension(ext2); +//END SNIPPET: resourceStringExtension + +String output = new FhirContext().newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); +System.out.println(output); + + +//START SNIPPET: parseExtension +// Get all extensions (modifier or not) for a given URL +List resourceExts = patient.getUndeclaredExtensionsByUrl("http://fooextensions.com#exts"); + +// Get all non-modifier extensions regardless of URL +List nonModExts = patient.getUndeclaredExtensions(); + +//Get all non-modifier extensions regardless of URL +List modExts = patient.getUndeclaredModifierExtensions(); +//END SNIPPET: parseExtension + +} + + +public void foo() { +//START SNIPPET: subExtension +Patient patient = new Patient(); + +ExtensionDt parent = new ExtensionDt(false, "http://example.com#parent"); +patient.addUndeclaredExtension(parent); + +ExtensionDt child1 = new ExtensionDt(false, "http://example.com#childOne", new StringDt("value1")); +parent.addUndeclaredExtension(child1); + +ExtensionDt child2 = new ExtensionDt(false, "http://example.com#childTwo", new StringDt("value1")); +parent.addUndeclaredExtension(child2); +//END SNIPPET: subExtension + +} + +} diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index 4ce51862bce..b7c5c8aad06 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -12,6 +12,13 @@ Allow generic client ... OAUTH --> + + Add server interceptor framework, and new interceptor for logging incoming + requests. + + + Add server validation framework for validating resources against the FHIR schemas and schematrons + Tester UI created double _format and _pretty param entries in searches. Thanks to Gered King of University Health Network for reporting! diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java index 7cb62b147e8..50fcc6e809f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java @@ -176,7 +176,8 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding } invokeServerMethod(params); - for (IServerInterceptor next : theServer.getInterceptors()) { + for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { + IServerInterceptor next = theServer.getInterceptors().get(i); boolean continueProcessing = next.outgoingResponse(theRequest, theRequest.getServletRequest(), theRequest.getServletResponse()); if (!continueProcessing) { return; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java index 3621bb333e6..1721fcb1515 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java @@ -149,7 +149,8 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding= 0; i--) { + IServerInterceptor next = theServer.getInterceptors().get(i); boolean continueProcessing = next.outgoingResponse(theRequest, outcome, theRequest.getServletRequest(), theRequest.getServletResponse()); if (!continueProcessing) { return; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 51e314c24d6..5ad5468fea5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -231,7 +231,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { + IServerInterceptor next = theServer.getInterceptors().get(i); boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse()); if (!continueProcessing) { return; @@ -249,7 +250,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { + IServerInterceptor next = theServer.getInterceptors().get(i); boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse()); if (!continueProcessing) { return; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java index b86cac4abbb..5b07650f98e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java @@ -156,7 +156,8 @@ public class GetTagsMethodBinding extends BaseMethodBinding { TagList resp = (TagList) invokeServerMethod(params); - for (IServerInterceptor next : theServer.getInterceptors()) { + for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { + IServerInterceptor next = theServer.getInterceptors().get(i); boolean continueProcessing = next.outgoingResponse(theRequest, resp, theRequest.getServletRequest(), theRequest.getServletResponse()); if (!continueProcessing) { return; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OtherOperationTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OtherOperationTypeEnum.java index d9b7a6fe1a0..671faf35ab9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OtherOperationTypeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OtherOperationTypeEnum.java @@ -8,7 +8,9 @@ public enum OtherOperationTypeEnum { DELETE_TAGS("delete-tags"), - GET_TAGS("get-tags"); + GET_TAGS("get-tags"), + + GET_PAGE("get-page"); private String myCode; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index cf5d8d66ea2..1dac21a48fc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -72,6 +72,7 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding; +import ca.uhn.fhir.rest.method.OtherOperationTypeEnum; import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SearchMethodBinding; @@ -420,7 +421,8 @@ public class RestfulServer extends HttpServlet { Bundle bundle = createBundleFromBundleProvider(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction); - for (IServerInterceptor next : getInterceptors()) { + for (int i = getInterceptors().size() - 1; i >= 0; i--) { + IServerInterceptor next = getInterceptors().get(i); boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse()); if (!continueProcessing) { return; @@ -433,7 +435,7 @@ public class RestfulServer extends HttpServlet { protected void handleRequest(SearchMethodBinding.RequestType theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException { for (IServerInterceptor next : myInterceptors) { - boolean continueProcessing = next.incomingRequest(theRequest, theResponse); + boolean continueProcessing = next.incomingRequestPreProcessed(theRequest, theResponse); if (!continueProcessing) { return; } @@ -583,6 +585,7 @@ public class RestfulServer extends HttpServlet { String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION); if (getPagingProvider() != null && isNotBlank(pagingAction)) { + r.setOtherOperationType(OtherOperationTypeEnum.GET_PAGE); handlePagingRequest(r, theResponse, pagingAction); return; } @@ -608,7 +611,7 @@ public class RestfulServer extends HttpServlet { requestDetails.setOtherOperationType(resourceMethod.getOtherOperationType()); for (IServerInterceptor next : myInterceptors) { - boolean continueProcessing = next.incomingRequest(requestDetails, theRequest, theResponse); + boolean continueProcessing = next.incomingRequestPostProcessed(requestDetails, theRequest, theResponse); if (!continueProcessing) { return; } @@ -781,6 +784,19 @@ public class RestfulServer extends HttpServlet { } } + /** + * Sets (or clears) the list of interceptors + * + * @param theList + * The list of interceptors (may be null) + */ + public void setInterceptors(IServerInterceptor... theList) { + myInterceptors.clear(); + if (theList != null) { + myInterceptors.addAll(Arrays.asList(theList)); + } + } + /** * Sets the paging provider to use, or null to use no paging (which is the default) */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java index 20be2e616f5..4eae9f59e4e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java @@ -26,12 +26,12 @@ import javax.servlet.http.HttpServletResponse; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TagList; -import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; /** - * Provides methods to intercept requests and responses + * Provides methods to intercept requests and responses. Note that implementations of this interface may wish + * to use {@link InterceptorAdapter} in order to not need to implement every method. */ public interface IServerInterceptor { @@ -46,16 +46,19 @@ public interface IServerInterceptor { * The incoming request * @param theResponse * The response. Note that interceptors may choose to provide a response (i.e. by calling - * {@link HttpServletResponse#getWriter()}) but in that case it is important to return true + * {@link HttpServletResponse#getWriter()}) but in that case it is important to return false * @return Return true if processing should continue normally. This is generally the right thing to do. * If your interceptor is providing a response rather than letting HAPI handle the response normally, you * must return false. In this case, no further processing will occur and no further * interceptors will be called. */ - public boolean incomingRequest(HttpServletRequest theRequest, HttpServletResponse theResponse); + public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse); /** - * This method is called just before the actual implementing server method is invoked + * This method is called just before the actual implementing server method is invoked. + *

+ * Note about exceptions: + *

* * @param theRequestDetails * A bean containing details about the request that is about to be processed, including @@ -63,16 +66,16 @@ public interface IServerInterceptor { * The incoming request * @param theResponse * The response. Note that interceptors may choose to provide a response (i.e. by calling - * {@link HttpServletResponse#getWriter()}) but in that case it is important to return true + * {@link HttpServletResponse#getWriter()}) but in that case it is important to return false * @return Return true if processing should continue normally. This is generally the right thing to do. * If your interceptor is providing a response rather than letting HAPI handle the response normally, you * must return false. In this case, no further processing will occur and no further * interceptors will be called. * @throws AuthenticationException * This exception may be thrown to indicate that the interceptor has detected an unauthorized access - * attempt + * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. */ - public boolean incomingRequest(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException; + public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException; /** * This method is called after the server implementation method has been called, but before any attempt to stream @@ -86,14 +89,14 @@ public interface IServerInterceptor { * The incoming request * @param theResponse * The response. Note that interceptors may choose to provide a response (i.e. by calling - * {@link HttpServletResponse#getWriter()}) but in that case it is important to return true + * {@link HttpServletResponse#getWriter()}) but in that case it is important to return false * @return Return true if processing should continue normally. This is generally the right thing to do. * If your interceptor is providing a response rather than letting HAPI handle the response normally, you * must return false. In this case, no further processing will occur and no further * interceptors will be called. * @throws AuthenticationException * This exception may be thrown to indicate that the interceptor has detected an unauthorized access - * attempt + * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. */ public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; @@ -110,14 +113,14 @@ public interface IServerInterceptor { * The incoming request * @param theResponse * The response. Note that interceptors may choose to provide a response (i.e. by calling - * {@link HttpServletResponse#getWriter()}) but in that case it is important to return true + * {@link HttpServletResponse#getWriter()}) but in that case it is important to return false * @return Return true if processing should continue normally. This is generally the right thing to do. * If your interceptor is providing a response rather than letting HAPI handle the response normally, you * must return false. In this case, no further processing will occur and no further * interceptors will be called. * @throws AuthenticationException * This exception may be thrown to indicate that the interceptor has detected an unauthorized access - * attempt + * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. */ public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; @@ -133,14 +136,14 @@ public interface IServerInterceptor { * The incoming request * @param theResponse * The response. Note that interceptors may choose to provide a response (i.e. by calling - * {@link HttpServletResponse#getWriter()}) but in that case it is important to return true + * {@link HttpServletResponse#getWriter()}) but in that case it is important to return false * @return Return true if processing should continue normally. This is generally the right thing to do. * If your interceptor is providing a response rather than letting HAPI handle the response normally, you * must return false. In this case, no further processing will occur and no further * interceptors will be called. * @throws AuthenticationException * This exception may be thrown to indicate that the interceptor has detected an unauthorized access - * attempt + * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. */ public boolean outgoingResponse(RequestDetails theRequestDetails, IResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; @@ -156,14 +159,14 @@ public interface IServerInterceptor { * The incoming request * @param theResponse * The response. Note that interceptors may choose to provide a response (i.e. by calling - * {@link HttpServletResponse#getWriter()}) but in that case it is important to return true + * {@link HttpServletResponse#getWriter()}) but in that case it is important to return false * @return Return true if processing should continue normally. This is generally the right thing to do. * If your interceptor is providing a response rather than letting HAPI handle the response normally, you * must return false. In this case, no further processing will occur and no further * interceptors will be called. * @throws AuthenticationException * This exception may be thrown to indicate that the interceptor has detected an unauthorized access - * attempt + * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. */ public boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java index ed3b1504941..0952c931e43 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java @@ -16,12 +16,12 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; public class InterceptorAdapter implements IServerInterceptor { @Override - public boolean incomingRequest(HttpServletRequest theRequest, HttpServletResponse theResponse) { + public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) { return true; } @Override - public boolean incomingRequest(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { + public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { return true; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java index 7bc016090c7..fcfd8271733 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java @@ -55,10 +55,10 @@ public class LoggingInterceptor extends InterceptorAdapter { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class); private Logger myLogger = ourLog; - private String myMessageFormat = "${operationType} - ${idOrResourceName}"; + @Override - public boolean incomingRequest(final RequestDetails theRequestDetails, final HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { + public boolean incomingRequestPostProcessed(final RequestDetails theRequestDetails, final HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { // Perform any string substitutions from the message format StrLookup lookup = new MyLookup(theRequest, theRequestDetails); diff --git a/hapi-fhir-base/src/site/resources/hapi.css b/hapi-fhir-base/src/site/resources/hapi.css index 3ebe1dc3436..8d397cbb201 100644 --- a/hapi-fhir-base/src/site/resources/hapi.css +++ b/hapi-fhir-base/src/site/resources/hapi.css @@ -123,3 +123,11 @@ dfn { a,a.externalLink,a:active,a:hover,a:link,a:visited { color: #993300; } + +DIV.sidebar-nav UL LI UL LI { + /* + list-style: initial; + list-style-type: circle; + */ + font-size: 0.9em; +} diff --git a/hapi-fhir-base/src/site/resources/svg/restful-server-interceptors.svg b/hapi-fhir-base/src/site/resources/svg/restful-server-interceptors.svg new file mode 100644 index 00000000000..2dbcd6d3980 --- /dev/null +++ b/hapi-fhir-base/src/site/resources/svg/restful-server-interceptors.svg @@ -0,0 +1 @@ +RESTfulServerInterceptorResource/PlainProviderMethodIncoming RequestincomingRequestPreProcessedreturn true;
Request is parsed
[Not supported by viewer]
incomingRequestPostProcessedreturn true;
Request is handled
[Not supported by viewer]
outgoingResponsereturn true;Response
\ No newline at end of file diff --git a/hapi-fhir-base/src/site/site.xml b/hapi-fhir-base/src/site/site.xml index 6ed7eff4881..6afd1d0c841 100644 --- a/hapi-fhir-base/src/site/site.xml +++ b/hapi-fhir-base/src/site/site.xml @@ -64,11 +64,14 @@ - + + + + @@ -90,7 +93,7 @@ - + diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml index 9f2f9fc7f39..1255d3bcbf3 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml @@ -352,7 +352,7 @@ - +

@@ -371,8 +371,8 @@ -

- +
+

RESTful clients (both Generic and Annotation-Driven) use Apache HTTP Client @@ -392,67 +392,12 @@ IRestfulClientFactory class from the FhirContext.

- - - -

- Both generic clients and annotation-driven clients support - Client Interceptors, - which may be registered in order to provide specific behaviour to each - client request. -

- -

- The following section shows some sample interceptors which may be used. -

- -
- - - -

- The following example shows how to configure your client to - use a specific username and password in every request. -

- - - - - - -
- - - - -

- An interceptor is provided with HAPI FHIR which can be used to - log every transaction. The interceptor can be configured to be extremely - verbose (logging entire transactions including HTTP headers and payload bodies) - or simply to log request URLs. -

- - - - - - -
- - - -

- An HTTP proxy may be configured directly on the - restful client factory, as shown below. -

- - - - - - -
- + +

+ Note that individual requests and responses + can be tweaked using interceptors. +

+
diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_client_interceptor.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_client_interceptor.xml new file mode 100644 index 00000000000..fca3b7d0680 --- /dev/null +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client_interceptor.xml @@ -0,0 +1,74 @@ + + + + + RESTful Client - HAPI FHIR + James Agnew + + + + +
+ +

+ Both generic clients and annotation-driven clients support + Client Interceptors, + which may be registered in order to provide specific behaviour to each + client request. +

+ +

+ The following section shows some sample interceptors which may be used. +

+ + + +

+ The following example shows how to configure your client to + use a specific username and password in every request. +

+ + + + + + +
+ + + + +

+ An interceptor is provided with HAPI FHIR which can be used to + log every transaction. The interceptor can be configured to be extremely + verbose (logging entire transactions including HTTP headers and payload bodies) + or simply to log request URLs. +

+ + + + + + +
+ + + +

+ An HTTP proxy may be configured directly on the + restful client factory, as shown below. +

+ + + + + + +
+ +
+ + + +
diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_server_interceptor.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_server_interceptor.xml new file mode 100644 index 00000000000..b8586589682 --- /dev/null +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_server_interceptor.xml @@ -0,0 +1,107 @@ + + + + + Server Interceptors - HAPI FHIR + James Agnew + + + + + +
+ + + + + Interceptors + +

+ The RESTful server provides a powerful mechanism for adding cross-cutting behaviour + to each incoming request that it processes. This mechanism consists of defining one or + more interceptors that will +

+ +

+ Interceptors must implement the + IServerInterceptor + interface (or extend the convenience + InterceptorAdapter + class provided). The RESTful server will normally invoke the interceptor at three + points in the execution of the client request. +

+ +
    +
  • + Before any processing at all is performed on the request, + incomingRequestPreProcessed will be invoked. This can be useful + if you wish to handle some requests completely outside of HAPI's processing + mechanism. If you are handling a request in your interceptor, you may + return false from your implementation method to signal to + HAPI that processing of the request should stop immediately. +
  • +
  • + Once the request is parsed (but before it is handled), + incomingRequestPostProcessed will be invoked. This method has + an additional parameter, the + RequestDetails + object which contains details about what operation is about to be + called, and what request parameters were receievd with that request. +
  • +
  • + After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method), + but before the actual response is returned to the client, + the outgoingResponse 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 outgoingResponse: The server + will invoke the one which corresponds to the response type + of the operation being invoked (resource, bundle, etc.) +
  • +
+ +
+ + +

+ To register an interceptor to the server, simply call + either registerInterceptor or setInterceptors + on your RestfulServer instance. +

+

+ Note that order is important: The server will invoke + incomingRequestPreProcessed and incomingRequestPostProcessed + in the same order that they are registered to the server. The server will + invoke outgoingResponse in the reverse order to the + order in which the interceptors were registered. This means that interceptors + can be thought of as "wrapping" the request. +

+ +
+ +
+ +
+ +

+ HAPI also provides built-in interceptors which may be useful. +

+ + + + +

+ The + LoggingInterceptor + can be used to generate a new log line (via SLF4j) for each incoming request. LoggingInterceptor + provides a flexible message format that can be used to provide a customized level + of detail about each incoming request. +

+ + + +
+ + + + diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java index 94aaf1c79a0..9c61a5b932b 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java @@ -25,6 +25,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.InOrder; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; @@ -51,22 +52,32 @@ public class InterceptorTest { private static int ourPort; private static Server ourServer; private static RestfulServer servlet; - private IServerInterceptor myInterceptor; + private IServerInterceptor myInterceptor1; + private IServerInterceptor myInterceptor2; @Test public void testInterceptorFires() throws Exception { - when(myInterceptor.incomingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor.incomingRequest(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); HttpResponse status = ourClient.execute(httpGet); IOUtils.closeQuietly(status.getEntity().getContent()); - verify(myInterceptor, times(1)).incomingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myInterceptor, times(1)).incomingRequest(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myInterceptor, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - verifyNoMoreInteractions(myInterceptor); + InOrder order = inOrder(myInterceptor1, myInterceptor2); + order.verify(myInterceptor1, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor2, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor1, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor2, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + + order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + verifyNoMoreInteractions(myInterceptor1); + verifyNoMoreInteractions(myInterceptor2); } @@ -77,8 +88,9 @@ public class InterceptorTest { @Before public void before() { - myInterceptor = mock(IServerInterceptor.class); - servlet.setInterceptors(Collections.singletonList(myInterceptor)); + myInterceptor1 = mock(IServerInterceptor.class); + myInterceptor2 = mock(IServerInterceptor.class); + servlet.setInterceptors(myInterceptor1, myInterceptor2); } diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html index 7231da9db54..421944f1c77 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html @@ -106,7 +106,7 @@ if (this.id) { if (this.id.substring(0,4) == 'inc_') { this.name = '_include'; - } else { + } else if (this.id.indexOf('autogen') == -1) { this.name = this.id; } } diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html index 77925d12eef..d9868986c22 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html @@ -7,6 +7,7 @@ +