More work on interceptors

This commit is contained in:
James Agnew 2015-08-17 18:35:51 -04:00
parent bb1e8b9ddd
commit c2fba2ce21
14 changed files with 423 additions and 236 deletions

View File

@ -258,11 +258,12 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
if (operationType != null) {
for (IServerInterceptor next : theServer.getInterceptors()) {
ActionRequestDetails details = new ActionRequestDetails(theRequest);
populateActionRequestDetailsForInterceptor(theRequest, details, theMethodParams);
next.incomingRequestPreHandled(operationType, details);
}
}
// Actuall invoke the method
// Actually invoke the method
try {
Method method = getMethod();
return method.invoke(getProvider(), theMethodParams);
@ -278,8 +279,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
}
/**
* Does this method have a parameter annotated with {@link ConditionalParamBinder}. Note that many operations don't
* actually support this paramter, so this will only return true occasionally.
* Does this method have a parameter annotated with {@link ConditionalParamBinder}. Note that many operations don't actually support this paramter, so this will only return true occasionally.
*/
public boolean isSupportsConditional() {
return mySupportsConditional;
@ -294,10 +294,9 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
protected byte[] loadRequestContents(RequestDetails theRequest) throws IOException {
/*
* This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on
* servlet-api in clients since there is no point. So we dynamically load a class that does the servlet processing
* in servers. Down the road it may make sense to just split the method binding classes into server and client
* versions, but this isn't actually a huge deal I don't think.
* This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on servlet-api in clients since there is no point. So we dynamically load a class
* that does the servlet processing in servers. Down the road it may make sense to just split the method binding classes into server and client versions, but this isn't actually a huge deal I
* don't think.
*/
IRequestReader reader = ourRequestReader;
if (reader == null) {
@ -322,9 +321,28 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
InputStream inputStream = reader.getInputStream(theRequest);
byte[] requestContents = IOUtils.toByteArray(inputStream);
theRequest.setRawRequest(requestContents);
return requestContents;
}
/**
* Subclasses may override this method (but should also call super.{@link #populateActionRequestDetailsForInterceptor(ActionRequestDetails, Object[])) to provide method specifics to the
* interceptors.
*
* @param theRequestDetails
* The server request details
* @param theDetails
* The details object to populate
* @param theMethodParams
* The method params as generated by the specific method binding
*/
protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
// TODO Auto-generated method stub
}
protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) {
BaseServerResponseException ex;
switch (theStatusCode) {
@ -347,7 +365,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theStatusCode);
// TODO: handle if something other than OO comes back
BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseReader);
ex = new UnprocessableEntityException(operationOutcome);
ex = new UnprocessableEntityException(myContext, operationOutcome);
break;
default:
ex = new UnclassifiedServerFailureException(theStatusCode, "Server responded with HTTP " + theStatusCode);
@ -414,14 +432,16 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
if (theProvider instanceof IResourceProvider) {
returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned " + toLogString(returnTypeFromRp) + " - Must return a resource type");
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned "
+ toLogString(returnTypeFromRp) + " - Must return a resource type");
}
}
Class<?> returnTypeFromMethod = theMethod.getReturnType();
if (getTags != null) {
if (!TagList.class.equals(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @" + GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName());
throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @"
+ GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName());
}
} else if (MethodOutcome.class.equals(returnTypeFromMethod)) {
// returns a method outcome
@ -434,13 +454,15 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
} else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !isResourceInterface(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
+ " returns a collection with generic type " + toLogString(returnTypeFromMethod)
+ " - Must return a resource type or a collection (List, Set) with a resource type parameter (e.g. List<Patient> or List<IBaseResource> )");
}
} else {
if (!isResourceInterface(returnTypeFromMethod) && !verifyIsValidResourceReturnType(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type (eg Patient, "
+ Bundle.class.getSimpleName() + ", " + IBundleProvider.class.getSimpleName() + ", etc., see the documentation for more details)");
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
+ " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type (eg Patient, " + Bundle.class.getSimpleName() + ", " + IBundleProvider.class.getSimpleName()
+ ", etc., see the documentation for more details)");
}
}
@ -470,12 +492,13 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
if (returnTypeFromRp != null) {
if (returnTypeFromAnnotation != null && !isResourceInterface(returnTypeFromAnnotation)) {
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
throw new ConfigurationException(
"Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
+ returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
}
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return " + returnTypeFromRp.getCanonicalName()
+ " (or a subclass of it) per IResourceProvider contract");
throw new ConfigurationException(
"Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName()
+ " per method annotation - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
}
returnType = returnTypeFromAnnotation;
} else {
@ -484,8 +507,8 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
} else {
if (!isResourceInterface(returnTypeFromAnnotation)) {
if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
throw new ConfigurationException(
"Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type");
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
+ " returns " + toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type");
}
returnType = returnTypeFromAnnotation;
} else {
@ -600,7 +623,8 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
if (obj1 == null) {
obj1 = object;
} else {
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both.");
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @"
+ obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both.");
}
}

