Add new interceptor hook for auditing

This commit is contained in:
jamesagnew 2015-08-16 22:09:01 -04:00
parent 89a7750bf4
commit 330dbde983
57 changed files with 893 additions and 274 deletions

View File

@ -93,7 +93,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_api_version}</version>
<scope>compile</scope>
</dependency>

View File

@ -149,7 +149,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_api_version}</version>
<scope>provided</scope>
</dependency>

View File

@ -115,7 +115,22 @@ public enum RestOperationTypeEnum {
/**
* Load the server's metadata
*/
METADATA("metadata"),
METADATA("metadata"),
/**
* $meta-add extended operation
*/
META_ADD("$meta-add"),
/**
* $meta-add extended operation
*/
META("$meta"),
/**
* $meta-delete extended operation
*/
META_DELETE("$meta-delete"),
;

View File

@ -38,7 +38,7 @@ class AddTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.ADD_TAGS;
}

View File

@ -107,7 +107,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return null;
}
@ -171,7 +171,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
} finally {
reader.close();
}
invokeServerMethod(params);
invokeServerMethod(theServer, theRequest, params);
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);

View File

@ -19,7 +19,7 @@ package ca.uhn.fhir.rest.method;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.InputStream;
@ -79,6 +79,8 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.ReflectionUtil;
public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T> {
@ -102,7 +104,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
myMethod = theMethod;
myContext = theContext;
myProvider = theProvider;
myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getResourceOperationType());
myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getRestOperationType());
for (IParameter next : myParameters) {
if (next instanceof ConditionalParamBinder) {
@ -232,7 +234,17 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
*/
public abstract String getResourceName();
public abstract RestOperationTypeEnum getResourceOperationType();
public abstract RestOperationTypeEnum getRestOperationType();
/**
* Determine which operation is being fired for a specific request
*
* @param theRequestDetails
* The request
*/
public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
return getRestOperationType();
}
public abstract boolean incomingServerRequestMatchesMethod(RequestDetails theRequest);
@ -240,7 +252,17 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
public abstract void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException;
protected Object invokeServerMethod(Object[] theMethodParams) {
protected final Object invokeServerMethod(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) {
// Handle server action interceptors
RestOperationTypeEnum operationType = getRestOperationType(theRequest);
if (operationType != null) {
for (IServerInterceptor next : theServer.getInterceptors()) {
ActionRequestDetails details = new ActionRequestDetails(theRequest);
next.incomingRequestPreHandled(operationType, details);
}
}
// Actuall invoke the method
try {
Method method = getMethod();
return method.invoke(getProvider(), theMethodParams);
@ -448,12 +470,12 @@ 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 {
@ -462,8 +484,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 {

View File

@ -152,7 +152,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
HttpServletResponse servletResponse = theRequest.getServletResponse();
MethodOutcome response;
try {
response = (MethodOutcome) invokeServerMethod(params);
response = (MethodOutcome) invokeServerMethod(theServer, theRequest, params);
} catch (InternalErrorException e) {
ourLog.error("Internal error during method invocation", e);
EncodingEnum encodingNotNull = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest());
@ -182,7 +182,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
}
boolean allowPrefer = false;
switch (getResourceOperationType()) {
switch (getRestOperationType()) {
case CREATE:
if (response == null) {
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName()

View File

@ -233,7 +233,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
throw new IllegalStateException("Should not get here!");
}
public abstract Object invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
public abstract Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
@Override
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
@ -265,7 +265,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
}
Object resultObj = invokeServer(theRequest, params);
Object resultObj = invokeServer(theServer, theRequest, params);
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
boolean respondGzip = theRequest.isRespondGzip();

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -71,8 +72,8 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
}
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
IBaseResource conf = (IBaseResource) invokeServerMethod(theMethodParams);
public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
IBaseResource conf = (IBaseResource) invokeServerMethod(theServer, theRequest, theMethodParams);
return new SimpleBundleProvider(conf);
}
@ -90,7 +91,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.METADATA;
}

View File

@ -38,7 +38,7 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.CREATE;
}

View File

@ -86,7 +86,7 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.DELETE;
}

View File

@ -38,7 +38,7 @@ public class DeleteTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.DELETE_TAGS;
}

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -85,17 +86,17 @@ public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBindi
}
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
if (myIdParamIndex != null) {
theMethodParams[myIdParamIndex] = theRequest.getId();
}
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
return toResourceList(response);
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.SEARCH_TYPE;
}

View File

@ -87,7 +87,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.GET_TAGS;
}
@ -178,7 +178,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
params[myVersionIdParamIndex] = theRequest.getId();
}
TagList resp = (TagList) invokeServerMethod(params);
TagList resp = (TagList) invokeServerMethod(theServer, theRequest, params);
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);

View File

@ -43,6 +43,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -87,7 +88,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return myResourceOperationType;
}
@ -154,12 +155,12 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
if (myIdParamIndex != null) {
theMethodParams[myIdParamIndex] = theRequest.getId();
}
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
final IBundleProvider resources = toResourceList(response);

View File

@ -49,6 +49,7 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
@ -166,7 +167,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return myOtherOperatiopnType;
}
@ -222,7 +223,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public Object invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
public Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
if (theRequest.getRequestType() == RequestTypeEnum.POST) {
// always ok
} else if (theRequest.getRequestType() == RequestTypeEnum.GET) {
@ -244,7 +245,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
theMethodParams[myIdParamIndex] = theRequest.getId();
}
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
IBundleProvider retVal = toResourceList(response);
return retVal;
}

View File

