Clean up rest hook interceptor a bit

This commit is contained in:
James 2017-06-30 07:24:33 -04:00
parent d81565b87c
commit b44bdeec88
9 changed files with 426 additions and 414 deletions

View File

@ -26,37 +26,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public interface IRestfulServerDefaults {
/**
* Returns the list of interceptors registered against this server
*/
List<IServerInterceptor> getInterceptors();
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
FhirContext getFhirContext();
/**
* Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
*
* @return Returns the default pretty print setting
*/
boolean isDefaultPrettyPrint();
/**
* @return Returns the server support for ETags (will not be <code>null</code>). Default is
* {@link RestfulServer#DEFAULT_ETAG_SUPPORT}
*/
ETagSupportEnum getETagSupport();
/**
* @return Returns the setting for automatically adding profile tags
* @deprecated As of HAPI FHIR 1.5, this property has been moved to
@ -73,15 +42,46 @@ public interface IRestfulServerDefaults {
EncodingEnum getDefaultResponseEncoding();
/**
* @return If <code>true</code> the server will use browser friendly content-types (instead of standard FHIR ones)
* when it detects that the request is coming from a browser
* instead of a FHIR
* @return Returns the server support for ETags (will not be <code>null</code>). Default is
* {@link RestfulServer#DEFAULT_ETAG_SUPPORT}
*/
boolean isUseBrowserFriendlyContentTypes();
ETagSupportEnum getETagSupport();
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
FhirContext getFhirContext();
/**
* Returns the list of interceptors registered against this server
*/
List<IServerInterceptor> getInterceptors();
/**
* Returns the paging provider for this server
*/
IPagingProvider getPagingProvider();
/**
* Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
*
* @return Returns the default pretty print setting
*/
boolean isDefaultPrettyPrint();
/**
* @return If <code>true</code> the server will use browser friendly content-types (instead of standard FHIR ones)
* when it detects that the request is coming from a browser
* instead of a FHIR
*/
boolean isUseBrowserFriendlyContentTypes();
}

View File