View File

@ -83,6 +83,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
b.append(response.getVersionId().getValue());
}
theResponse.addHeader(headerLocation, b.toString());
}
protected abstract void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams);
@ -140,6 +141,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
@Override
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
byte[] requestContents = loadRequestContents(theRequest);
// if (requestContainsResource()) {
// requestContents = parseIncomingServerResource(theRequest);
// } else {

View File

@ -35,11 +35,12 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOutcomeReturningMethodBinding {
private Integer myIdParamIndex;
private String myResourceName;
private int myResourceParameterIndex;
private int myResourceParameterIndex = -1;
private Class<? extends IBaseResource> myResourceType;
private Class<? extends IIdType> myIdParamType;
@ -86,6 +87,22 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
}
@Override
protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
super.populateActionRequestDetailsForInterceptor(theRequestDetails, theDetails, theMethodParams);
/*
* If the method has no parsed resource parameter, we parse here in order to have something for the interceptor.
*/
if (myResourceParameterIndex != -1) {
theDetails.setResource((IBaseResource) theMethodParams[myResourceParameterIndex]);
} else {
theDetails.setResource(ResourceParameter.parseResourceFromRequest(theRequestDetails, this, myResourceType));
}
}
@Override
protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) {
if (myIdParamIndex != null) {

View File

@ -172,7 +172,7 @@ public class OperationParameter implements IParameter {
}
Class<? extends IBaseResource> wantedResourceType = theMethodBinding.getContext().getResourceDefinition("Parameters").getImplementingClass();
IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, theRequestContents, theMethodBinding, wantedResourceType);
IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, theMethodBinding, wantedResourceType);
RuntimeResourceDefinition def = ctx.getResourceDefinition(requestContents);

View File