@ -48,6 +48,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -91,6 +92,15 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
}
@Override
public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
if (mySupportsVersion && theRequestDetails.getId().hasVersionIdPart()) {
return RestOperationTypeEnum.VREAD;
} else {
return RestOperationTypeEnum.READ;
}
}
@Override
public List<Class<?>> getAllowableParamAnnotations() {
ArrayList<Class<?>> retVal = new ArrayList<Class<?>>();
@ -99,7 +109,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
}
@ -192,13 +202,13 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
}
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
theMethodParams[myIdIndex] = MethodUtil.convertIdToType(theRequest.getId(), myIdParameterType);
if (myVersionIdIndex != null) {
theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart());
}
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
IBundleProvider retVal = toResourceList(response);
if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) {

View File

@ -46,8 +46,8 @@ public class RequestDetails {
private String myRequestPath;
private RequestTypeEnum myRequestType;
private String myResourceName;
private RestOperationTypeEnum myResourceOperationType;
private boolean myRespondGzip;
private RestOperationTypeEnum myRestOperationType;
private String mySecondaryOperation;
private RestfulServer myServer;
private HttpServletRequest myServletRequest;
@ -96,8 +96,8 @@ public class RequestDetails {
return myResourceName;
}
public RestOperationTypeEnum getResourceOperationType() {
return myResourceOperationType;
public RestOperationTypeEnum getRestOperationType() {
return myRestOperationType;
}
public String getSecondaryOperation() {
@ -185,14 +185,14 @@ public class RequestDetails {
myResourceName = theResourceName;
}
public void setResourceOperationType(RestOperationTypeEnum theResourceOperationType) {
myResourceOperationType = theResourceOperationType;
}
public void setRespondGzip(boolean theRespondGzip) {
myRespondGzip = theRespondGzip;
}
public void setRestOperationType(RestOperationTypeEnum theRestOperationType) {
myRestOperationType = theRestOperationType;
}
public void setSecondaryOperation(String theSecondaryOperation) {
mySecondaryOperation = theSecondaryOperation;
}

View File

@ -48,6 +48,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.BaseQueryParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -127,7 +128,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.SEARCH_TYPE;
}
@ -277,12 +278,12 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
if (myIdParamIndex != null) {
theMethodParams[myIdParamIndex] = theRequest.getId();
}
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
return toResourceList(response);

View File

@ -44,6 +44,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.TransactionParameter;
import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -75,7 +76,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.TRANSACTION;
}
@ -118,7 +119,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
@SuppressWarnings("unchecked")
@Override
public Object invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
public Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
/*
* The design of HAPI's transaction method for DSTU1 support assumed that a transaction was just an update on a
@ -127,7 +128,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
*/
if (myTransactionParamStyle == ParamStyle.RESOURCE_BUNDLE) {
// This is the DSTU2 style
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
return response;
}
@ -145,7 +146,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
}
// Call the server implementation method
Object response = invokeServerMethod(theMethodParams);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
IBundleProvider retVal = toResourceList(response);
/*

View File

@ -47,7 +47,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.UPDATE;
}

View File

@ -44,7 +44,7 @@ public class ValidateMethodBindingDstu1 extends BaseOutcomeReturningMethodBindin
}
@Override
public RestOperationTypeEnum getResourceOperationType() {
public RestOperationTypeEnum getRestOperationType() {
return RestOperationTypeEnum.VALIDATE;
}

View File

@ -166,7 +166,7 @@ public class ResourceParameter implements IParameter {
String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
if (ctValue != null) {
if (ctValue.startsWith("application/x-www-form-urlencoded")) {
String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getResourceOperationType());
String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
throw new InvalidRequestException(msg);
}
}
@ -183,13 +183,13 @@ public class ResourceParameter implements IParameter {
}
encoding = MethodUtil.detectEncodingNoDefault(body);
if (encoding == null) {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", theMethodBinding.getResourceOperationType());
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", theMethodBinding.getRestOperationType());
throw new InvalidRequestException(msg);
} else {
requestReader = new InputStreamReader(new ByteArrayInputStream(theRequestContents), charset);
}
} else {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getResourceOperationType());
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
throw new InvalidRequestException(msg);
}
}

View File

@ -19,8 +19,7 @@ package ca.uhn.fhir.rest.server;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
@ -640,7 +639,7 @@ public class RestfulServer extends HttpServlet {
String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION);
if (getPagingProvider() != null && isNotBlank(pagingAction)) {
requestDetails.setResourceOperationType(RestOperationTypeEnum.GET_PAGE);
requestDetails.setRestOperationType(RestOperationTypeEnum.GET_PAGE);
if (theRequestType != RequestTypeEnum.GET) {
/*
* We reconstruct the link-self URL using the request parameters, and this would break if the parameters came in using a POST. We could probably work around that but why bother unless
@ -665,8 +664,9 @@ public class RestfulServer extends HttpServlet {
}
}
requestDetails.setResourceOperationType(resourceMethod.getResourceOperationType());
requestDetails.setRestOperationType(resourceMethod.getRestOperationType());
// Handle server interceptors
for (IServerInterceptor next : myInterceptors) {
boolean continueProcessing = next.incomingRequestPostProcessed(requestDetails, theRequest, theResponse);
if (!continueProcessing) {
@ -674,7 +674,15 @@ public class RestfulServer extends HttpServlet {
return;
}
}
/*
* Actualy invoke the server method. This call is to a HAPI method
* binding, which is an object that wraps a specific implementing (user-supplied)
* method, but handles its input and provides its output back to the client.
*
* This is basically the end of processing for a successful request,
* since the method binding replies to the client and closes the response.
*/
resourceMethod.invokeServer(this, requestDetails);
} catch (NotModifiedException e) {

View File

@ -1,64 +0,0 @@
package ca.uhn.fhir.rest.server.interceptor;
import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
/**
* Action interceptors are invoked by the server upon specific fhir operations, such as "read" (HTTP GET) or "create" (HTTP POST). They can be thought of as being a layer "above"
* {@link IServerInterceptor} interceptors.
* <p>
* These interceptors are useful as a means of adding authentication checks or audit operations on top of a server, since the HAPI RestfulServer translates the incoming requests into higher level
* operations.
* </p>
* <p>
* Note that unlike {@link IServerInterceptor}s, {@link IServerActionInterceptor}s do not have the ability to handle a request themselves and stop processing.
* </p>
*/
public interface IServerActionInterceptor extends IServerInterceptor {
/**
* Invoked before an incoming request is processed
*
* @param theServletRequest
* The incoming servlet request as provided by the servlet container
* @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
*/
void preAction(HttpServletRequest theServletRequest, ActionOperationEnum theOperation, ActionRequestDetails theRequestDetails);
/**
* Represents the type of operation being invoked for a {@link IServerActionInterceptor#preAction(HttpServletRequest, ActionOperationEnum, ActionRequestDetails) preAction} call
*/
public static enum ActionOperationEnum {
READ, VREAD
}
public static class ActionRequestDetails {
private final IIdType myId;
private final IBaseResource myRequestResource;
public ActionRequestDetails(IIdType theId, IBaseResource theRequestResource) {
super();
myId = theId;
myRequestResource = theRequestResource;
}
/**
* Returns the ID of the incoming request (typically this is from the request URL)
*/
public IIdType getId() {
return myId;
}
/**
* Returns the incoming resource from the request (this will be populated only for operations which receive a resource, such as "create" and "update")
*/
public IBaseResource getRequestResource() {
return myRequestResource;
}
}
}

View File

@ -27,12 +27,14 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
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.Search;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -44,8 +46,6 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
* <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>
*
* @see
*/
public interface IServerInterceptor {
@ -83,7 +83,7 @@ public interface IServerInterceptor {
* @throws IOException
* If this exception is thrown, it will be re-thrown up to the container for handling.
*/
public 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.
@ -109,7 +109,7 @@ public interface IServerInterceptor {
* 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.
*/
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
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
@ -129,7 +129,7 @@ public interface IServerInterceptor {
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
*/
public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse);
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
@ -153,7 +153,7 @@ public interface IServerInterceptor {
* 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.
*/
public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
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
@ -177,7 +177,7 @@ public interface IServerInterceptor {
* 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.
*/
public boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
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
@ -203,7 +203,7 @@ public interface IServerInterceptor {
* 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.
*/
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
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
@ -229,20 +229,33 @@ public interface IServerInterceptor {
* 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.
*/
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* Invoked before an incoming request is processed
*
* @param theServletRequest
* The incoming servlet request as provided by the servlet container
* @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)
*/
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.
* {@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>true</code>. In
* 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.
@ -255,4 +268,34 @@ public interface IServerInterceptor {
*/
BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException;
public static class ActionRequestDetails {
private final IIdType myId;
private final String myResourceType;
public ActionRequestDetails(IIdType theId, String theResourceType) {
myId = theId;
myResourceType = theResourceType;
}
public ActionRequestDetails(RequestDetails theRequestDetails) {
myId = theRequestDetails.getId();
myResourceType = theRequestDetails.getResourceName();
}
/**
* Returns the ID of the incoming request (typically this is from the request URL)
*/
public IIdType getId() {
return myId;
}
/**
* 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;
}
}
}

View File

@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -38,8 +39,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
* Base class for {@link IServerInterceptor} implementations. Provides a No-op implementation
* of all methods, always returning <code>true</code>
*/
@SuppressWarnings("unused")
public class InterceptorAdapter implements IServerInterceptor, IServerActionInterceptor {
public class InterceptorAdapter implements IServerInterceptor {
@Override
public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
@ -83,7 +83,7 @@ public class InterceptorAdapter implements IServerInterceptor, IServerActionInte
}
@Override
public void preAction(HttpServletRequest theServletRequest, ActionOperationEnum theOperation, ActionRequestDetails theRequestDetails) {
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theRequestDetails) {
// nothing
}

View File

@ -141,13 +141,13 @@ public class LoggingInterceptor extends InterceptorAdapter {
*/
if ("operationType".equals(theKey)) {
if (myRequestDetails.getResourceOperationType() != null) {
return myRequestDetails.getResourceOperationType().getCode();
if (myRequestDetails.getRestOperationType() != null) {
return myRequestDetails.getRestOperationType().getCode();
}
return "";
} else if ("operationName".equals(theKey)) {
if (myRequestDetails.getResourceOperationType() != null) {
switch (myRequestDetails.getResourceOperationType()) {
if (myRequestDetails.getRestOperationType() != null) {
switch (myRequestDetails.getRestOperationType()) {
case EXTENDED_OPERATION_INSTANCE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:

View File

@ -127,20 +127,23 @@
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

View File

@ -60,6 +60,11 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@ -95,6 +100,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
@ -105,13 +111,10 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
public abstract class BaseHapiFhirDao implements IDao {
public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
@ -305,6 +308,21 @@ public abstract class BaseHapiFhirDao implements IDao {
}
}
protected void notifyInterceptors(RestOperationTypeEnum operationType, ActionRequestDetails requestDetails) {
if (requestDetails.getId() != null && requestDetails.getId().hasResourceType() && isNotBlank(requestDetails.getResourceType())) {
if (requestDetails.getId().getResourceType().equals(requestDetails.getResourceType()) == false) {
throw new InternalErrorException("Inconsistent server state - Resource types don't match: " + requestDetails.getId().getResourceType() + " / " + requestDetails.getResourceType());
}
}
List<IServerInterceptor> interceptors = getConfig().getInterceptors();
if (interceptors == null) {
return;
}
for (IServerInterceptor next : interceptors) {
next.incomingRequestPreHandled(operationType, requestDetails);
}
}
protected DaoConfig getConfig() {
return myConfig;
}

View File

@ -106,6 +106,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
@ -125,6 +126,7 @@ 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.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ObjectUtil;
@ -1192,6 +1194,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version");
}
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, theId.getResourceType());
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
ResourceTable savedEntity = updateEntity(null, entity, true, new Date());
notifyWriteCompleted();
@ -1212,9 +1218,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
Long pid = resource.iterator().next();
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
ActionRequestDetails requestDetails = new ActionRequestDetails(idToDelete, idToDelete.getResourceType());
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
// Perform delete
ResourceTable savedEntity = updateEntity(null, entity, true, new Date());
notifyWriteCompleted();
@ -1241,7 +1252,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return toMethodOutcome(entity, theResource).setCreated(false);
}
}
if (isNotBlank(theResource.getId().getIdPart())) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException("This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
@ -1259,6 +1270,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource));
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
updateEntity(theResource, entity, false, null, thePerformIndexing, true);
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
@ -1274,6 +1289,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public TagList getAllResourceTags() {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null,null);
notifyInterceptors(RestOperationTypeEnum.GET_TAGS, requestDetails);
StopWatch w = new StopWatch();
TagList tags = super.getTags(myResourceType, null);
ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart());
@ -1286,8 +1305,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return myResourceType;
}
public String getResourceName() {
return myResourceName;
}
@Override
public TagList getTags(IIdType theResourceId) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theResourceId, null);
notifyInterceptors(RestOperationTypeEnum.GET_TAGS, requestDetails);
StopWatch w = new StopWatch();
TagList retVal = super.getTags(myResourceType, theResourceId);
ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart());
@ -1296,6 +1324,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public IBundleProvider history(Date theSince) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
notifyInterceptors(RestOperationTypeEnum.HISTORY_SYSTEM, requestDetails);
StopWatch w = new StopWatch();
IBundleProvider retVal = super.history(myResourceName, null, theSince);
ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart());
@ -1304,6 +1336,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public IBundleProvider history(final IIdType theId, final Date theSince) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, getResourceName());
notifyInterceptors(RestOperationTypeEnum.HISTORY_INSTANCE, requestDetails);
final InstantDt end = createHistoryToTimestamp();
final String resourceType = getContext().getResourceDefinition(myResourceType).getName();
@ -1400,6 +1436,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public IBundleProvider history(Long theId, Date theSince) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, getResourceName());
notifyInterceptors(RestOperationTypeEnum.HISTORY_TYPE, requestDetails);
StopWatch w = new StopWatch();
IBundleProvider retVal = super.history(myResourceName, theId, theSince);
ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart());
@ -1492,6 +1532,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public MetaDt metaAddOperation(IIdType theResourceId, MetaDt theMetaAdd) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theResourceId, getResourceName());
notifyInterceptors(RestOperationTypeEnum.META_ADD, requestDetails);
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theResourceId);
if (entity == null) {
@ -1532,6 +1576,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public MetaDt metaDeleteOperation(IIdType theResourceId, MetaDt theMetaDel) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theResourceId, getResourceName());
notifyInterceptors(RestOperationTypeEnum.META_DELETE, requestDetails);
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theResourceId);
if (entity == null) {
@ -1566,6 +1614,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public MetaDt metaGetOperation() {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, getResourceName());
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
q.setParameter("res_type", myResourceName);
@ -1578,6 +1630,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public MetaDt metaGetOperation(IIdType theId) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, getResourceName());
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
Long pid = super.translateForcedIdToPid(theId);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type AND t.myResourceId = :res_id)";
@ -1622,6 +1678,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
public T read(IIdType theId) {
validateResourceTypeAndThrowIllegalArgumentException(theId);
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, getResourceName());
RestOperationTypeEnum operationType = theId.hasVersionIdPart() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
notifyInterceptors(operationType, requestDetails);
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theId);
validateResourceType(entity);
@ -1691,6 +1752,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, getResourceName());
notifyInterceptors(RestOperationTypeEnum.DELETE_TAGS, requestDetails);
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theId);
if (entity == null) {
@ -1728,6 +1793,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public IBundleProvider search(final SearchParameterMap theParams) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, getResourceName());
notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails);
StopWatch w = new StopWatch();
final InstantDt now = InstantDt.withCurrentTime();
@ -2205,6 +2274,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
throw new InvalidRequestException("Trying to update " + resourceId + " but this is not the current version");
}
if (resourceId.hasResourceType() && !resourceId.getResourceType().equals(getResourceName())) {
throw new UnprocessableEntityException("Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]");
}
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(resourceId, getResourceName());
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
// Perform update
ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true);
notifyWriteCompleted();