@ -48,6 +48,7 @@ import ca.uhn.fhir.rest.server.AddProfileTagEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IPagingProvider;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.IServerAddressStrategy;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -62,216 +63,246 @@ import ca.uhn.fhir.util.OperationOutcomeUtil;
*/
public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
private final FhirContext CTX;
private static final String ERROR = "error";
private static final String PROCESSING = "processing";
private static final String ERROR = "error";
private static final String PROCESSING = "processing";
/** the uri info */
@Context
private UriInfo theUriInfo;
/** the http headers */
@Context
private HttpHeaders theHeaders;
private final FhirContext CTX;
/** the http headers */
@Context
private HttpHeaders theHeaders;
@Override
public FhirContext getFhirContext() {
return CTX;
}
/** the uri info */
@Context
private UriInfo theUriInfo;
/**
* Default is DSTU2. Use {@link AbstractJaxRsProvider#AbstractJaxRsProvider(FhirContext)} to specify a DSTU3 context.
*/
protected AbstractJaxRsProvider() {
CTX = FhirContext.forDstu2();
}
/**
* Default is DSTU2. Use {@link AbstractJaxRsProvider#AbstractJaxRsProvider(FhirContext)} to specify a DSTU3 context.
*/
protected AbstractJaxRsProvider() {
CTX = FhirContext.forDstu2();
}
/**
*
* @param ctx the {@link FhirContext} to support.
*/
protected AbstractJaxRsProvider(final FhirContext ctx) {
CTX = ctx;
}
/**
*
* @param ctx
* the {@link FhirContext} to support.
*/
protected AbstractJaxRsProvider(final FhirContext ctx) {
CTX = ctx;
}
/**
* This method returns the query parameters
* @return the query parameters
*/
public Map<String, String[]> getParameters() {
final MultivaluedMap<String, String> queryParameters = getUriInfo().getQueryParameters();
final HashMap<String, String[]> params = new HashMap<String, String[]>();
for (final Entry<String, List<String>> paramEntry : queryParameters.entrySet()) {
params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()]));
}
return params;
}
private IBaseOperationOutcome createOutcome(final DataFormatException theException) {
final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext());
final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException);
OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING);
return oo;
}
/**
* This method returns the default server address strategy. The default strategy return the
* base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()}
* @return
*/
public IServerAddressStrategy getServerAddressStrategy() {
final HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy();
addressStrategy.setValue(getBaseForRequest());
return addressStrategy;
}
/**
* DEFAULT = AddProfileTagEnum.NEVER
*/
@Override
public AddProfileTagEnum getAddProfileTag() {
return AddProfileTagEnum.NEVER;
}
/**
* This method returns the server base, independent of the request or resource.
* @see javax.ws.rs.core.UriInfo#getBaseUri()
* @return the ascii string for the server base
*/
public String getBaseForServer() {
final String url = getUriInfo().getBaseUri().toASCIIString();
return StringUtils.isNotBlank(url) && url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
}
/**
* This method returns the server base, including the resource path.
* {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()}
*
* @return the ascii string for the base resource provider path
*/
public String getBaseForRequest() {
return getBaseForServer();
}
/**
* This method returns the server base, including the resource path.
* {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()}
* @return the ascii string for the base resource provider path
*/
public String getBaseForRequest() {
return getBaseForServer();
}
/**
* This method returns the server base, independent of the request or resource.
*
* @see javax.ws.rs.core.UriInfo#getBaseUri()
* @return the ascii string for the server base
*/
public String getBaseForServer() {
final String url = getUriInfo().getBaseUri().toASCIIString();
return StringUtils.isNotBlank(url) && url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
}
/**
* Default: an empty list of interceptors (Interceptors are not yet supported
* in the JAX-RS server). Please get in touch if you'd like to help!
*
* @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
*/
@Override
public List<IServerInterceptor> getInterceptors() {
return Collections.emptyList();
}
/**
* DEFAULT = EncodingEnum.JSON
*/
@Override
public EncodingEnum getDefaultResponseEncoding() {
return EncodingEnum.JSON;
}
/**
* Get the uriInfo
* @return the uri info
*/
public UriInfo getUriInfo() {
return this.theUriInfo;
}
/**
* DEFAULT = ETagSupportEnum.DISABLED
*/
@Override
public ETagSupportEnum getETagSupport() {
return ETagSupportEnum.DISABLED;
}
/**
* Set the Uri Info
* @param uriInfo the uri info
*/
public void setUriInfo(final UriInfo uriInfo) {
this.theUriInfo = uriInfo;
}
@Override
public FhirContext getFhirContext() {
return CTX;
}
/**
* Get the headers
* @return the headers
*/
public HttpHeaders getHeaders() {
return this.theHeaders;
}
/**
* Get the headers
*
* @return the headers
*/
public HttpHeaders getHeaders() {
return this.theHeaders;
}
/**
* Set the headers
* @param headers the headers to set
*/
public void setHeaders(final HttpHeaders headers) {
this.theHeaders = headers;
}
/**
* Default: an empty list of interceptors (Interceptors are not yet supported
* in the JAX-RS server). Please get in touch if you'd like to help!
*
* @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
*/
@Override
public List<IServerInterceptor> getInterceptors() {
return Collections.emptyList();
}
/**
* Return the requestbuilder for the server
* @param requestType the type of the request
* @param restOperation the rest operation type
* @param theResourceName the resource name
* @return the requestbuilder
*/
public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation, final String theResourceName) {
return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString(), theResourceName);
}
/**
* By default, no paging provider is used
*/
@Override
public IPagingProvider getPagingProvider() {
return null;
}
/**
* Return the requestbuilder for the server
* @param requestType the type of the request
* @param restOperation the rest operation type
* @return the requestbuilder
*/
public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) {
return getRequest(requestType, restOperation, null);
}
/**
* This method returns the query parameters
*
* @return the query parameters
*/
public Map<String, String[]> getParameters() {
final MultivaluedMap<String, String> queryParameters = getUriInfo().getQueryParameters();
final HashMap<String, String[]> params = new HashMap<String, String[]>();
for (final Entry<String, List<String>> paramEntry : queryParameters.entrySet()) {
params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()]));
}
return params;
}
/**
* DEFAULT = EncodingEnum.JSON
*/
@Override
public EncodingEnum getDefaultResponseEncoding() {
return EncodingEnum.JSON;
}
/**
* Return the requestbuilder for the server
*
* @param requestType
* the type of the request
* @param restOperation
* the rest operation type
* @return the requestbuilder
*/
public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) {
return getRequest(requestType, restOperation, null);
}
/**
* DEFAULT = true
*/
@Override
public boolean isDefaultPrettyPrint() {
return true;
}
/**
* Return the requestbuilder for the server
*
* @param requestType
* the type of the request
* @param restOperation
* the rest operation type
* @param theResourceName
* the resource name
* @return the requestbuilder
*/
public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation, final String theResourceName) {
return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString(), theResourceName);
}
/**
* DEFAULT = ETagSupportEnum.DISABLED
*/
@Override
public ETagSupportEnum getETagSupport() {
return ETagSupportEnum.DISABLED;
}
/**
* This method returns the default server address strategy. The default strategy return the
* base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()}
*
* @return
*/
public IServerAddressStrategy getServerAddressStrategy() {
final HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy();
addressStrategy.setValue(getBaseForRequest());
return addressStrategy;
}
/**
* DEFAULT = AddProfileTagEnum.NEVER
*/
@Override
public AddProfileTagEnum getAddProfileTag() {
return AddProfileTagEnum.NEVER;
}
/**
* Get the uriInfo
*
* @return the uri info
*/
public UriInfo getUriInfo() {
return this.theUriInfo;
}
/**
* DEFAULT = false
*/
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return true;
}
/**
* Convert an exception to a response
*
* @param theRequest
* the incoming request
* @param theException
* the exception to convert
* @return response
* @throws IOException
*/
public Response handleException(final JaxRsRequest theRequest, final Throwable theException)
throws IOException {
if (theException instanceof JaxRsResponseException) {
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, (JaxRsResponseException) theException);
} else if (theException instanceof DataFormatException) {
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, new JaxRsResponseException(
new InvalidRequestException(theException.getMessage(), createOutcome((DataFormatException) theException))));
} else {
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest,
new JaxRsExceptionInterceptor().convertException(this, theException));
}
}
/**
* DEFAULT = false
*/
public boolean withStackTrace() {
return false;
}
/**
* DEFAULT = true
*/
@Override
public boolean isDefaultPrettyPrint() {
return true;
}
/**
* Convert an exception to a response
* @param theRequest the incoming request
* @param theException the exception to convert
* @return response
* @throws IOException
*/
public Response handleException(final JaxRsRequest theRequest, final Throwable theException)
throws IOException {
if (theException instanceof JaxRsResponseException) {
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, (JaxRsResponseException) theException);
} else if (theException instanceof DataFormatException) {
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, new JaxRsResponseException(
new InvalidRequestException(theException.getMessage(), createOutcome((DataFormatException) theException))));
} else {
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest,
new JaxRsExceptionInterceptor().convertException(this, theException));
}
}
/**
* DEFAULT = false
*/
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return true;
}
private IBaseOperationOutcome createOutcome(final DataFormatException theException) {
final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext());
final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException);
OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING);
return oo;
}
/**
* Set the headers
*
* @param headers
* the headers to set
*/
public void setHeaders(final HttpHeaders headers) {
this.theHeaders = headers;
}
/**
* Set the Uri Info
*
* @param uriInfo
* the uri info
*/
public void setUriInfo(final UriInfo uriInfo) {
this.theUriInfo = uriInfo;
}
/**
* DEFAULT = false
*/
public boolean withStackTrace() {
return false;
}
}