@ -43,6 +43,7 @@ public class RequestDetails {
private IdDt myId;
private String myOperation;
private Map<String, String[]> myParameters;
private byte[] myRawRequest;
private String myRequestPath;
private RequestTypeEnum myRequestType;
private String myResourceName;
@ -78,6 +79,10 @@ public class RequestDetails {
return myParameters;
}
public byte[] getRawRequest() {
return myRawRequest;
}
/**
* The part of the request URL that comes after the server base.
* <p>
@ -172,6 +177,10 @@ public class RequestDetails {
}
public void setRawRequest(byte[] theRawRequest) {
myRawRequest = theRawRequest;
}
public void setRequestPath(String theRequestPath) {
assert theRequestPath.length() == 0 || theRequestPath.charAt(0) != '/';
myRequestPath = theRequestPath;

View File

@ -59,18 +59,16 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ResourceParameter implements IParameter {
private boolean myBinary;
private Mode myMode;
private Class<? extends IBaseResource> myResourceType;
public ResourceParameter(Class<? extends IResource> theParameterType, Object theProvider, Mode theMode) {
Validate.notNull(theParameterType, "theParameterType can not be null");
Validate.notNull(theMode, "theMode can not be null");
myResourceType = theParameterType;
myBinary = IBaseBinary.class.isAssignableFrom(theParameterType);
myMode = theMode;
Class<? extends IBaseResource> providerResourceType = null;
if (theProvider instanceof IResourceProvider) {
providerResourceType = ((IResourceProvider) theProvider).getResourceType();
@ -85,7 +83,7 @@ public class ResourceParameter implements IParameter {
public Mode getMode() {
return myMode;
}
public Class<? extends IBaseResource> getResourceType() {
return myResourceType;
}
@ -96,13 +94,15 @@ public class ResourceParameter implements IParameter {
}
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource)
throws InternalErrorException {
// TODO Auto-generated method stub
}
@Override
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding)
throws InternalErrorException, InvalidRequestException {
switch (myMode) {
case BODY:
try {
@ -116,18 +116,8 @@ public class ResourceParameter implements IParameter {
case RESOURCE:
break;
}
if (myBinary) {
FhirContext ctx = theRequest.getServer().getFhirContext();
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
binary.setContentType(ct);
binary.setContent(theRequestContents);
return binary;
}
IBaseResource retVal = loadResourceFromRequest(theRequest, theRequestContents, theMethodBinding, myResourceType);
IBaseResource retVal = parseResourceFromRequest(theRequest, theMethodBinding, myResourceType);
return retVal;
}
@ -155,12 +145,12 @@ public class ResourceParameter implements IParameter {
return charset;
}
public static IBaseResource loadResourceFromRequest(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
public static IBaseResource loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
FhirContext ctx = theRequest.getServer().getFhirContext();
final Charset charset = determineRequestCharset(theRequest);
Reader requestReader = createRequestReader(theRequestContents, charset);
Reader requestReader = createRequestReader(theRequest.getRawRequest(), charset);
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
if (encoding == null) {
String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
@ -186,14 +176,14 @@ public class ResourceParameter implements IParameter {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", theMethodBinding.getRestOperationType());
throw new InvalidRequestException(msg);
} else {
requestReader = new InputStreamReader(new ByteArrayInputStream(theRequestContents), charset);
requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.getRawRequest()), charset);
}
} else {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
throw new InvalidRequestException(msg);
}
}
}
IParser parser = encoding.newParser(ctx);
IBaseResource retVal;
@ -214,13 +204,29 @@ public class ResourceParameter implements IParameter {
MethodUtil.parseTagValue(tagList, nextTagComplete);
}
if (tagList.isEmpty() == false) {
((IResource)retVal).getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
((IResource) retVal).getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
}
}
return retVal;
}
public enum Mode{
public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
IBaseResource retVal;
if (IBaseBinary.class.isAssignableFrom(theResourceType)) {
FhirContext ctx = theRequest.getServer().getFhirContext();
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
binary.setContentType(ct);
binary.setContent(theRequest.getRawRequest());
retVal = binary;
} else {
retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType);
}
return retVal;
}
public enum Mode {
BODY, ENCODING, RESOURCE
}

View File

@ -33,6 +33,7 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
@ -40,50 +41,41 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
/**
* 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.
* 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.
* <p>
* <b>See:</b> See the <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_server_interceptor.html">server
* interceptor documentation</a> for more information on how to use this class.
* <b>See:</b> See the <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_server_interceptor.html">server interceptor documentation</a> for more information on how to use this class.
* </p>
*/
public interface IServerInterceptor {
/**
* This method is called upon any exception being thrown within the server's request processing code. This includes
* any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
* any runtime exceptions thrown by the server itself. This also includes any {@link AuthenticationException}s
* thrown.
* This method is called upon any exception being thrown within the server's request processing code. This includes any exceptions thrown within resource provider methods (e.g. {@link Search} and
* {@link Read} methods) as well as any runtime exceptions thrown by the server itself. This also includes any {@link AuthenticationException}s thrown.
* <p>
* Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>true</code>. In
* this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
* OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
* should return <code>false</code>, to indicate that they have handled the request and processing should stop.
* Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>true</code>. In this case, processing will continue, and the server will automatically generate an
* {@link BaseOperationOutcome OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they should return <code>false</code>, to indicate that
* they have handled the request and processing should stop.
* </p>
*
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean
* properties are not all guaranteed to be populated, depending on how early during processing the
* exception occurred.
* A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
* request which have been pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean properties are not all guaranteed to be populated, depending
* on how early during processing the exception occurred.
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to
* return <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
* @throws ServletException
* If this exception is thrown, it will be re-thrown up to the container for handling.
* @throws IOException
* If this exception is thrown, it will be re-thrown up to the container for handling.
*/
boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException, IOException;
boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws ServletException, IOException;
/**
* This method is called just before the actual implementing server method is invoked.
@ -92,145 +84,21 @@ public interface IServerInterceptor {
* </p>
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
* request which have been pulled out of the {@link HttpServletRequest servlet request}.
* @param theRequest
* 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 <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* 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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the client.
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the
* client.
*/
boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
/**
* This method is called before any other processing takes place for each incoming request. It may be used to provide
* alternate handling for some requests, or to screen requests before they are handled, etc.
* <p>
* Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server)
* </p>
*
* @param theRequest
* 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 <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
*/
boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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 <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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 <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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 <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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 <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* Invoked before an incoming request is processed
*
@ -239,37 +107,131 @@ public interface IServerInterceptor {
* @param theOperation
* The type of operation that the FHIR server has determined that the client is trying to invoke
* @param theRequestDetails
* An object which will be populated with any relevant details about the incoming request (this includes
* the HttpServletRequest)
* An object which will be populated with any relevant details about the incoming request (this includes the HttpServletRequest)
*/
void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theRequestDetails);
/**
* This method is called upon any exception being thrown within the server's request processing code. This includes
* any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
* any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them
* returns a non-<code>null</code> response or the end of the list is reached), after which
* {@link #handleException(RequestDetails, BaseServerResponseException, HttpServletRequest, HttpServletResponse)} is
* called for each interceptor.
* This method is called before any other processing takes place for each incoming request. It may be used to provide alternate handling for some requests, or to screen requests before they are
* handled, etc.
* <p>
* Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server)
* </p>
*
* @param theRequest
* 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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
*/
boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the
* client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
* request which have been pulled out of the {@link HttpServletRequest servlet request}.
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the
* client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
* request which have been pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the
* client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
* request which have been pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* 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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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. If thrown, processing will stop and an HTTP 401 will be returned to the
* client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called upon any exception being thrown within the server's request processing code. This includes any exceptions thrown within resource provider methods (e.g. {@link Search} and
* {@link Read} methods) as well as any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them returns a non-<code>null</code> response or
* the end of the list is reached), after which {@link #handleException(RequestDetails, BaseServerResponseException, HttpServletRequest, HttpServletResponse)} is called for each interceptor.
* <p>
* This may be used to add an OperationOutcome to a response, or to convert between exception types for any reason.
* </p>
* <p>
* Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>null</code>. In
* this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
* OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
* should return a non-<code>null</code>, to indicate that they have handled the request and processing should stop.
* Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>null</code>. In this case, processing will continue, and the server will automatically generate an
* {@link BaseOperationOutcome OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they should return a non-<code>null</code>, to indicate
* that they have handled the request and processing should stop.
* </p>
*
* @return Returns the new exception to use for processing, or <code>null</code> if this interceptor is not trying to
* modify the exception. For example, if this interceptor has nothing to do with exception processing, it
* should always return <code>null</code>. If this interceptor adds an OperationOutcome to the exception, it
* should return an exception.
* @return Returns the new exception to use for processing, or <code>null</code> if this interceptor is not trying to modify the exception. For example, if this interceptor has nothing to do with
* exception processing, it should always return <code>null</code>. If this interceptor adds an OperationOutcome to the exception, it should return an exception.
*/
BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException;
public static class ActionRequestDetails {
private final IIdType myId;
private boolean myLocked = false;
private IBaseResource myResource;
private final String myResourceType;
public ActionRequestDetails(IIdType theId, String theResourceType) {
@ -282,6 +244,11 @@ public interface IServerInterceptor {
myResourceType = theRequestDetails.getResourceName();
}
public ActionRequestDetails(IIdType theId, String theResourceType, IBaseResource theResource) {
this(theId, theResourceType);
myResource = theResource;
}
/**
* Returns the ID of the incoming request (typically this is from the request URL)
*/
@ -290,12 +257,43 @@ public interface IServerInterceptor {
}
/**
* Returns the resource type this request pertains to, or <code>null</code> if this request is not type specific
* (e.g. server-history)
* For requests where a resource is passed from the client to the server (e.g. create, update, etc.) this method will return the resource which was provided by the client. Otherwise, this method
* will return <code>null</code>.
* <p>
* Note that this method is currently only populated if the handling method has a parameter annotated with the {@link ResourceParam} annotation.
* </p>
*/
public IBaseResource getResource() {
return myResource;
}
/**
* Returns the resource type this request pertains to, or <code>null</code> if this request is not type specific (e.g. server-history)
*/
public String getResourceType() {
return myResourceType;
}
/**
* Prevent any further changes to this object
*/
public void lock() {
myLocked = true;
}
/**
* This method should not be called by client code
*/
public void setResource(IBaseResource theObject) {
validateNotLocked();
myResource = theObject;
}
private void validateNotLocked() {
if (myLocked) {
throw new IllegalStateException("Values on this object may not be changed");
}
}
}
}

View File

@ -1271,7 +1271,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource));
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource), theResource);
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
updateEntity(theResource, entity, false, null, thePerformIndexing, true);
@ -2279,7 +2279,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(resourceId, getResourceName());
ActionRequestDetails requestDetails = new ActionRequestDetails(resourceId, getResourceName(), theResource);
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
// Perform update