View File

@ -41,8 +41,10 @@ import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
public abstract class BaseHapiFhirSystemDao<T> extends BaseHapiFhirDao implements IFhirSystemDao<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirSystemDao.class);
@ -50,6 +52,10 @@ public abstract class BaseHapiFhirSystemDao<T> extends BaseHapiFhirDao implement
@Transactional(propagation=Propagation.REQUIRED)
@Override
public void deleteAllTagsOnServer() {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
notifyInterceptors(RestOperationTypeEnum.DELETE_TAGS, requestDetails);
myEntityManager.createQuery("DELETE from ResourceTag t").executeUpdate();
}
@ -77,6 +83,10 @@ public abstract class BaseHapiFhirSystemDao<T> extends BaseHapiFhirDao implement
@Override
public IBundleProvider history(Date theSince) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
notifyInterceptors(RestOperationTypeEnum.HISTORY_SYSTEM, requestDetails);
StopWatch w = new StopWatch();
IBundleProvider retVal = super.history(null, null, theSince);
ourLog.info("Processed global history in {}ms", w.getMillisAndRestart());
@ -85,6 +95,10 @@ public abstract class BaseHapiFhirSystemDao<T> extends BaseHapiFhirDao implement
@Override
public TagList getAllTags() {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
notifyInterceptors(RestOperationTypeEnum.GET_TAGS, requestDetails);
StopWatch w = new StopWatch();
TagList retVal = super.getTags(null, null);
ourLog.info("Processed getAllTags in {}ms", w.getMillisAndRestart());

View File

@ -1,5 +1,9 @@
package ca.uhn.fhir.jpa.dao;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/*
* #%L
* HAPI FHIR JPA Server
@ -21,23 +25,16 @@ package ca.uhn.fhir.jpa.dao;
*/
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class DaoConfig {
private int myHardSearchLimit = 1000;
private int myHardTagListLimit = 1000;
private ResourceEncodingEnum myResourceEncoding=ResourceEncodingEnum.JSONC;
private int myIncludeLimit = 2000;
/**
* This is the maximum number of resources that will be added to a single page of
* returned resources. Because of includes with wildcards and other possibilities it is possible for a client to make
* requests that include very large amounts of data, so this hard limit can be imposed to prevent runaway
* requests.
*/
public void setIncludeLimit(int theIncludeLimit) {
myIncludeLimit = theIncludeLimit;
}
private List<IServerInterceptor> myInterceptors;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
/**
* See {@link #setIncludeLimit(int)}
@ -50,6 +47,23 @@ public class DaoConfig {
return myHardTagListLimit;
}
public int getIncludeLimit() {
return myIncludeLimit;
}
/**
* Returns the interceptors which will be notified of operations.
*
* @see #setInterceptors(List)
*/
public List<IServerInterceptor> getInterceptors() {
return myInterceptors;
}
public ResourceEncodingEnum getResourceEncoding() {
return myResourceEncoding;
}
public void setHardSearchLimit(int theHardSearchLimit) {
myHardSearchLimit = theHardSearchLimit;
}
@ -58,16 +72,47 @@ public class DaoConfig {
myHardTagListLimit = theHardTagListLimit;
}
public ResourceEncodingEnum getResourceEncoding() {
return myResourceEncoding;
/**
* This is the maximum number of resources that will be added to a single page of returned resources. Because of
* includes with wildcards and other possibilities it is possible for a client to make requests that include very
* large amounts of data, so this hard limit can be imposed to prevent runaway requests.
*/
public void setIncludeLimit(int theIncludeLimit) {
myIncludeLimit = theIncludeLimit;
}
/**
* This may be used to optionally register server interceptors directly against the DAOs.
* <p>
* Registering server action interceptors against the JPA DAOs can be more powerful than registering them against the
* {@link RestfulServer}, since the DAOs are able to break transactions into individual actions, and will account for
* match URLs (e.g. if a request contains an If-None-Match URL, the ID will be adjusted to account for the matching
* ID).
* </p>
*/
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
myInterceptors = theInterceptors;
}
public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
myResourceEncoding = theResourceEncoding;
}
public int getIncludeLimit() {
return myIncludeLimit;
/**
* This may be used to optionally register server interceptors directly against the DAOs.
* <p>
* Registering server action interceptors against the JPA DAOs can be more powerful than registering them against the
* {@link RestfulServer}, since the DAOs are able to break transactions into individual actions, and will account for
* match URLs (e.g. if a request contains an If-None-Match URL, the ID will be adjusted to account for the matching
* ID).
* </p>
*/
public void setInterceptors(IServerInterceptor... theInterceptor) {
if (theInterceptor == null || theInterceptor.length==0){
setInterceptors(new ArrayList<IServerInterceptor>());
} else {
setInterceptors(Arrays.asList(theInterceptor));
}
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.dao;
import java.util.ArrayList;
/*
* #%L
* HAPI FHIR JPA Server
@ -22,7 +24,6 @@ package ca.uhn.fhir.jpa.dao;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -41,8 +42,10 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.IParserErrorHandler;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
@ -70,12 +73,15 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage) {
OperationOutcome oo = new OperationOutcome();
oo.getIssueFirstRep().getSeverityElement().setValue(theSeverity);
oo.getIssueFirstRep().getDetailsElement().setValue(theMessage);
oo.getIssueFirstRep().getDiagnosticsElement().setValue(theMessage);
return oo;
}
@Override
public MethodOutcome validate(T theResource, IdDt theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, null);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
final OperationOutcome oo = new OperationOutcome();
IParser parser = theEncoding.newParser(getContext());
@ -83,17 +89,17 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
@Override
public void unknownAttribute(IParseLocation theLocation, String theAttributeName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Unknown attribute found: " + theAttributeName);
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDiagnostics("Unknown attribute found: " + theAttributeName);
}
@Override
public void unknownElement(IParseLocation theLocation, String theElementName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Unknown element found: " + theElementName);
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDiagnostics("Unknown element found: " + theElementName);
}
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Multiple repetitions of non-repeatable element found: " + theElementName);
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDiagnostics("Multiple repetitions of non-repeatable element found: " + theElementName);
}
});
@ -108,11 +114,12 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
// This method returns a MethodOutcome object
MethodOutcome retVal = new MethodOutcome();
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Validation succeeded");
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Validation succeeded");
retVal.setOperationOutcome(oo);
return retVal;
}
}

View File

@ -19,9 +19,7 @@ package ca.uhn.fhir.jpa.dao;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.util.Date;
import java.util.HashMap;
@ -58,12 +56,14 @@ import ca.uhn.fhir.model.dstu2.valueset.IssueTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
@ -137,7 +137,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
Entry nextEntry = resp.addEntry();
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setDetails(caughtEx.getMessage());
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setDiagnostics(caughtEx.getMessage());
nextEntry.setResource(oo);
EntryResponse nextEntryResp = nextEntry.getResponse();
@ -148,13 +148,16 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
long delay = System.currentTimeMillis() - start;
ourLog.info("Batch completed in {}ms", new Object[] { delay });
ooResp.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Batch completed in " + delay + "ms");
ooResp.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Batch completed in " + delay + "ms");
return resp;
}
@Override
public MetaDt metaGetOperation() {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
@ -251,7 +254,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
OperationOutcome statusOperationOutcome = new OperationOutcome();
if (transactionType == null) {
String message = "Transactiion Bundle did not specify valid Bundle.type, assuming " + BundleTypeEnum.TRANSACTION.getCode();
statusOperationOutcome.addIssue().setCode(IssueTypeEnum.INVALID_CONTENT).setSeverity(IssueSeverityEnum.WARNING).setDetails(message);
statusOperationOutcome.addIssue().setCode(IssueTypeEnum.INVALID_CONTENT).setSeverity(IssueSeverityEnum.WARNING).setDiagnostics(message);
ourLog.warn(message);
transactionType = BundleTypeEnum.TRANSACTION;
}
@ -405,7 +408,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
int configuredMax = 100; // this should probably be configurable or something
if (bundle.size() > configuredMax) {
statusOperationOutcome.addIssue().setSeverity(IssueSeverityEnum.WARNING)
.setDetails("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions");
.setDiagnostics("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions");
}
List<IBaseResource> resourcesToAdd = bundle.getResources(0, Math.min(bundle.size(), configuredMax));
for (IBaseResource next : resourcesToAdd) {
@ -472,7 +475,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
long delay = System.currentTimeMillis() - start;
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
statusOperationOutcome.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails(theActionName + " completed in " + delay + "ms");
statusOperationOutcome.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics(theActionName + " completed in " + delay + "ms");
for (IdDt next : allIds) {
IdDt replacement = idSubstitutions.get(next);
@ -482,7 +485,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
if (replacement.equals(next)) {
continue;
}
statusOperationOutcome.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Placeholder resource ID \"" + next + "\" was replaced with permanent ID \"" + replacement + "\"");
statusOperationOutcome.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Placeholder resource ID \"" + next + "\" was replaced with permanent ID \"" + replacement + "\"");
}
notifyWriteCompleted();

View File

@ -65,7 +65,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implements ISearchParamExtractor {
public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implements ISearchParamExtractor {
public SearchParamExtractorDstu1(FhirContext theContext) {
super(theContext);

View File

@ -70,7 +70,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISearchParamExtractor {
public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISearchParamExtractor {
public SearchParamExtractorDstu2(FhirContext theContext) {
super(theContext);

View File

@ -52,6 +52,9 @@ import ca.uhn.fhir.validation.ValidationResult;
public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResourceProvider<T> {
public static final String OPERATION_NAME_META = "$meta";
public static final String OPERATION_NAME_META_DELETE = "$meta-delete";
public static final String OPERATION_NAME_META_ADD = "$meta-add";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaResourceProviderDstu2.class);
public JpaResourceProviderDstu2() {
@ -91,7 +94,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
//@formatter:off
@Operation(name="$meta", idempotent=true, returnParameters= {
@Operation(name=OPERATION_NAME_META, idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
})
//@formatter:on
@ -103,7 +106,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
//@formatter:off
@Operation(name="$meta", idempotent=true, returnParameters= {
@Operation(name=OPERATION_NAME_META, idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
})
//@formatter:on
@ -115,7 +118,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
//@formatter:off
@Operation(name="$meta-add", idempotent=true, returnParameters= {
@Operation(name=OPERATION_NAME_META_ADD, idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
})
//@formatter:on
@ -127,7 +130,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
//@formatter:off
@Operation(name="$meta-delete", idempotent=true, returnParameters= {
@Operation(name=OPERATION_NAME_META_DELETE, idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
})
//@formatter:on

View File

@ -1,7 +1,10 @@
package ca.uhn.fhir.jpa.dao;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.math.BigDecimal;
import java.util.ArrayList;
@ -14,12 +17,15 @@ import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains;
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.springframework.context.support.ClassPathXmlApplicationContext;
import ca.uhn.fhir.context.FhirContext;
@ -64,6 +70,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.param.CompositeParam;
@ -82,6 +89,8 @@ 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.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
@SuppressWarnings("unchecked")
public class FhirResourceDaoDstu2Test extends BaseJpaTest {
@ -101,22 +110,22 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
private static IFhirResourceDao<Questionnaire> ourQuestionnaireDao;
private static IFhirSystemDao<Bundle> ourSystemDao;
private static IFhirResourceDao<Practitioner> ourPractitionerDao;
private static IServerInterceptor ourInterceptor;
private List<String> extractNames(IBundleProvider theSearch) {
ArrayList<String> retVal = new ArrayList<String>();
for (IBaseResource next : theSearch.getResources(0, theSearch.size())) {
Patient nextPt = (Patient)next;
Patient nextPt = (Patient) next;
retVal.add(nextPt.getNameFirstRep().getNameAsSingleString());
}
return retVal;
}
@Test
public void testCreateOperationOutcome() {
/*
* If any of this ever fails, it means that one of the OperationOutcome
* issue severity codes has changed code value across versions. We store
* the string as a constant, so something will need to be fixed.
* If any of this ever fails, it means that one of the OperationOutcome issue severity codes has changed code
* value across versions. We store the string as a constant, so something will need to be fixed.
*/
assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR);
assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.ERROR.getCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR);
@ -127,7 +136,46 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
assertEquals(org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN);
assertEquals(ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum.WARNING.getCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN);
assertEquals(ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum.WARNING.getCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN);
}
}
@Test
public void testRead() {
Observation o1 = new Observation();
o1.getCode().addCoding().setSystem("foo").setCode("testRead");
IIdType id1 = ourObservationDao.create(o1).getId();
/*
* READ
*/
reset(ourInterceptor);
Observation obs = ourObservationDao.read(id1.toUnqualifiedVersionless());
assertEquals(o1.getCode().getCoding().get(0).getCode(), obs.getCode().getCoding().get(0).getCode());
// Verify interceptor
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.READ), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals(id1.toUnqualifiedVersionless().getValue(), details.getId().toUnqualifiedVersionless().getValue());
assertEquals("Observation", details.getResourceType());
/*
* VREAD
*/
assertTrue(id1.hasVersionIdPart()); // just to make sure..
reset(ourInterceptor);
obs = ourObservationDao.read(id1);
assertEquals(o1.getCode().getCoding().get(0).getCode(), obs.getCode().getCoding().get(0).getCode());
// Verify interceptor
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.VREAD), detailsCapt.capture());
details = detailsCapt.getValue();
assertEquals(id1.toUnqualified().getValue(), details.getId().toUnqualified().getValue());
assertEquals("Observation", details.getResourceType());
}
@Test
public void testChoiceParamConcept() {
@ -271,6 +319,15 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
IIdType id = ourPatientDao.create(p).getId();
ourLog.info("Created patient, got it: {}", id);
// Verify interceptor
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertNotNull(details.getId());
assertEquals("Patient", details.getResourceType());
reset(ourInterceptor);
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
p.addName().addFamily("Hello");
@ -278,6 +335,8 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
assertEquals(id.getIdPart(), results.getId().getIdPart());
assertFalse(results.getCreated().booleanValue());
verifyNoMoreInteractions(ourInterceptor);
// Now create a second one
p = new Patient();
@ -522,7 +581,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().addFamily("Tester_testDeleteThenUndelete").addGiven("Joe");
IIdType id = ourPatientDao.create(patient).getId();
assertThat(id.getValue(), endsWith("/_history/1"));
assertThat(id.getValue(), Matchers.endsWith("/_history/1"));
// should be ok
ourPatientDao.read(id.toUnqualifiedVersionless());
@ -1476,16 +1535,16 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
patient.addTelecom().setSystem(ContactPointSystemEnum.EMAIL).setValue("abc");
id2 = ourPractitionerDao.create(patient).getId().toUnqualifiedVersionless();
}
Map<String, IQueryParameterType> params;
List<IIdType> patients;
params = new HashMap<String, IQueryParameterType>();
params.put(Practitioner.SP_FAMILY, new StringDt(methodName));
patients = toUnqualifiedVersionlessIds(ourPractitionerDao.search(params));
assertEquals(2, patients.size());
assertThat(patients, containsInAnyOrder(id1, id2));
params = new HashMap<String, IQueryParameterType>();
params.put(Practitioner.SP_FAMILY, new StringParam(methodName));
params.put(Practitioner.SP_EMAIL, new TokenParam(null, "abc"));
@ -1507,8 +1566,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
assertThat(patients, containsInAnyOrder(id1));
}
@Test
public void testSearchNameParam() {
IIdType id1;
@ -1765,7 +1823,8 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
}
/*
* TODO: it's kind of weird that we throw a 404 for textual IDs that don't exist, but just return an empty list for numeric IDs that don't exist
* TODO: it's kind of weird that we throw a 404 for textual IDs that don't exist, but just return an empty list
* for numeric IDs that don't exist
*/
result = toList(ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam("999999999999999")));
@ -1773,7 +1832,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
}
@Test
public void testSearchStringParam() {
{
@ -1800,7 +1858,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
}
@Test
public void testSearchStringParamReallyLong() {
String methodName = "testSearchStringParamReallyLong";
@ -1863,8 +1920,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001");
patient.addName().addFamily("Tester").addGiven("testSearchTokenParam1");
patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem")
.setDisplay("testSearchTokenParamDisplay");
patient.addCommunication().getLanguage().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem").setDisplay("testSearchTokenParamDisplay");
ourPatientDao.create(patient);
patient = new Patient();
@ -1957,7 +2013,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
@Test
public void testSearchWithTagParameter() {
String methodName = "testSearchWithTagParameter";
IIdType tag1id;
{
Organization org = new Organization();
@ -2528,7 +2584,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
assertThat(actual.subList(0, 2), containsInAnyOrder(id2, id4));
assertThat(actual.subList(2, 4), containsInAnyOrder(id1, id3));
}
@Test
public void testSortByString01() {
Patient p = new Patient();
@ -2599,7 +2655,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
p.addIdentifier().setSystem("urn:system").setValue(string);
p.addName().addFamily("Fam2").addGiven("Giv2");
ourPatientDao.create(p).getId().toUnqualifiedVersionless();
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(string);
p.addName().addFamily("Fam1").addGiven("Giv2");
@ -2607,7 +2663,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
SearchParameterMap pm;
List<String> names;
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", string));
pm.setSort(new SortSpec(Patient.SP_FAMILY));
@ -2639,7 +2695,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
ourLog.info("Names: {}", names);
assertThat(names.subList(0, 2), contains("Giv2 Fam2", "Giv1 Fam2"));
assertThat(names.subList(2, 4), contains("Giv2 Fam1", "Giv1 Fam1"));
}
}
@Test
public void testStoreUnversionedResources() {
@ -3011,7 +3067,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
ourOrganizationDao.update(p2);
fail();
} catch (UnprocessableEntityException e) {
// good
ourLog.error("Good", e);
}
}
@ -3063,8 +3119,19 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
ourQuestionnaireDao = ourCtx.getBean("myQuestionnaireDaoDstu2", IFhirResourceDao.class);
ourQuestionnaireResponseDao = ourCtx.getBean("myQuestionnaireResponseDaoDstu2", IFhirResourceDao.class);
ourFhirCtx = ourCtx.getBean(FhirContext.class);
ourInterceptor = mock(IServerInterceptor.class);
DaoConfig daoConfig = ourCtx.getBean(DaoConfig.class);
daoConfig.setInterceptors(ourInterceptor);
}
@Before
public void before() {
reset(ourInterceptor);
}
private static void deleteEverything() {
FhirSystemDaoDstu2Test.doDeleteEverything(ourSystemDao);
}