View File

@ -564,7 +564,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
protected boolean isPagingProviderDatabaseBacked(RequestDetails theRequestDetails) {
if (theRequestDetails == null) {
if (theRequestDetails == null || theRequestDetails.getServer() == null) {
return false;
}
if (theRequestDetails.getServer().getPagingProvider() instanceof DatabaseBackedPagingProvider) {

View File

@ -0,0 +1,71 @@
package ca.uhn.fhir.jpa.interceptor;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
public abstract class BaseRestHookSubscriptionInterceptor extends ServerOperationInterceptorAdapter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRestHookSubscriptionInterceptor.class);
protected static final Integer MAX_SUBSCRIPTION_RESULTS = 10000;
protected abstract IFhirResourceDao<?> getSubscriptionDao();
protected void checkSubscriptionCriterias(String theCriteria) {
try {
IBundleProvider results = executeSubscriptionCriteria(theCriteria, null);
} catch (Exception e) {
ourLog.warn("Invalid criteria when creating subscription", e);
throw new InvalidRequestException("Invalid criteria: " + e.getMessage());
}
}
private IBundleProvider executeSubscriptionCriteria(String theCriteria, IIdType idType) {
String criteria = theCriteria;
/*
* Run the subscriptions query and look for matches, add the id as part of the criteria
* to avoid getting matches of previous resources rather than the recent resource
*/
if (idType != null) {
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
}
IBundleProvider results = getBundleProvider(criteria, true);
return results;
}
/**
* Search based on a query criteria
*
* @param theCheckOnly Is this just a test that the search works
*/
protected IBundleProvider getBundleProvider(String theCriteria, boolean theCheckOnly) {
RuntimeResourceDefinition responseResourceDef = getSubscriptionDao().validateCriteriaAndReturnResourceDefinition(theCriteria);
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(getSubscriptionDao(), getSubscriptionDao().getContext(), theCriteria, responseResourceDef);
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
IFhirResourceDao<? extends IBaseResource> responseDao = getSubscriptionDao().getDao(responseResourceDef.getImplementingClass());
if (theCheckOnly) {
responseCriteriaUrl.setLoadSynchronousUpTo(1);
} else {
responseCriteriaUrl.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS);
}
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
return responseResults;
}
}