View File

@ -79,7 +79,7 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
@Override
public MethodOutcome validate(T theResource, IdDt theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, null);
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, null, theResource);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
final OperationOutcome oo = new OperationOutcome();

View File

@ -43,8 +43,10 @@ import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
@ -59,6 +61,11 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
@Override
public List<IResource> transaction(List<IResource> theResources) {
ourLog.info("Beginning transaction with {} resources", theResources.size());
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
long start = System.currentTimeMillis();
Set<IdDt> allIds = new HashSet<IdDt>();

View File

@ -240,6 +240,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(Bundle theRequest) {
ActionRequestDetails requestDetails = new ActionRequestDetails(null, "Bundle", theRequest);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
String theActionName = "Transaction";
return transaction(theRequest, theActionName);
}

View File

@ -325,6 +325,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
ActionRequestDetails details = detailsCapt.getValue();
assertNotNull(details.getId());
assertEquals("Patient", details.getResourceType());
assertEquals(Patient.class, details.getResource().getClass());
reset(ourInterceptor);
@ -2913,13 +2914,21 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
Thread.sleep(1000);
reset(ourInterceptor);
retrieved.getIdentifierFirstRep().setValue("002");
MethodOutcome outcome2 = ourPatientDao.update(retrieved);
assertEquals(outcome.getId().getIdPart(), outcome2.getId().getIdPart());
assertNotEquals(outcome.getId().getVersionIdPart(), outcome2.getId().getVersionIdPart());
assertEquals("2", outcome2.getId().getVersionIdPart());
// Verify interceptor
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.UPDATE), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertNotNull(details.getId());
assertEquals("Patient", details.getResourceType());
assertEquals(Patient.class, details.getResource().getClass());
Date now2 = new Date();
Patient retrieved2 = ourPatientDao.read(outcome.getId().toVersionless());