View File

@ -16,19 +16,14 @@
<bean id="myDaoConfig" class="ca.uhn.fhir.jpa.dao.DaoConfig">
</bean>
<bean id="myPersistenceDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" lazy-init="true">
<bean id="myPersistenceDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver"></property>
<property name="url" value="jdbc:derby:memory:myUnitTestDB;create=true" />
<property name="username" value=""/>
<property name="password" value=""/>
</bean>
<bean id="myPersistenceDataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" lazy-init="true">
<!-- <property name="url" value="jdbc:hsqldb:mem:unit-testing-jpa"/> -->
<!-- <property name="url" value="jdbc:hsqldb:file:svcret.hsqldb" /> -->
<property name="url" value="jdbc:derby:memory:myUnitTestDB;create=true" />
<!--
<property name="username" value="sa" />
<property name="password" value="" />
-->
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myPersistenceDataSource" />
@ -47,4 +42,4 @@
</bean>
<tx:annotation-driven transaction-manager="myTxManager" />
</beans>
</beans>

View File

@ -18,7 +18,6 @@
<groupId>ca.uhn.hapi.example</groupId>
<artifactId>hapi-fhir-jpaserver-example</artifactId>
<version>1.2-SNAPSHOT</version>
<packaging>war</packaging>
<name>HAPI FHIR JPA Server - Example</name>
@ -82,14 +81,13 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
<version>${logback_version}</version>
</dependency>
<!-- Needed for JEE/Servlet support -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
@ -100,7 +98,7 @@
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>2.1.4.RELEASE</version>
<version>${thymeleaf-version}</version>
</dependency>
<!-- Used for CORS support -->
@ -134,7 +132,6 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.0.1</version>
</dependency>
<!--
@ -146,19 +143,25 @@
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.11.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbynet</artifactId>
<version>10.11.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.11.1.1</version>
</dependency>
<!--
Arquillian is just used for automated tests, you don't neccesarily need it
to use this example.
-->
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -24,7 +24,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_api_version}</version>
<scope>provided</scope>
</dependency>