View File

@ -29,12 +29,9 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -45,8 +42,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
@ -62,11 +57,9 @@ import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.interceptor.*;
public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterceptorAdapter {
public class RestHookSubscriptionDstu2Interceptor extends BaseRestHookSubscriptionInterceptor {
private static final Integer MAX_SUBSCRIPTION_RESULTS = 10000;
private static volatile ExecutorService executor;
private final static int MAX_THREADS = 1;
@ -75,13 +68,13 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
@Autowired
private FhirContext myFhirContext;
private boolean myNotifyOnDelete = false;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
@Autowired
@Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> mySubscriptionDao;
private boolean myNotifyOnDelete = false;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
/**
* Check subscriptions and send notifications or payload
*
@ -101,7 +94,7 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
}
if (resourceType != null && subscription.getCriteria() != null && !criteriaResource.equals(resourceType)) {
ourLog.info("Skipping subscription search for {} because it does not match the criteria {}", resourceType , subscription.getCriteria());
ourLog.info("Skipping subscription search for {} because it does not match the criteria {}", resourceType, subscription.getCriteria());
continue;
}
@ -110,7 +103,7 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
criteria = massageCriteria(criteria);
IBundleProvider results = getBundleProvider(criteria);
IBundleProvider results = getBundleProvider(criteria, false);
if (results.size() == 0) {
continue;
@ -195,25 +188,6 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
return request;
}
/**
* Search based on a query criteria
*
* @param criteria
* @return
*/
private IBundleProvider getBundleProvider(String criteria) {
RuntimeResourceDefinition responseResourceDef = mySubscriptionDao.validateCriteriaAndReturnResourceDefinition(criteria);
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(mySubscriptionDao, mySubscriptionDao.getContext(), criteria, responseResourceDef);
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass());
responseCriteriaUrl.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
return responseResults;
}
/**
* Get subscription from cache
*
@ -259,6 +233,27 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
return entity;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theDetails) {
// check the subscription criteria to see if its valid before creating or updating a subscription
if (RestOperationTypeEnum.CREATE.equals(theOperation) || RestOperationTypeEnum.UPDATE.equals(theOperation)) {
String resourceType = theDetails.getResourceType();
ourLog.info("prehandled resource type: " + resourceType);
if (resourceType != null && resourceType.equals(Subscription.class.getSimpleName())) {
Subscription subscription = (Subscription) theDetails.getResource();
if (subscription != null) {
checkSubscriptionCriterias(subscription.getCriteria());
}
}
}
super.incomingRequestPreHandled(theOperation, theDetails);
}
/**
* Read the existing subscriptions from the database
*/
@ -273,7 +268,7 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
map.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over "+MAX_SUBSCRIPTION_RESULTS+" subscriptions. Some subscriptions have not been loaded.");
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
}
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
@ -409,57 +404,4 @@ public class RestHookSubscriptionDstu2Interceptor extends ServerOperationInterce
mySubscriptionDao = theSubscriptionDao;
}
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theDetails) {
//check the subscription criteria to see if its valid before creating or updating a subscription
if (RestOperationTypeEnum.CREATE.equals(theOperation) || RestOperationTypeEnum.UPDATE.equals(theOperation)) {
String resourceType = theDetails.getResourceType();
ourLog.info("prehandled resource type: " + resourceType);
if (resourceType != null && resourceType.equals(Subscription.class.getSimpleName())) {
Subscription subscription = (Subscription) theDetails.getResource();
if (subscription != null) {
checkSubscriptionCriterias(subscription);
}
}
}
super.incomingRequestPreHandled(theOperation, theDetails);
}
private void checkSubscriptionCriterias(Subscription subscription){
try {
IBundleProvider results = executeSubscriptionCriteria(subscription, null);
}catch (Exception e){
throw new InvalidRequestException("Invalid criteria");
}
}
private IBundleProvider executeSubscriptionCriteria(Subscription subscription, IIdType idType){
//run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
String criteria = subscription.getCriteria();
if(idType != null) {
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
}
IBundleProvider results = getBundleProvider(criteria);
return results;
}
/**
* Get the encoding from the criteria or return JSON encoding if its not found
*
* @param criteria
* @return
*/
private EncodingEnum getEncoding(String criteria) {
//check criteria
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
return EncodingEnum.forContentType(nameValuePair.getValue());
}
}
return EncodingEnum.JSON;
}
}