View File

@ -2,6 +2,11 @@ package ca.uhn.fhir.jpa.dao;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.InputStream;
import java.sql.SQLException;
@ -12,8 +17,11 @@ import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ca.uhn.fhir.context.FhirContext;
@ -38,6 +46,7 @@ import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
@ -45,6 +54,8 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
public class FhirSystemDaoDstu2Test extends BaseJpaTest {
@ -54,6 +65,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaTest {
private static IFhirResourceDao<Observation> ourObservationDao;
private static IFhirResourceDao<Patient> ourPatientDao;
private static IFhirSystemDao<Bundle> ourSystemDao;
private static IServerInterceptor ourInterceptor;
private void deleteEverything() {
FhirSystemDaoDstu2Test.doDeleteEverything(ourSystemDao);
@ -390,6 +402,24 @@ public class FhirSystemDaoDstu2Test extends BaseJpaTest {
assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus());
String patientId = respEntry.getResponse().getLocation();
assertThat(patientId, not(containsString("test")));
/*
* Interceptor should have been called once for the transaction, and once for the
* embedded operation
*/
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.TRANSACTION), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Bundle", details.getResourceType());
assertEquals(Bundle.class, details.getResource().getClass());
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture());
details = detailsCapt.getValue();
assertNotNull(details.getId());
assertEquals("Patient", details.getResourceType());
assertEquals(Patient.class, details.getResource().getClass());
}
@Test
@ -666,6 +696,35 @@ public class FhirSystemDaoDstu2Test extends BaseJpaTest {
assertEquals(Bundle.class, nextEntry.getResource().getClass());
Bundle respBundle = (Bundle) nextEntry.getResource();
assertEquals(1, respBundle.getTotal().intValue());
/*
* Interceptor should have been called once for the transaction, and once for the
* embedded operation
*/
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.TRANSACTION), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Bundle", details.getResourceType());
assertEquals(Bundle.class, details.getResource().getClass());
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.READ), detailsCapt.capture());
details = detailsCapt.getValue();
assertEquals(idv1.toUnqualifiedVersionless(), details.getId());
assertEquals("Patient", details.getResourceType());
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.VREAD), detailsCapt.capture());
details = detailsCapt.getValue();
assertEquals(idv1.toUnqualified(), details.getId());
assertEquals("Patient", details.getResourceType());
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor, times(1)).incomingRequestPreHandled(eq(RestOperationTypeEnum.SEARCH_TYPE), detailsCapt.capture());
details = detailsCapt.getValue();
assertEquals("Patient", details.getResourceType());
}
@Test
@ -1122,6 +1181,16 @@ public class FhirSystemDaoDstu2Test extends BaseJpaTest {
ourPatientDao = ourCtx.getBean("myPatientDaoDstu2", IFhirResourceDao.class);
ourObservationDao = ourCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class);
ourSystemDao = ourCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
ourInterceptor = mock(IServerInterceptor.class);
DaoConfig daoConfig = ourCtx.getBean(DaoConfig.class);
daoConfig.setInterceptors(ourInterceptor);
}
@Before
public void before() {
reset(ourInterceptor);
}
static void doDeleteEverything(IFhirSystemDao<Bundle> systemDao) {

View File

@ -1,8 +1,12 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
@ -27,6 +31,7 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
@ -96,6 +101,23 @@ public class ServerActionInterceptorTest {
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Patient", details.getResourceType());
assertEquals(Patient.class, details.getResource().getClass());
assertEquals("FAMILY", ((Patient) details.getResource()).getName().get(0).getFamily().get(0).getValue());
}
@Test
public void testCreateWhereMethodHasNoResourceParam() throws Exception {
Observation observation = new Observation();
observation.getCode().setText("OBSCODE");
ourFhirClient.create().resource(observation).execute();
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Observation", details.getResourceType());
assertEquals(Observation.class, details.getResource().getClass());
assertEquals("OBSCODE", ((Observation) details.getResource()).getCode().getText());
}
@Test
@ -111,8 +133,11 @@ public class ServerActionInterceptorTest {
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Patient", details.getResourceType());
assertEquals("Patient/123", details.getId().getValue());
assertEquals(Patient.class, details.getResource().getClass());
assertEquals("FAMILY", ((Patient) details.getResource()).getName().get(0).getFamily().get(0).getValue());
assertEquals("Patient/123", ((Patient) details.getResource()).getId().getValue());
}
@Test
public void testHistorySystem() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
@ -160,7 +185,7 @@ public class ServerActionInterceptorTest {
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.registerInterceptor(new ResponseHighlighterInterceptor());
servlet.setResourceProviders(new DummyPatientResourceProvider());
servlet.setResourceProviders(new DummyPatientResourceProvider(), new DummyObservationResourceProvider());
servlet.setPlainProviders(new PlainProvider());
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(servlet);
@ -175,11 +200,11 @@ public class ServerActionInterceptorTest {
ourInterceptor = mock(InterceptorAdapter.class);
servlet.registerInterceptor(ourInterceptor);
ourCtx.getRestfulClientFactory().setSocketTimeout(240*1000);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
}
@Before
@ -247,4 +272,22 @@ public class ServerActionInterceptorTest {
}
public static class DummyObservationResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Observation.class;
}
@Create()
public MethodOutcome create(@ResourceParam String theBody) {
Observation retVal = new Observation();
retVal.setId("Observation/123/_history/2");
return new MethodOutcome(retVal.getId());
}
}
}