View File

@ -138,7 +138,7 @@ public class AuditingInterceptor extends InterceptorAdapter {
participants.add(participant);
auditEvent.setParticipant(participants);
SecurityEventObjectLifecycleEnum lifecycle = mapResourceTypeToSecurityLifecycle(theRequestDetails.getResourceOperationType());
SecurityEventObjectLifecycleEnum lifecycle = mapResourceTypeToSecurityLifecycle(theRequestDetails.getRestOperationType());
byte[] query = getQueryFromRequestDetails(theRequestDetails);
List<ObjectElement> auditableObjects = new ArrayList<SecurityEvent.ObjectElement>();
for (BundleEntry entry : theResponseObject.getEntries()) {
@ -193,7 +193,7 @@ public class AuditingInterceptor extends InterceptorAdapter {
auditEvent.setParticipant(participants);
byte[] query = getQueryFromRequestDetails(theRequestDetails);
SecurityEventObjectLifecycleEnum lifecycle = mapResourceTypeToSecurityLifecycle(theRequestDetails.getResourceOperationType());
SecurityEventObjectLifecycleEnum lifecycle = mapResourceTypeToSecurityLifecycle(theRequestDetails.getRestOperationType());
ObjectElement auditableObject = getObjectElement((IResource) theResponseObject, lifecycle, query);
if (auditableObject == null) {
log.debug("No auditable resources to audit");
@ -235,7 +235,7 @@ public class AuditingInterceptor extends InterceptorAdapter {
*/
protected Event getEventInfo(RequestDetails theRequestDetails) {
Event event = new Event();
event.setAction(mapResourceTypeToSecurityEventAction(theRequestDetails.getResourceOperationType()));
event.setAction(mapResourceTypeToSecurityEventAction(theRequestDetails.getRestOperationType()));
event.setDateTimeWithMillisPrecision(new Date());
event.setOutcome(SecurityEventOutcomeEnum.SUCCESS); // we audit successful return of PHI only, otherwise an
// exception is thrown and no resources are returned to be

View File

@ -145,8 +145,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
// Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
// Conformance.RestResourceSearchParam>();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
if (nextMethodBinding.getResourceOperationType() != null) {
RestfulOperationTypeEnum resOp = RestfulOperationTypeEnum.VALUESET_BINDER.fromCodeString(nextMethodBinding.getResourceOperationType().getCode());
if (nextMethodBinding.getRestOperationType() != null) {
RestfulOperationTypeEnum resOp = RestfulOperationTypeEnum.VALUESET_BINDER.fromCodeString(nextMethodBinding.getRestOperationType().getCode());
if (resOp != null) {
if (resourceOps.contains(resOp) == false) {
resourceOps.add(resOp);
@ -154,7 +154,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
}
}
RestfulOperationSystemEnum sysOp = RestfulOperationSystemEnum.VALUESET_BINDER.fromCodeString(nextMethodBinding.getResourceOperationType().getCode());
RestfulOperationSystemEnum sysOp = RestfulOperationSystemEnum.VALUESET_BINDER.fromCodeString(nextMethodBinding.getRestOperationType().getCode());
if (sysOp != null) {
if (systemOps.contains(sysOp) == false) {
systemOps.add(sysOp);

View File

@ -39,8 +39,10 @@ import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.PortUtil;
/**
@ -74,6 +76,8 @@ public class InterceptorTest {
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(myInterceptor1, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class));
order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.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));

View File

@ -24,7 +24,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_api_version}</version>
<scope>provided</scope>
</dependency>
@ -116,7 +115,6 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -101,8 +101,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
}
private void checkBindingForSystemOps(Rest rest, Set<SystemRestfulInteractionEnum> systemOps, BaseMethodBinding<?> nextMethodBinding) {
if (nextMethodBinding.getResourceOperationType() != null) {
String sysOpCode = nextMethodBinding.getResourceOperationType().getCode();
if (nextMethodBinding.getRestOperationType() != null) {
String sysOpCode = nextMethodBinding.getRestOperationType().getCode();
if (sysOpCode != null) {
SystemRestfulInteractionEnum sysOp = SystemRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(sysOpCode);
if (sysOp == null) {
@ -193,8 +193,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
// Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
// Conformance.RestResourceSearchParam>();
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
if (nextMethodBinding.getResourceOperationType() != null) {
String resOpCode = nextMethodBinding.getResourceOperationType().getCode();
if (nextMethodBinding.getRestOperationType() != null) {
String resOpCode = nextMethodBinding.getRestOperationType().getCode();
if (resOpCode != null) {
TypeRestfulInteractionEnum resOp = TypeRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(resOpCode);
if (resOp != null) {

View File

@ -10,8 +10,7 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle.Link;
public class BundleTest {
@Test
public void testGetLink() {
public void testGetLink() {
Bundle b = new Bundle();
Link link = b.getLink(Bundle.LINK_NEXT);

View File

@ -0,0 +1,250 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.BundleInclusionRule;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.PortUtil;
public class ServerActionInterceptorTest {
private static CloseableHttpClient ourClient;
private static final FhirContext ourCtx = FhirContext.forDstu2();
private static int ourPort;
private static Server ourServer;
private static IServerInterceptor ourInterceptor;
private static IGenericClient ourFhirClient;
@Test
public void testRead() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123");
CloseableHttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.READ), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Patient/123", details.getId().getValue());
}
@Test
public void testVRead() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history/456");
CloseableHttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.VREAD), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Patient/123/_history/456", details.getId().getValue());
}
@Test
public void testCreate() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("FAMILY");
ourFhirClient.create().resource(patient).execute();
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.CREATE), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Patient", details.getResourceType());
}
@Test
public void testUpdate() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("FAMILY");
patient.setId("Patient/123");
ourFhirClient.update().resource(patient).execute();
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.UPDATE), detailsCapt.capture());
ActionRequestDetails details = detailsCapt.getValue();
assertEquals("Patient", details.getResourceType());
assertEquals("Patient/123", details.getId().getValue());
}
@Test
public void testHistorySystem() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
CloseableHttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.HISTORY_SYSTEM), detailsCapt.capture());
}
@Test
public void testHistoryType() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
CloseableHttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.HISTORY_TYPE), detailsCapt.capture());
assertEquals("Patient", detailsCapt.getValue().getResourceType());
}
@Test
public void testHistoryInstance() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history");
CloseableHttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
ArgumentCaptor<ActionRequestDetails> detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
verify(ourInterceptor).incomingRequestPreHandled(eq(RestOperationTypeEnum.HISTORY_INSTANCE), detailsCapt.capture());
assertEquals("Patient", detailsCapt.getValue().getResourceType());
assertEquals("Patient/123", detailsCapt.getValue().getId().getValue());
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.registerInterceptor(new ResponseHighlighterInterceptor());
servlet.setResourceProviders(new DummyPatientResourceProvider());
servlet.setPlainProviders(new PlainProvider());
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
ourInterceptor = mock(InterceptorAdapter.class);
servlet.registerInterceptor(ourInterceptor);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
}
@Before
public void before() {
reset(ourInterceptor);
when(ourInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
when(ourInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
}
public static class PlainProvider {
@History()
public List<IBaseResource> history() {
Patient retVal = new Patient();
retVal.setId("Patient/123/_history/2");
return Collections.singletonList((IBaseResource) retVal);
}
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Read(version = true)
public Patient read(@IdParam IdDt theId) {
Patient retVal = new Patient();
retVal.setId(theId);
return retVal;
}
@History()
public List<Patient> history() {
Patient retVal = new Patient();
retVal.setId("Patient/123/_history/2");
return Collections.singletonList(retVal);
}
@History()
public List<Patient> history(@IdParam IdDt theId) {
Patient retVal = new Patient();
retVal.setId("Patient/123/_history/2");
return Collections.singletonList(retVal);
}
@Create()
public MethodOutcome create(@ResourceParam Patient thePatient) {
Patient retVal = new Patient();
retVal.setId("Patient/123/_history/2");
return new MethodOutcome(retVal.getId());
}
@Update()
public MethodOutcome update(@IdParam IdDt theId, @ResourceParam Patient thePatient) {
Patient retVal = new Patient();
retVal.setId("Patient/123/_history/2");
return new MethodOutcome(retVal.getId());
}
}
}

View File

@ -24,7 +24,6 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_api_version}</version>
<scope>provided</scope>
</dependency>

View File

@ -102,8 +102,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
private void checkBindingForSystemOps(ConformanceRestComponent rest, Set<SystemRestfulInteraction> systemOps,
BaseMethodBinding<?> nextMethodBinding) {
if (nextMethodBinding.getResourceOperationType() != null) {
String sysOpCode = nextMethodBinding.getResourceOperationType().getCode();
if (nextMethodBinding.getRestOperationType() != null) {
String sysOpCode = nextMethodBinding.getRestOperationType().getCode();
if (sysOpCode != null) {
SystemRestfulInteraction sysOp;
try {
@ -200,8 +200,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
// Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
// Conformance.RestResourceSearchParam>();
for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
if (nextMethodBinding.getResourceOperationType() != null) {
String resOpCode = nextMethodBinding.getResourceOperationType().getCode();
if (nextMethodBinding.getRestOperationType() != null) {
String resOpCode = nextMethodBinding.getRestOperationType().getCode();
if (resOpCode != null) {
TypeRestfulInteraction resOp;
try {

44
pom.xml
View File

@ -171,7 +171,7 @@
<siteMainDirectory>${user.home}/sites/hapi-fhir</siteMainDirectory>
<scmPubCheckoutDirectory>${user.home}/sites/scm/hapi-fhir</scmPubCheckoutDirectory>
<!-- Plugin Versions -->
<!-- Dependency Versions -->
<apache_httpclient_version>4.4</apache_httpclient_version>
<apache_httpcore_version>4.4</apache_httpcore_version>
<commons_io_version>2.4</commons_io_version>
@ -209,7 +209,6 @@
<phloc_schematron_version>2.7.1</phloc_schematron_version>
<phloc_commons_version>4.3.6</phloc_commons_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<servlet_api_version>3.1.0</servlet_api_version>
<slf4j_version>1.7.10</slf4j_version>
<spring_version>4.1.5.RELEASE</spring_version>
<spring_security_version>3.2.4.RELEASE</spring_security_version>
@ -220,6 +219,47 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencyManagement>
<dependencies>
<!-- Set dependency versions -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby_version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbynet</artifactId>
<version>${derby_version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>${derby_version}</version>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<version>1.1.8.Final</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>

View File

@ -1 +1 @@
<mxGraphModel dx="940" dy="431" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" style="default-style2" math="0"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="2" value="RESTful&#xa;Server" style="shape=umlLifeline;perimeter=lifelinePerimeter;size=46;strokeColor=#0000FF;strokeWidth=2;fillColor=#99FFFF" parent="1" vertex="1"><mxGeometry x="150" y="20" width="100" height="280" as="geometry"/></mxCell><mxCell id="3" value="Interceptor" style="shape=umlLifeline;perimeter=lifelinePerimeter;size=46;strokeColor=#0000FF;strokeWidth=2;fillColor=#99FFFF" parent="1" vertex="1"><mxGeometry x="280" y="20" width="100" height="280" as="geometry"/></mxCell><mxCell id="4" value="Resource/Plain&#xa;Provider&#xa;Method" style="shape=umlLifeline;perimeter=lifelinePerimeter;size=46;strokeColor=#0000FF;strokeWidth=2;fillColor=#99FFFF" parent="1" vertex="1"><mxGeometry x="400" y="20" width="100" height="280" as="geometry"/></mxCell><mxCell id="7" value="" style="ellipse;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="1" vertex="1"><mxGeometry x="70" y="80" width="30" height="30" as="geometry"/></mxCell><mxCell id="8" value="Incoming Request" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;entryX=0.5;entryY=0.3;entryPerimeter=0" parent="1" source="7" edge="1"><mxGeometry x="70" y="80" as="geometry"><mxPoint x="200" y="95" as="targetPoint"/></mxGeometry></mxCell><mxCell id="21" value="Request is handled" style="rounded=1;whiteSpace=wrap" parent="1" vertex="1"><mxGeometry x="390" y="150" width="120" height="30" as="geometry"/></mxCell><mxCell id="22" value="" style="edgeStyle=none;align=left;entryX=0.5;entryY=0.632;entryPerimeter=0;exitX=0.5;exitY=0.632;exitPerimeter=0" parent="1" edge="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="200" y="140.15999999999997" as="sourcePoint"/><mxPoint x="450" y="140.15999999999997" as="targetPoint"/></mxGeometry></mxCell><mxCell id="24" value="" parent="1" vertex="1"><mxGeometry x="320" y="233" width="20" height="27" as="geometry"/></mxCell><mxCell id="25" value="handleException" style="edgeStyle=elbowEdgeStyle;elbow=vertical;verticalAlign=bottom;endArrow=block;exitX=0.5;exitY=0.478;exitPerimeter=0;align=left" parent="1" target="24" edge="1"><mxGeometry x="221.75" y="233.25" as="geometry"><mxPoint x="200" y="233.39999999999998" as="sourcePoint"/></mxGeometry></mxCell><mxCell id="26" value="return true;" style="edgeStyle=elbowEdgeStyle;elbow=vertical;verticalAlign=bottom;dashed=1;endArrow=open;endSize=8;" parent="1" source="24" edge="1"><mxGeometry x="221.75" y="233.25" as="geometry"><mxPoint x="200" y="260" as="targetPoint"/></mxGeometry></mxCell><mxCell id="27" value="" style="ellipse;shape=endState;fillColor=#000000;strokeColor=#ff0000;align=left" parent="1" vertex="1"><mxGeometry x="70" y="270" width="30" height="30" as="geometry"/></mxCell><mxCell id="28" value="Response" style="edgeStyle=orthogonalEdgeStyle;endArrow=block;dashed=0;dashPattern=1 4;align=left;entryX=1;entryY=0.5;exitX=0.5;exitY=0.954;exitPerimeter=0;endFill=1;strokeWidth=1;strokeColor=#FF0000" parent="1" target="27" edge="1"><mxGeometry x="0.500299820107935" y="-13" width="100" height="100" relative="1" as="geometry"><mxPoint x="200" y="285.05999999999995" as="sourcePoint"/><mxPoint x="650" y="213" as="targetPoint"/><mxPoint x="-8" y="-12" as="offset"/></mxGeometry></mxCell><mxCell id="29" value="" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1"><mxGeometry x="175" y="100" width="50" height="30" as="geometry"/></mxCell><mxCell id="30" value="throw exception" style="edgeStyle=elbowEdgeStyle;elbow=vertical;verticalAlign=bottom;dashed=1;endArrow=open;endSize=8;" edge="1" parent="1"><mxGeometry x="351.75" y="173.25" as="geometry"><mxPoint x="200" y="200" as="targetPoint"/><mxPoint x="450" y="200" as="sourcePoint"/></mxGeometry></mxCell></root></mxGraphModel>
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:40.0) Gecko/20100101 Firefox/40.0" type="device"><diagram>3VlNb9s4EP01PsaQTH/pWCd2d4EWCOoFdvfIWJRFlBa1FJU4/fUdWkN9UbEVR2m79cEwh+SIfPPmcUSPyO3h+FHRNP4sQyZGEy88jsjdaDKZz2fwbQzPhWE69QvDXvGwMNUMW/6NodFDa85DljUGaimF5mnTuJNJwna6YYukaD4ipXvrvjJsd1S41r95qOPCupzMK/sfjO9j+xh/HhQ9mX62PkIW0Vzom5MJ+kz3gVpfp12RNQCmpAQ35tfheMuEAc0CUmx980JvuUjFElzI+Qm4iEcqclzjl/X2ryiHMbAxcqQjstoy9ciUs5kspqn5mR/EJx4xwRNorVKm+IFpGE/uBJrvK9sqK6I4nZvfWsmv7FYKaUbD4zz4bGCp2GNxBlRWEReiNjIINvBxd4sAwHo1Q5qdTLj7j0zCQtQzDLG9MyQTchDx8p6qGPse2uJafMkCjRS5ti9dV3DDD0S8G33ioP9nAjDtWKpho78n4JPllYDPBwB86tKdZTIHxGHmvaA8qfH+XslHEBgIRGn6zHQsw980MlML+8+IzMKJjIMyTABlN8havDNNld5qqo2xBYsBEFbbAW0UYc8AmFkVQMgst2uQkS7EBgBs2aEdO3ngyR6sX9h/Ocu0C2EIZxo2mXiQT+vKsDoZoCOWin+Tiabw6JWBgsMp+EHwfQKdD1JreTCjk/CDUqcJMmXQZSx4Ri/Pwg5oq+d/wOiNZ7b576lJbLOWJneIVUeYMHPr/DEbfEPcugKCPu4lh8dXMtZKlgBrGesBmLlnGie1oloup1egS1LWRQvD6/HMUIsmoWCuLCmZJyHYyZ0PwD7FXLNtSk+IPUFBNgD9SdBEoTxN65phdWToDOgoXs4SPpEnWabIZMEiWMwZNs6JUdsOPoLxyHVtDrQaU6Ddj8EX6XpJe0ujYoJq/tgsVq+nsz/1xv4sqD6YX9ZjkXoOvR2/01Z5dcnvgGnjnvVDEN6S2Z6RAXqtBaqL71af3sR3lJjajorMXx9NxcglFC/X6L2V+J5q/yDk7uuZHJgujPy7OdDKvJdiUTCgGcO+qg794wWiVAVobIG7Ph2MF1zLhQy4hqn4ClmLq2I6VxBOTytjwkrlPQIb0iwuj4hLZ3qvk/iXCRrx8Q38HcTlqnLVgHl1sdorea4uXIndwA+oXDtKV3gLS2UCYJ3juVTw8rWXCRUNsrvKVHLa4GYa91SDChn0ABRveqYIKLKgLAGssnXpXDAzjrp0Dpa0gfiiu+YLXGWpBXmzOQW5lyS+stCFVXveJAiWE8/3FgFpJtqNj1cQP6vOIGQx9ur1QCvr+9YZ8/Y1jr1bvJD7jqMbJGcJEOaF9SOjKINYvFE+UJheJx87IXNw0FHIk1WsD5ZvQ1yKtfS4jH6NJhbwodXB+qhho2OT38D5969zhjkO+yYnmTlHn78gQ9Qrtv1a+rfrdcfRcGUPcd9rX+BpB8b9C3TLpx9QoMPLX3tHqWJwjbhjWfb/LdJbbG6eRzaK/SPUVe8RbwjSgxe4Da0+KOXvQF334r6s2JPcTLuyYu939fYqkaoYAAhXFKhOCie89WL+VwrvbKi7AmhW/7AVw6v/J8n6Ow==</diagram></mxfile>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -10,22 +10,28 @@
<!-- The body of the document contains a number of sections -->
<section name="Server Interceptors">
<img src="svg/restful-server-interceptors.svg" alt="Interceptors" align="right"/>
<p>
The RESTful server provides a powerful mechanism for adding cross-cutting behaviour
(e.g. requests, such as authnorization, auditing, fancy output, logging, etc.)
to each incoming request that it processes. This mechanism consists of defining one or
more <b>interceptors</b> that will be invoked at defined points in the processing of
each incoming request.
</p>
<img src="svg/restful-server-interceptors.svg" alt="Interceptors"/>
<p>
Interceptors will intercept the incoming request, and can take action such as
logging or auditing it, or examining/injecting headers. They can optionally choose
to handle the request themself and the cancel any subsequent processing. Interceptors
to handle the request directly and the cancel any subsequent processing (in other words,
the interceptor can choose to supply a response to the client, and can then signal
to the server that it does not need to do so).
</p>
<p>
Interceptors
may also be notified of responses prior to those responses being served to a client,
and may audit or even cancel response. The diagram on the right shows the
and may audit or even cancel the response. The diagram on the right shows the
lifecycle of a normal (non failing) request which is subject to an interceptor.
</p>
@ -34,7 +40,7 @@
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html">IServerInterceptor</a>
interface (or extend the convenience
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.html">InterceptorAdapter</a>
class provided). The RESTful server will normally invoke the interceptor at three
class provided). The RESTful server will normally invoke the interceptor at several
points in the execution of the client request.
</p>
@ -43,17 +49,62 @@
Before any processing at all is performed on the request,
<b>incomingRequestPreProcessed</b> will be invoked. This can be useful
if you wish to handle some requests completely outside of HAPI's processing
mechanism. If you are handling a request in your interceptor, you may
return <code>false</code> from your implementation method to signal to
HAPI that processing of the request should stop immediately.
mechanism.
<ul>
<li>
If this method returns <code>true</code>, processing continues to the
next interceptor, and ultimately to the next phase of processing.
</li>
<li>
If this method returns <code>false</code>, processing stops immediately.
This is useful if the interceptor wishes to supply its own response
by directly calling methods on the <code>HttpServletResponse</code>
</li>
<li>
If this method throws any subclass of
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>,
processing is stopped immedicately and the corresponding status is returned to the client.
This is useful if an interceptor wishes to abort the request (e.g. because
it did not detect valid credentials)
</li>
</ul>
</li>
<li>
Once the request is parsed (but before it is handled),
Once the request is classified (meaning that the URL and request headers are
examined to determine exactly what kind of request is being made),
<b>incomingRequestPostProcessed</b> will be invoked. This method has
an additional parameter, the
<a href="./apidocs/ca/uhn/fhir/rest/method/RequestDetails.html">RequestDetails</a>
object which contains details about what operation is about to be
called, and what request parameters were receievd with that request.
<ul>
<li>
If this method returns <code>true</code>, processing continues to the
next interceptor, and ultimately to the next phase of processing.
</li>
<li>
If this method returns <code>false</code>, processing stops immediately.
This is useful if the interceptor wishes to supply its own response
by directly calling methods on the <code>HttpServletResponse</code>
</li>
<li>
If this method throws any subclass of
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>,
processing is stopped immedicately and the corresponding status is returned to the client.
This is useful if an interceptor wishes to abort the request (e.g. because
it did not detect valid credentials)
</li>
</ul>
</li>
<li>
Once the request is being handled,
<b>incomingRequestPreHandled</b> will be invoked. This method is useful in that
it provides details about the FHIR operation being invoked (e.g. is this a "read" or a "create"? what
is the resource type and ID of the resource being accessed, etc.). This method can be
useful for adding finer grained access controls. Note that <code>incomingRequestPreHandled</code>
is not able to directly supply a response, but it may throw a
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>
to abort processing.
</li>
<li>
After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method),