View File

@ -30,12 +30,9 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.hl7.fhir.dstu3.model.Subscription;
@ -48,8 +45,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
@ -60,32 +55,31 @@ import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.interceptor.*;
public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterceptorAdapter {
public class RestHookSubscriptionDstu3Interceptor extends BaseRestHookSubscriptionInterceptor {
private static final Integer MAX_SUBSCRIPTION_RESULTS = 10000;
private static volatile ExecutorService executor;
private final static int MAX_THREADS = 1;
private final static int MAX_THREADS = 1;
private static final Logger ourLog = LoggerFactory.getLogger(RestHookSubscriptionDstu3Interceptor.class);
@Autowired
private FhirContext myFhirContext;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<Subscription> mySubscriptionDao;
private boolean notifyOnDelete = false;
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
/**
* Check subscriptions and send notifications or payload
*
* @param idType
* @param resourceType
* @param theOperation
* @param theOperation
*/
private void checkSubscriptions(IIdType idType, String resourceType, RestOperationTypeEnum theOperation) {
for (Subscription subscription : myRestHookSubscriptions) {
@ -99,7 +93,7 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
}
if (resourceType != null && subscription.getCriteria() != null && !criteriaResource.equals(resourceType)) {
ourLog.info("Skipping subscription search for {} because it does not match the criteria {}", resourceType , subscription.getCriteria());
ourLog.info("Skipping subscription search for {} because it does not match the criteria {}", resourceType, subscription.getCriteria());
continue;
}
@ -108,7 +102,7 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
criteria = massageCriteria(criteria);
IBundleProvider results = getBundleProvider(criteria);
IBundleProvider results = getBundleProvider(criteria, false);
if (results.size() == 0) {
continue;
@ -134,13 +128,13 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
while (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
HttpUriRequest request = null;
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
String payload = theSubscription.getChannel().getPayload();
String resourceId = theResource.getIdElement().getIdPart();
// HTTP put
if (theOperation == RestOperationTypeEnum.UPDATE && EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
ourLog.info("XML payload found");
@ -186,25 +180,6 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
return request;
}
/**
* Search based on a query criteria
*
* @param criteria
* @return
*/
private IBundleProvider getBundleProvider(String criteria) {
RuntimeResourceDefinition responseResourceDef = mySubscriptionDao.validateCriteriaAndReturnResourceDefinition(criteria);
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(mySubscriptionDao, mySubscriptionDao.getContext(), criteria, responseResourceDef);
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass());
responseCriteriaUrl.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
return responseResults;
}
/**
* Get subscription from cache
*
@ -250,6 +225,27 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
return entity;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theDetails) {
// check the subscription criteria to see if its valid before creating or updating a subscription
if (RestOperationTypeEnum.CREATE.equals(theOperation) || RestOperationTypeEnum.UPDATE.equals(theOperation)) {
String resourceType = theDetails.getResourceType();
ourLog.info("prehandled resource type: " + resourceType);
if (resourceType != null && resourceType.equals(Subscription.class.getSimpleName())) {
Subscription subscription = (Subscription) theDetails.getResource();
if (subscription != null) {
checkSubscriptionCriterias(subscription.getCriteria());
}
}
}
super.incomingRequestPreHandled(theOperation, theDetails);
}
/**
* Read the existing subscriptions from the database
*/
@ -264,7 +260,7 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
map.setCount(MAX_SUBSCRIPTION_RESULTS);
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over "+MAX_SUBSCRIPTION_RESULTS+" subscriptions. Some subscriptions have not been loaded.");
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
}
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
@ -400,57 +396,4 @@ public class RestHookSubscriptionDstu3Interceptor extends ServerOperationInterce
mySubscriptionDao = theSubscriptionDao;
}
@Override
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theDetails) {
//check the subscription criteria to see if its valid before creating or updating a subscription
if (RestOperationTypeEnum.CREATE.equals(theOperation) || RestOperationTypeEnum.UPDATE.equals(theOperation)) {
String resourceType = theDetails.getResourceType();
ourLog.info("prehandled resource type: " + resourceType);
if (resourceType != null && resourceType.equals(Subscription.class.getSimpleName())) {
Subscription subscription = (Subscription) theDetails.getResource();
if (subscription != null) {
checkSubscriptionCriterias(subscription);
}
}
}
super.incomingRequestPreHandled(theOperation, theDetails);
}
private void checkSubscriptionCriterias(Subscription subscription){
try {
IBundleProvider results = executeSubscriptionCriteria(subscription, null);
}catch (Exception e){
throw new InvalidRequestException("Invalid criteria");
}
}
private IBundleProvider executeSubscriptionCriteria(Subscription subscription, IIdType idType){
//run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
String criteria = subscription.getCriteria();
if(idType != null) {
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
}
IBundleProvider results = getBundleProvider(criteria);
return results;
}
/**
* Get the encoding from the criteria or return JSON encoding if its not found
*
* @param criteria
* @return
*/
private EncodingEnum getEncoding(String criteria) {
//check criteria
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
return EncodingEnum.forContentType(nameValuePair.getValue());
}
}
return EncodingEnum.JSON;
}
}

View File

@ -196,8 +196,6 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc1Id, enc2Id));
System.exit(0);
map = new SearchParameterMap();
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Patient", "foo|bar").setChain("identifier"));
results = myEncounterDao.search(map);

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.subscription;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.util.List;
@ -36,6 +36,7 @@ import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.PortUtil;
/**
@ -58,13 +59,12 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute();
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
}
@Before
public void beforeRegisterRestHookListener() {
// ourRestHookSubscriptionInterceptor.set
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
}
@ -127,22 +127,21 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
Thread.sleep(500);
assertEquals(1, ourCreatedObservations.size());
assertEquals(0, ourUpdatedObservations.size());
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.setCriteria(criteria1);
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
Observation observation2 = sendObservation(code, "SNOMED-CT");
// Should see two subscription notifications
Thread.sleep(500);
assertEquals(3, ourCreatedObservations.size());
assertEquals(0, ourUpdatedObservations.size());
ourClient.delete().resourceById(new IdDt("Subscription/"+ subscription2.getId())).execute();
ourClient.delete().resourceById(new IdDt("Subscription/" + subscription2.getId())).execute();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
@ -183,6 +182,20 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
Assert.assertFalse(observation2.getId().isEmpty());
}
@Test
public void testRestHookSubscriptionInvalidCriteria() throws Exception {
String payload = "application/xml";
String criteria1 = "Observation?codeeeee=SNOMED-CT";
try {
createSubscription(criteria1, payload, ourListenerServerBase);
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid criteria: Failed to parse match URL[Observation?codeeeee=SNOMED-CT] - Resource type Observation does not have a parameter with name: codeeeee", e.getMessage());
}
}
@Test
public void testRestHookSubscriptionXml() throws Exception {
String payload = "application/xml";
@ -200,22 +213,21 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
Thread.sleep(500);
assertEquals(1, ourCreatedObservations.size());
assertEquals(0, ourUpdatedObservations.size());
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.setCriteria(criteria1);
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
Observation observation2 = sendObservation(code, "SNOMED-CT");
// Should see two subscription notifications
Thread.sleep(500);
assertEquals(3, ourCreatedObservations.size());
assertEquals(0, ourUpdatedObservations.size());
ourClient.delete().resourceById(new IdDt("Subscription/"+ subscription2.getId())).execute();
ourClient.delete().resourceById(new IdDt("Subscription/" + subscription2.getId())).execute();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
@ -256,7 +268,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
Assert.assertFalse(observation2.getId().isEmpty());
}
@BeforeClass
public static void startListenerServer() throws Exception {
ourListenerPort = PortUtil.findFreePort();

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
/**
* Test the rest-hook subscriptions
@ -65,6 +66,21 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
ourUpdatedObservations.clear();
ourContentTypes.clear();
}
@Test
public void testRestHookSubscriptionInvalidCriteria() throws Exception {
String payload = "application/xml";
String criteria1 = "Observation?codeeeee=SNOMED-CT";
try {
createSubscription(criteria1, payload, ourListenerServerBase);
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid criteria: Failed to parse match URL[Observation?codeeeee=SNOMED-CT] - Resource type Observation does not have a parameter with name: codeeeee", e.getMessage());
}
}
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) {
Subscription subscription = new Subscription();