diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/NumberParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/NumberParam.java index 7dc32ef8b5b..1e9e113a374 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/NumberParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/NumberParam.java @@ -32,6 +32,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType; public class NumberParam extends BaseParamWithPrefix implements IQueryParameterType { + private static final long serialVersionUID = 1L; private BigDecimal myQuantity; /** @@ -41,6 +42,16 @@ public class NumberParam extends BaseParamWithPrefix implements IQu super(); } + /** + * Constructor + * + * @param theValue + * A value, e.g. "10" + */ + public NumberParam(int theValue) { + setValue(new BigDecimal(theValue)); + } + /** * Constructor * @@ -79,6 +90,15 @@ public class NumberParam extends BaseParamWithPrefix implements IQu } + public BigDecimal getValue() { + return myQuantity; + } + + public NumberParam setValue(BigDecimal theValue) { + myQuantity = theValue; + return this; + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE); @@ -87,13 +107,4 @@ public class NumberParam extends BaseParamWithPrefix implements IQu return b.build(); } - public BigDecimal getValue() { - return myQuantity; - } - - public NumberParam setValue(BigDecimal theValue) { - myQuantity = theValue; - return this; - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java index cd29240777e..d27c8dcb4dd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java @@ -173,12 +173,14 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ return true; } - public void setChain(String theChain) { + public ReferenceParam setChain(String theChain) { myChain = theChain; + return this; } - public void setValue(String theValue) { + public ReferenceParam setValue(String theValue) { myId.setValue(theValue); + return this; } /** diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 368e2359324..0962b25db41 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -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 getParameters() { - final MultivaluedMap queryParameters = getUriInfo().getQueryParameters(); - final HashMap params = new HashMap(); - for (final Entry> 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 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 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 getParameters() { + final MultivaluedMap queryParameters = getUriInfo().getQueryParameters(); + final HashMap params = new HashMap(); + for (final Entry> 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; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 7dacbe72832..31d108d7ed8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -26,52 +26,21 @@ import static org.apache.commons.lang3.StringUtils.trim; import java.io.UnsupportedEncodingException; import java.text.Normalizer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import javax.persistence.EntityManager; -import javax.persistence.NoResultException; -import javax.persistence.PersistenceContext; -import javax.persistence.PersistenceContextType; -import javax.persistence.Tuple; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; +import javax.persistence.*; +import javax.persistence.criteria.*; import javax.xml.stream.events.Characters; import javax.xml.stream.events.XMLEvent; -import org.apache.commons.lang3.NotImplementedException; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.*; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseCoding; -import org.hl7.fhir.instance.model.api.IBaseExtension; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IDomainResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.instance.model.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; @@ -81,89 +50,31 @@ import com.google.common.collect.Sets; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.RuntimeChildResourceDefinition; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; -import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; -import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; -import ca.uhn.fhir.jpa.dao.data.ISearchDao; -import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; -import ca.uhn.fhir.jpa.entity.BaseHasResource; -import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; -import ca.uhn.fhir.jpa.entity.BaseTag; -import ca.uhn.fhir.jpa.entity.ForcedId; -import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTag; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; -import ca.uhn.fhir.jpa.entity.ResourceLink; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.ResourceTag; -import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; -import ca.uhn.fhir.jpa.entity.SearchTypeEnum; -import ca.uhn.fhir.jpa.entity.TagDefinition; -import ca.uhn.fhir.jpa.entity.TagTypeEnum; +import ca.uhn.fhir.context.*; +import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.DeleteConflict; -import ca.uhn.fhir.model.api.IQueryParameterAnd; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.api.Tag; -import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu.resource.BaseResource; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.model.primitive.XhtmlDt; +import ca.uhn.fhir.model.primitive.*; import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.parser.LenientErrorHandler; +import ca.uhn.fhir.parser.*; 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; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringAndListParam; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.TokenAndListParam; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.param.UriAndListParam; -import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.method.*; +import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.FhirTerser; -import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.util.*; public abstract class BaseHapiFhirDao implements IDao { @@ -442,6 +353,11 @@ public abstract class BaseHapiFhirDao implements IDao { "Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType()); } + if (target.getDeleted() != null) { + String resName = targetResourceDef.getName(); + throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit); + } + if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) { continue; } @@ -634,6 +550,14 @@ public abstract class BaseHapiFhirDao implements IDao { } } + private Set getAllTagDefinitions(ResourceTable theEntity) { + HashSet retVal = Sets.newHashSet(); + for (ResourceTag next : theEntity.getTags()) { + retVal.add(next.getTag()); + } + return retVal; + } + protected DaoConfig getConfig() { return myConfig; } @@ -1000,14 +924,6 @@ public abstract class BaseHapiFhirDao implements IDao { return changed; } - private Set getAllTagDefinitions(ResourceTable theEntity) { - HashSet retVal = Sets.newHashSet(); - for (ResourceTag next : theEntity.getTags()) { - retVal.add(next.getTag()); - } - return retVal; - } - @SuppressWarnings("unchecked") private R populateResourceMetadataHapi(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation, IResource res) { R retVal = (R) res; @@ -1806,6 +1722,14 @@ public abstract class BaseHapiFhirDao implements IDao { throw new ResourceVersionConflictException(firstMsg, oo); } + protected void validateMetaCount(int theMetaCount) { + if (myConfig.getResourceMetaCountHardLimit() != null) { + if (theMetaCount > myConfig.getResourceMetaCountHardLimit()) { + throw new UnprocessableEntityException("Resource contains " + theMetaCount + " meta entries (tag/profile/security label), maximum is " + myConfig.getResourceMetaCountHardLimit()); + } + } + } + /** * This method is invoked immediately before storing a new resource, or an update to an existing resource to allow the DAO to ensure that it is valid for persistence. By default, checks for the * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check. @@ -1817,15 +1741,26 @@ public abstract class BaseHapiFhirDao implements IDao { */ protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { Object tag = null; + + int totalMetaCount = 0; + if (theResource instanceof IResource) { IResource res = (IResource) theResource; TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res); if (tagList != null) { tag = tagList.getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE); + totalMetaCount += tagList.size(); + } + List profileList = ResourceMetadataKeyEnum.PROFILES.get(res); + if (profileList != null) { + totalMetaCount += profileList.size(); } } else { IAnyResource res = (IAnyResource) theResource; tag = res.getMeta().getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE); + totalMetaCount += res.getMeta().getTag().size(); + totalMetaCount += res.getMeta().getProfile().size(); + totalMetaCount += res.getMeta().getSecurity().size(); } if (tag != null) { @@ -1835,6 +1770,8 @@ public abstract class BaseHapiFhirDao implements IDao { String resName = getContext().getResourceDefinition(theResource).getName(); validateChildReferences(theResource, resName); + validateMetaCount(totalMetaCount); + } protected static boolean isValidPid(IIdType theId) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 56400d4f9e5..c15c9ab2f0c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -29,6 +29,7 @@ import javax.annotation.PostConstruct; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.instance.model.api.*; import org.springframework.beans.factory.annotation.Autowired; @@ -48,6 +49,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.StopWatch; @@ -117,9 +119,10 @@ public abstract class BaseHapiFhirResourceDao extends B TagDefinition def = getTagOrNull(TagTypeEnum.TAG, theScheme, theTerm, theLabel); if (def != null) { BaseTag newEntity = entity.addTag(def); - - myEntityManager.persist(newEntity); - myEntityManager.merge(entity); + if (newEntity.getTagId() == null) { + myEntityManager.persist(newEntity); + myEntityManager.merge(entity); + } } ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); @@ -430,7 +433,6 @@ public abstract class BaseHapiFhirResourceDao extends B private void doMetaAdd(MT theMetaAdd, BaseHasResource entity) { List tags = toTagList(theMetaAdd); - //@formatter:off for (TagDefinition nextDef : tags) { boolean hasTag = false; @@ -449,12 +451,15 @@ public abstract class BaseHapiFhirResourceDao extends B TagDefinition def = getTagOrNull(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay()); if (def != null) { BaseTag newEntity = entity.addTag(def); - myEntityManager.persist(newEntity); + if (newEntity.getTagId() == null) { + myEntityManager.persist(newEntity); + } } } } - //@formatter:on + validateMetaCount(entity.getTags().size()); + myEntityManager.merge(entity); } @@ -562,6 +567,16 @@ public abstract class BaseHapiFhirResourceDao extends B theSavedEntity.setVersion(newVersionLong); } + protected boolean isPagingProviderDatabaseBacked(RequestDetails theRequestDetails) { + if (theRequestDetails == null || theRequestDetails.getServer() == null) { + return false; + } + if (theRequestDetails.getServer().getPagingProvider() instanceof DatabaseBackedPagingProvider) { + return true; + } + return false; + } + @Override public MT metaAddOperation(IIdType theResourceId, MT theMetaAdd, RequestDetails theRequestDetails) { // Notify interceptors @@ -594,6 +609,7 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } + @Override public MT metaDeleteOperation(IIdType theResourceId, MT theMetaDel, RequestDetails theRequestDetails) { // Notify interceptors @@ -628,7 +644,6 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } - @Override public MT metaGetOperation(Class theType, IIdType theId, RequestDetails theRequestDetails) { // Notify interceptors @@ -860,12 +875,12 @@ public abstract class BaseHapiFhirResourceDao extends B ourLog.debug("Indexing resource {} - PID {}", theResource.getIdElement().getValue(), theEntity.getId()); updateEntity(theResource, theEntity, null, true, false, theEntity.getUpdatedDate(), true, false); } - + @Override public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm) { removeTag(theId, theTagType, theScheme, theTerm, null); } - + @Override public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, RequestDetails theRequestDetails) { // Notify interceptors @@ -915,19 +930,21 @@ public abstract class BaseHapiFhirResourceDao extends B notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails); if (theRequestDetails.isSubRequest()) { - theParams.setLoadSynchronous(true); - int max = myDaoConfig.getMaximumSearchResultCountInTransaction(); - theParams.setLoadSynchronousUpTo(myDaoConfig.getMaximumSearchResultCountInTransaction()); + Integer max = myDaoConfig.getMaximumSearchResultCountInTransaction(); + if (max != null) { + Validate.inclusiveBetween(1, Integer.MAX_VALUE, max.intValue(), "Maximum search result count in transaction ust be a positive integer"); + theParams.setLoadSynchronousUpTo(myDaoConfig.getMaximumSearchResultCountInTransaction()); + } + } + + if (!isPagingProviderDatabaseBacked(theRequestDetails)) { + theParams.setLoadSynchronous(true); } - } return mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName()); } - - - @Override public Set searchForIds(SearchParameterMap theParams) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 475599260bd..99f9ae23518 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -57,7 +57,7 @@ public class DaoConfig { * * @see #setMaximumSearchResultCountInTransaction(int) */ - private static final int DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = 500; + private static final Integer DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION = null; /** * Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute) @@ -103,13 +103,19 @@ public class DaoConfig { * update setter javadoc if default changes */ private boolean myIndexContainedResources = true; + private List myInterceptors; + /** * update setter javadoc if default changes */ private int myMaximumExpansionSize = 5000; - private int myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION; + private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION; private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC; + /** + * update setter javadoc if default changes + */ + private Integer myResourceMetaCountHardLimit = 1000; private Long myReuseCachedSearchResultsForMillis = DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS; private boolean mySchedulingDisabled; private boolean mySubscriptionEnabled; @@ -121,7 +127,6 @@ public class DaoConfig { private boolean mySuppressUpdatesWithNoChange = true; private Set myTreatBaseUrlsAsLocal = new HashSet(); private Set myTreatReferencesAsLogical = new HashSet(DEFAULT_LOGICAL_BASE_URLS); - /** * Add a value to the {@link #setTreatReferencesAsLogical(Set) logical references list}. * @@ -135,7 +140,6 @@ public class DaoConfig { } myTreatReferencesAsLogical.add(theTreatReferencesAsLogical); } - /** * When a code system is added that contains more than this number of codes, * the code system will be indexed later in an incremental process in order to @@ -229,14 +233,17 @@ public class DaoConfig { } /** - * Provides the maximum number of results which may be returned by a search within a FHIR transaction - * operation. For example, if this value is set to 100 and a FHIR transaction is processed with a sub-request - * for Patient?gender=male, the server will throw an error (and the transaction will fail) if there are more than + * Provides the maximum number of results which may be returned by a search (HTTP GET) which + * is executed as a sub-operation within within a FHIR transaction or + * batch operation. For example, if this value is set to 100 and + * a FHIR transaction is processed with a sub-request for Patient?gender=male, + * the server will throw an error (and the transaction will fail) if there are more than * 100 resources on the server which match this query. - * - * @see #DEFAULT_LOGICAL_BASE_URLS The default value for this setting + *

+ * The default value is null, which means that there is no limit. + *

*/ - public int getMaximumSearchResultCountInTransaction() { + public Integer getMaximumSearchResultCountInTransaction() { return myMaximumSearchResultCountInTransaction; } @@ -244,6 +251,21 @@ public class DaoConfig { return myResourceEncoding; } + /** + * If set, an individual resource will not be allowed to have more than the + * given number of tags, profiles, and security labels (the limit is for the combined + * total for all of these things on an individual resource). + *

+ * If set to null, no limit will be applied. + *

+ *

+ * The default value for this setting is 1000. + *

+ */ + public Integer getResourceMetaCountHardLimit() { + return myResourceMetaCountHardLimit; + } + /** * If set to a non {@literal null} value (default is {@link #DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS non null}) * if an identical search is requested multiple times within this window, the same results will be returned @@ -376,8 +398,6 @@ public class DaoConfig { * and other FHIR features may not behave as expected when referential integrity is not * preserved. Use this feature with caution. *

- * - * @return */ public boolean isEnforceReferentialIntegrityOnDelete() { return myEnforceReferentialIntegrityOnDelete; @@ -682,14 +702,17 @@ public class DaoConfig { } /** - * Provides the maximum number of results which may be returned by a search within a FHIR transaction - * operation. For example, if this value is set to 100 and a FHIR transaction is processed with a sub-request - * for Patient?gender=male, the server will throw an error (and the transaction will fail) if there are more than + * Provides the maximum number of results which may be returned by a search (HTTP GET) which + * is executed as a sub-operation within within a FHIR transaction or + * batch operation. For example, if this value is set to 100 and + * a FHIR transaction is processed with a sub-request for Patient?gender=male, + * the server will throw an error (and the transaction will fail) if there are more than * 100 resources on the server which match this query. - * - * @see #DEFAULT_LOGICAL_BASE_URLS The default value for this setting + *

+ * The default value is null, which means that there is no limit. + *

*/ - public void setMaximumSearchResultCountInTransaction(int theMaximumSearchResultCountInTransaction) { + public void setMaximumSearchResultCountInTransaction(Integer theMaximumSearchResultCountInTransaction) { myMaximumSearchResultCountInTransaction = theMaximumSearchResultCountInTransaction; } @@ -697,6 +720,21 @@ public class DaoConfig { myResourceEncoding = theResourceEncoding; } + /** + * If set, an individual resource will not be allowed to have more than the + * given number of tags, profiles, and security labels (the limit is for the combined + * total for all of these things on an individual resource). + *

+ * If set to null, no limit will be applied. + *

+ *

+ * The default value for this setting is 1000. + *

+ */ + public void setResourceMetaCountHardLimit(Integer theResourceMetaCountHardLimit) { + myResourceMetaCountHardLimit = theResourceMetaCountHardLimit; + } + /** * If set to a non {@literal null} value (default is {@link #DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS non null}) * if an identical search is requested multiple times within this window, the same results will be returned diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java index c9c48450959..f2681e1a1ea 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java @@ -46,7 +46,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im super(); } - private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) { + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { paramMap.setCount(theCount.getValue()); @@ -65,6 +65,10 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im paramMap.add("_id", new StringParam(theId.getIdPart())); } + if (!isPagingProviderDatabaseBacked(theRequestDetails)) { + paramMap.setLoadSynchronous(true); + } + return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName()); } @@ -74,7 +78,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getResourceName(), null); notifyInterceptors(RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE, requestDetails); - return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative); + return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); } @Override @@ -83,7 +87,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getResourceName(), null); notifyInterceptors(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, requestDetails); - return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative); + return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index f2031d0eedf..3f503c089e4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -106,8 +106,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { Bundle resp = new Bundle(); resp.setType(BundleTypeEnum.BATCH_RESPONSE); - OperationOutcome ooResp = new OperationOutcome(); - resp.addEntry().setResource(ooResp); /* * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others @@ -163,7 +161,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { long delay = System.currentTimeMillis() - start; ourLog.info("Batch completed in {}ms", new Object[] { delay }); - ooResp.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Batch completed in " + delay + "ms"); return resp; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index 36f99d9368e..a593810fe67 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -27,86 +27,34 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; import java.math.MathContext; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; -import javax.persistence.criteria.AbstractQuery; -import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.*; import javax.persistence.criteria.CriteriaBuilder.In; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Expression; -import javax.persistence.criteria.From; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.JoinType; -import javax.persistence.criteria.Order; -import javax.persistence.criteria.Path; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; -import javax.persistence.criteria.Subquery; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.*; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.tuple.Pair; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.query.Query; +import org.hl7.fhir.instance.model.api.*; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import com.google.common.collect.*; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; -import ca.uhn.fhir.context.RuntimeChildResourceDefinition; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.*; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; -import ca.uhn.fhir.jpa.entity.BaseHasResource; -import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; -import ca.uhn.fhir.jpa.entity.ResourceLink; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.ResourceTag; -import ca.uhn.fhir.jpa.entity.SearchParam; -import ca.uhn.fhir.jpa.entity.SearchParamPresent; -import ca.uhn.fhir.jpa.entity.TagDefinition; -import ca.uhn.fhir.jpa.entity.TagTypeEnum; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; import ca.uhn.fhir.jpa.util.StopWatch; -import ca.uhn.fhir.model.api.IPrimitiveDatatype; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.base.composite.BaseCodingDt; -import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; -import ca.uhn.fhir.model.base.composite.BaseQuantityDt; +import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.base.composite.*; import ca.uhn.fhir.model.dstu.resource.BaseResource; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; @@ -115,23 +63,9 @@ import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; -import ca.uhn.fhir.rest.param.CompositeParam; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.HasParam; -import ca.uhn.fhir.rest.param.NumberParam; -import ca.uhn.fhir.rest.param.ParamPrefixEnum; -import ca.uhn.fhir.rest.param.QuantityParam; -import ca.uhn.fhir.rest.param.ReferenceParam; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.param.TokenParamModifier; -import ca.uhn.fhir.rest.param.UriParam; -import ca.uhn.fhir.rest.param.UriParamQualifierEnum; +import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.Constants; -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.*; import ca.uhn.fhir.util.UrlUtil; /** @@ -139,8 +73,8 @@ import ca.uhn.fhir.util.UrlUtil; * searchs for resources */ public class SearchBuilder implements ISearchBuilder { + private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList()); - private static Long NO_MORE = Long.valueOf(-1); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class); private List myAlsoIncludePids; @@ -161,7 +95,7 @@ public class SearchBuilder implements ISearchBuilder { private ISearchParamRegistry mySearchParamRegistry; private String mySearchUuid; private IHapiTerminologySvc myTerminologySvc; - + /** * Constructor */ @@ -2037,6 +1971,7 @@ public class SearchBuilder implements ISearchBuilder { static Predicate[] toArray(List thePredicates) { return thePredicates.toArray(new Predicate[thePredicates.size()]); } + public class IncludesIterator implements Iterator{ private Iterator myCurrentIterator; @@ -2094,7 +2029,6 @@ public class SearchBuilder implements ISearchBuilder { } } - private enum JoinEnum { DATE, NUMBER, QUANTITY, REFERENCE, STRING, TOKEN, URI @@ -2160,8 +2094,11 @@ public class SearchBuilder implements ISearchBuilder { Integer maximumResults = myCallingDao.getConfig().getFetchSizeDefaultMaximum(); final TypedQuery query = createQuery(mySort, maximumResults); - myResultsIterator = query.getResultList().iterator(); - + + Query hibernateQuery = (Query) query; + ScrollableResults scroll = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY); + myResultsIterator = new ScrollableResultsIterator(scroll); + // If the query resulted in extra results being requested if (myAlsoIncludePids != null) { myPreResultsIterator = myAlsoIncludePids.iterator(); @@ -2245,4 +2182,40 @@ public class SearchBuilder implements ISearchBuilder { } } + public class ScrollableResultsIterator implements Iterator { + + private Long myNext; + private ScrollableResults myScroll; + + public ScrollableResultsIterator(ScrollableResults theScroll) { + myScroll = theScroll; + } + + private void ensureHaveNext() { + if (myNext == null) { + if (myScroll.next()) { + myNext = (Long) myScroll.get(0); + } else { + myNext = NO_MORE; + } + } + } + + @Override + public boolean hasNext() { + ensureHaveNext(); + return myNext != NO_MORE; + } + + @Override + public Long next() { + ensureHaveNext(); + Validate.isTrue(myNext != NO_MORE); + Long next = myNext; + myNext = null; + return next; + } + + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java index 61f071f748f..3d334deeda4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java @@ -35,8 +35,8 @@ public interface ISearchDao extends JpaRepository { @Query("SELECT s FROM Search s WHERE s.myUuid = :uuid") public Search findByUuid(@Param("uuid") String theUuid); - @Query("SELECT s FROM Search s WHERE s.mySearchLastReturned < :cutoff") - public Collection findWhereLastReturnedBefore(@Param("cutoff") Date theCutoff); + @Query("SELECT s.myId FROM Search s WHERE s.mySearchLastReturned < :cutoff") + public Collection findWhereLastReturnedBefore(@Param("cutoff") Date theCutoff); // @Query("SELECT s FROM Search s WHERE s.myCreated < :cutoff") // public Collection findWhereCreatedBefore(@Param("cutoff") Date theCutoff); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java index cfbde6e12da..eed04b60297 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java @@ -50,7 +50,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3im @Autowired private ISearchParamRegistry mySerarchParamRegistry; - private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) { + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { paramMap.setCount(theCount.getValue()); @@ -69,17 +69,21 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3im paramMap.add("_id", new StringParam(theId.getIdPart())); } + if (!isPagingProviderDatabaseBacked(theRequestDetails)) { + paramMap.setLoadSynchronous(true); + } + return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName()); } @Override public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { - return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative); + return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); } @Override public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { - return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative); + return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java index 6809fa97daf..79a3d3a965f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java @@ -114,8 +114,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { Bundle resp = new Bundle(); resp.setType(BundleType.BATCHRESPONSE); - OperationOutcome ooResp = new OperationOutcome(); - resp.addEntry().setResource(ooResp); /* * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others @@ -171,7 +169,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { long delay = System.currentTimeMillis() - start; ourLog.info("Batch completed in {}ms", new Object[] { delay }); - ooResp.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Batch completed in " + delay + "ms"); return resp; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java index 812085c51a1..055ed717096 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java @@ -23,16 +23,7 @@ package ca.uhn.fhir.jpa.entity; import java.util.Collection; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.Lob; -import javax.persistence.MappedSuperclass; -import javax.persistence.OneToOne; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import javax.persistence.*; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.model.primitive.IdDt; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java index 62c049fd775..8f15307cce3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseTag.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.entity; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -32,13 +32,17 @@ public class BaseTag implements Serializable { private static final long serialVersionUID = 1L; - @ManyToOne(cascade= {}) - @JoinColumn(name="TAG_ID", nullable=false) + @ManyToOne(cascade = {}) + @JoinColumn(name = "TAG_ID", nullable = false) private TagDefinition myTag; - @Column(name="TAG_ID", insertable=false,updatable=false) + @Column(name = "TAG_ID", insertable = false, updatable = false) private Long myTagId; + public Long getTagId() { + return myTagId; + } + public TagDefinition getTag() { return myTag; } @@ -46,6 +50,5 @@ public class BaseTag implements Serializable { public void setTag(TagDefinition theTag) { myTag = theTag; } - - + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java index e8b765460cf..3953ba04854 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java @@ -93,8 +93,13 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl } @Override - public BaseTag addTag(TagDefinition theDef) { - ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theDef); + public ResourceHistoryTag addTag(TagDefinition theTag) { + for (ResourceHistoryTag next : getTags()) { + if (next.getTag().equals(theTag)) { + return next; + } + } + ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag); getTags().add(historyTag); return historyTag; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java index 5995895a922..72c2b874320 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java @@ -22,21 +22,13 @@ package ca.uhn.fhir.jpa.entity; import java.io.Serializable; -import javax.persistence.Column; -import javax.persistence.Embeddable; -import javax.persistence.Entity; -import javax.persistence.ForeignKey; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +import javax.persistence.*; @Embeddable @Entity -@Table(name = "HFJ_HISTORY_TAG") +@Table(name = "HFJ_HISTORY_TAG", uniqueConstraints= { + @UniqueConstraint(name="IDX_RESHISTTAG_TAGID", columnNames= {"RES_VER_PID","TAG_ID"}) +}) public class ResourceHistoryTag extends BaseTag implements Serializable { private static final long serialVersionUID = 1L; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index 1bb19109aca..cd2d318436a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -250,6 +250,11 @@ public class ResourceTable extends BaseHasResource implements Serializable { @Override public ResourceTag addTag(TagDefinition theTag) { + for (ResourceTag next : getTags()) { + if (next.getTag().equals(theTag)) { + return next; + } + } ResourceTag tag = new ResourceTag(this, theTag); getTags().add(tag); return tag; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java index d54a10f9aeb..7ab7166db0b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java @@ -19,23 +19,14 @@ package ca.uhn.fhir.jpa.entity; * limitations under the License. * #L% */ +import javax.persistence.*; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.ForeignKey; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.*; @Entity -@Table(name = "HFJ_RES_TAG") +@Table(name = "HFJ_RES_TAG", uniqueConstraints= { + @UniqueConstraint(name="IDX_RESTAG_TAGID", columnNames= {"RES_ID","TAG_ID"}) +}) public class ResourceTag extends BaseTag { private static final long serialVersionUID = 1L; @@ -116,4 +107,12 @@ public class ResourceTag extends BaseTag { return b.toHashCode(); } + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("resId", getResourceId()); + b.append("tag", getTag().getId()); + return b.build(); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java index 13cd45265e0..0ef85dd8d40 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.entity; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -45,8 +45,8 @@ import ca.uhn.fhir.model.api.Tag; //@formatter:on @Entity -@Table(name = "HFJ_TAG_DEF", uniqueConstraints = { - @UniqueConstraint(name="IDX_TAGDEF_TYPESYSCODE", columnNames = { "TAG_TYPE", "TAG_SYSTEM", "TAG_CODE" }) +@Table(name = "HFJ_TAG_DEF", uniqueConstraints = { + @UniqueConstraint(name = "IDX_TAGDEF_TYPESYSCODE", columnNames = { "TAG_TYPE", "TAG_SYSTEM", "TAG_CODE" }) }) //@formatter:off public class TagDefinition implements Serializable { @@ -78,6 +78,8 @@ public class TagDefinition implements Serializable { @Enumerated(EnumType.ORDINAL) private TagTypeEnum myTagType; + private Integer myHashCode; + public TagDefinition() { } @@ -110,6 +112,7 @@ public class TagDefinition implements Serializable { public void setCode(String theCode) { myCode = theCode; + myHashCode = null; } public void setDisplay(String theDisplay) { @@ -118,10 +121,12 @@ public class TagDefinition implements Serializable { public void setSystem(String theSystem) { mySystem = theSystem; + myHashCode = null; } public void setTagType(TagTypeEnum theTagType) { myTagType = theTagType; + myHashCode = null; } public Tag toTag() { @@ -155,15 +160,14 @@ public class TagDefinition implements Serializable { @Override public int hashCode() { - HashCodeBuilder b = new HashCodeBuilder(); - if (myId != null) { - b.append(myId); - } else { + if (myHashCode == null) { + HashCodeBuilder b = new HashCodeBuilder(); b.append(myTagType); b.append(mySystem); b.append(myCode); + myHashCode = b.toHashCode(); } - return b.toHashCode(); + return myHashCode; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/BaseRestHookSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/BaseRestHookSubscriptionInterceptor.java new file mode 100644 index 00000000000..aebc05815dc --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/BaseRestHookSubscriptionInterceptor.java @@ -0,0 +1,91 @@ +package ca.uhn.fhir.jpa.interceptor; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +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 responseDao = getSubscriptionDao().getDao(responseResourceDef.getImplementingClass()); + + if (theCheckOnly) { + responseCriteriaUrl.setLoadSynchronousUpTo(1); + } else { + responseCriteriaUrl.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS); + } + + IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req); + return responseResults; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu2Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu2Interceptor.java index cf33f7d0a11..c74fb12602f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu2Interceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu2Interceptor.java @@ -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 myRestHookSubscriptions = new ArrayList(); @Autowired @Qualifier("mySubscriptionDaoDstu2") private IFhirResourceDao mySubscriptionDao; - private boolean myNotifyOnDelete = false; - private final List myRestHookSubscriptions = new ArrayList(); - /** * 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 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 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 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; - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu3Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu3Interceptor.java index 757411f4125..0eda2bd28a2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu3Interceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/RestHookSubscriptionDstu3Interceptor.java @@ -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 myRestHookSubscriptions = new ArrayList(); + @Autowired @Qualifier("mySubscriptionDaoDstu3") private IFhirResourceDao mySubscriptionDao; - + private boolean notifyOnDelete = false; - private final List myRestHookSubscriptions = new ArrayList(); /** * 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 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 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 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; - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java index 1ae9c54633f..da70d52eb69 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java @@ -19,9 +19,7 @@ package ca.uhn.fhir.jpa.provider; * limitations under the License. * #L% */ - -import java.util.List; -import java.util.Map; +import java.util.*; import javax.servlet.http.HttpServletRequest; @@ -51,6 +49,7 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { private volatile Conformance myCachedValue; private DaoConfig myDaoConfig; private String myImplementationDescription; + private boolean myIncludeResourceCounts; private RestfulServer myRestfulServer; private IFhirSystemDao mySystemDao; @@ -61,6 +60,7 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { public JpaConformanceProviderDstu2(){ super(); super.setCache(false); + setIncludeResourceCounts(true); } /** @@ -72,13 +72,17 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { mySystemDao = theSystemDao; myDaoConfig = theDaoConfig; super.setCache(false); + setIncludeResourceCounts(true); } @Override public Conformance getServerConformance(HttpServletRequest theRequest) { Conformance retVal = myCachedValue; - Map counts = mySystemDao.getResourceCounts(); + Map counts = Collections.emptyMap(); + if (myIncludeResourceCounts) { + counts = mySystemDao.getResourceCounts(); + } FhirContext ctx = myRestfulServer.getFhirContext(); @@ -119,6 +123,10 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { return retVal; } + public boolean isIncludeResourceCounts() { + return myIncludeResourceCounts; + } + public void setDaoConfig(DaoConfig myDaoConfig) { this.myDaoConfig = myDaoConfig; } @@ -128,6 +136,10 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { myImplementationDescription = theImplDesc; } + public void setIncludeResourceCounts(boolean theIncludeResourceCounts) { + myIncludeResourceCounts = theIncludeResourceCounts; + } + @Override public void setRestfulServer(RestfulServer theRestfulServer) { this.myRestfulServer = theRestfulServer; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java index b55b70e4e08..ba417412326 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import java.util.Collection; - /* * #%L * HAPI FHIR JPA Server @@ -21,23 +19,18 @@ import java.util.Collection; * limitations under the License. * #L% */ - -import java.util.Map; +import java.util.*; import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.CapabilityStatement.*; import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType; -import org.springframework.beans.factory.annotation.Autowired; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; @@ -47,9 +40,10 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se private volatile CapabilityStatement myCachedValue; private DaoConfig myDaoConfig; private String myImplementationDescription; + private boolean myIncludeResourceCounts; private RestfulServer myRestfulServer; private IFhirSystemDao mySystemDao; - + /** * Constructor */ @@ -57,8 +51,9 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se public JpaConformanceProviderDstu3(){ super(); super.setCache(false); + setIncludeResourceCounts(true); } - + /** * Constructor */ @@ -68,15 +63,17 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se mySystemDao = theSystemDao; myDaoConfig = theDaoConfig; super.setCache(false); + setIncludeResourceCounts(true); } - + @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest) { CapabilityStatement retVal = myCachedValue; - Map counts = mySystemDao.getResourceCounts(); - - FhirContext ctx = myRestfulServer.getFhirContext(); + Map counts = Collections.emptyMap(); + if (myIncludeResourceCounts) { + counts = mySystemDao.getResourceCounts(); + } retVal = super.getServerConformance(theRequest); for (CapabilityStatementRestComponent nextRest : retVal.getRest()) { @@ -148,6 +145,10 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se return retVal; } + public boolean isIncludeResourceCounts() { + return myIncludeResourceCounts; + } + /** * Subclasses may override */ @@ -164,6 +165,10 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se myImplementationDescription = theImplDesc; } + public void setIncludeResourceCounts(boolean theIncludeResourceCounts) { + myIncludeResourceCounts = theIncludeResourceCounts; + } + @Override public void setRestfulServer(RestfulServer theRestfulServer) { this.myRestfulServer = theRestfulServer; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java index da3ca84ee2d..2e4a9ea0da8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java @@ -77,7 +77,6 @@ public class DatabaseBackedPagingProvider extends BasePagingProvider implements @Override public synchronized String storeResultList(IBundleProvider theList) { String uuid = theList.getUuid(); - Validate.notNull(uuid); return uuid; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 77b0097533c..8eb3e8885be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -19,24 +19,9 @@ package ca.uhn.fhir.jpa.search; * limitations under the License. * #L% */ +import java.util.*; +import java.util.concurrent.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.transaction.Transactional; import javax.transaction.Transactional.TxType; @@ -47,34 +32,18 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.data.domain.*; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.transaction.*; +import org.springframework.transaction.support.*; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IDao; -import ca.uhn.fhir.jpa.dao.ISearchBuilder; -import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.dao.data.ISearchDao; -import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; -import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; -import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchInclude; -import ca.uhn.fhir.jpa.entity.SearchResult; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; -import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.method.PageMethodBinding; @@ -83,7 +52,7 @@ import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.*; public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { - static final int DEFAULT_SYNC_SIZE = 250; + public static final int DEFAULT_SYNC_SIZE = 250; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class); @@ -360,7 +329,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } @VisibleForTesting - void setLoadingThrottleForUnitTests(Integer theLoadingThrottleForUnitTests) { + public void setLoadingThrottleForUnitTests(Integer theLoadingThrottleForUnitTests) { myLoadingThrottleForUnitTests = theLoadingThrottleForUnitTests; } @@ -369,7 +338,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { myMaxMillisToWaitForRemoteResults = theMaxMillisToWaitForRemoteResults; } - void setNeverUseLocalSearchForUnitTests(boolean theNeverUseLocalSearchForUnitTests) { + @VisibleForTesting + public void setNeverUseLocalSearchForUnitTests(boolean theNeverUseLocalSearchForUnitTests) { myNeverUseLocalSearchForUnitTests = theNeverUseLocalSearchForUnitTests; } @@ -389,7 +359,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } @VisibleForTesting - void setSyncSizeForUnitTests(int theSyncSize) { + public void setSyncSizeForUnitTests(int theSyncSize) { mySyncSize = theSyncSize; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java index 726ac86c5f6..dff4f46fa67 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java @@ -71,13 +71,13 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { @Autowired private PlatformTransactionManager myTransactionManager; - protected void deleteSearch(final Search next) { + protected void deleteSearch(final Long theSearchPid) { TransactionTemplate tt = new TransactionTemplate(myTransactionManager); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - Search searchToDelete = mySearchDao.findOne(next.getId()); - ourLog.info("Expiring stale search {} / {}", searchToDelete.getId(), searchToDelete.getUuid()); + Search searchToDelete = mySearchDao.findOne(theSearchPid); + ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), searchToDelete.getCreated(), searchToDelete.getSearchLastReturned()); mySearchIncludeDao.deleteForSearch(searchToDelete.getId()); mySearchResultDao.deleteForSearch(searchToDelete.getId()); mySearchDao.delete(searchToDelete); @@ -97,13 +97,10 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { ourLog.debug("Searching for searches which are before {}", cutoff); - Collection toDelete = mySearchDao.findWhereLastReturnedBefore(cutoff); + Collection toDelete = mySearchDao.findWhereLastReturnedBefore(cutoff); if (!toDelete.isEmpty()) { - for (final Search next : toDelete) { - - ourLog.info("Deleting search {} - Created[{}] -- Last returned[{}]", next.getUuid(), next.getCreated(), next.getSearchLastReturned()); - + for (final Long next : toDelete) { deleteSearch(next); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index 578edef274d..89366320cb7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -69,7 +69,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { private Properties jpaProperties() { Properties extraProperties = new Properties(); extraProperties.put("hibernate.jdbc.batch_size", "50"); - extraProperties.put("hibernate.format_sql", "true"); + extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java index 7537c60e7f1..3adbb5fd8ea 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java @@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; @@ -75,7 +76,6 @@ import ca.uhn.fhir.util.TestUtil; @ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class}) //@formatter:on public abstract class BaseJpaDstu2Test extends BaseJpaTest { - @Autowired protected ApplicationContext myAppCtx; @Autowired @@ -114,18 +114,18 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { @Autowired @Qualifier("myLocationDaoDstu2") protected IFhirResourceDao myLocationDao; + @Autowired + @Qualifier("myMediaDaoDstu2") + protected IFhirResourceDao myMediaDao; @Autowired - @Qualifier("myMediaDaoDstu2") - protected IFhirResourceDao myMediaDao; +@Qualifier("myMedicationAdministrationDaoDstu2") +protected IFhirResourceDao myMedicationAdministrationDao; @Autowired @Qualifier("myMedicationDaoDstu2") protected IFhirResourceDao myMedicationDao; @Autowired - @Qualifier("myMedicationAdministrationDaoDstu2") - protected IFhirResourceDao myMedicationAdministrationDao; - @Autowired @Qualifier("myMedicationOrderDaoDstu2") protected IFhirResourceDao myMedicationOrderDao; @Autowired @@ -135,6 +135,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { @Qualifier("myOrganizationDaoDstu2") protected IFhirResourceDao myOrganizationDao; @Autowired + protected DatabaseBackedPagingProvider myPagingProvider; + @Autowired @Qualifier("myPatientDaoDstu2") protected IFhirResourceDaoPatient myPatientDao; @Autowired @@ -150,8 +152,12 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { @Qualifier("myResourceProvidersDstu2") protected Object myResourceProviders; @Autowired + protected ISearchCoordinatorSvc mySearchCoordinatorSvc; + @Autowired protected IFulltextSearchSvc mySearchDao; @Autowired + protected ISearchParamPresenceSvc mySearchParamPresenceSvc; + @Autowired @Qualifier("myStructureDefinitionDaoDstu2") protected IFhirResourceDao myStructureDefinitionDao; @Autowired @@ -171,10 +177,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { @Autowired @Qualifier("myValueSetDaoDstu2") protected IFhirResourceDaoValueSet myValueSetDao; - @Autowired - protected ISearchParamPresenceSvc mySearchParamPresenceSvc; - @Autowired - protected ISearchCoordinatorSvc mySearchCoordinatorSvc; @Before public void beforeCreateInterceptor() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java index 33a3b6d610d..6a025e36722 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSystemDaoDstu2Test.java @@ -303,36 +303,31 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest { request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient/THIS_ID_DOESNT_EXIST"); Bundle resp = mySystemDao.transaction(mySrd, request); - assertEquals(3, resp.getEntry().size()); + assertEquals(2, resp.getEntry().size()); assertEquals(BundleTypeEnum.BATCH_RESPONSE, resp.getTypeElement().getValueAsEnum()); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); EntryResponse respEntry; - // Bundle.entry[0] is operation outcome - assertEquals(OperationOutcome.class, resp.getEntry().get(0).getResource().getClass()); - assertEquals(IssueSeverityEnum.INFORMATION, ((OperationOutcome) resp.getEntry().get(0).getResource()).getIssue().get(0).getSeverityElement().getValueAsEnum()); - assertThat(((OperationOutcome) resp.getEntry().get(0).getResource()).getIssue().get(0).getDiagnostics(), startsWith("Batch completed in ")); - // Bundle.entry[1] is create response - assertEquals("201 Created", resp.getEntry().get(1).getResponse().getStatus()); - assertThat(resp.getEntry().get(1).getResponse().getLocation(), startsWith("Patient/")); + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), startsWith("Patient/")); // Bundle.entry[2] is failed read response - assertEquals(OperationOutcome.class, resp.getEntry().get(2).getResource().getClass()); - assertEquals(IssueSeverityEnum.ERROR, ((OperationOutcome) resp.getEntry().get(2).getResource()).getIssue().get(0).getSeverityElement().getValueAsEnum()); - assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) resp.getEntry().get(2).getResource()).getIssue().get(0).getDiagnostics()); - assertEquals("404 Not Found", resp.getEntry().get(2).getResponse().getStatus()); + assertEquals(OperationOutcome.class, resp.getEntry().get(1).getResource().getClass()); + assertEquals(IssueSeverityEnum.ERROR, ((OperationOutcome) resp.getEntry().get(1).getResource()).getIssue().get(0).getSeverityElement().getValueAsEnum()); + assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) resp.getEntry().get(1).getResource()).getIssue().get(0).getDiagnostics()); + assertEquals("404 Not Found", resp.getEntry().get(1).getResponse().getStatus()); // Check POST - respEntry = resp.getEntry().get(1).getResponse(); + respEntry = resp.getEntry().get(0).getResponse(); assertEquals("201 Created", respEntry.getStatus()); IdDt createdId = new IdDt(respEntry.getLocation()); assertEquals("Patient", createdId.getResourceType()); myPatientDao.read(createdId, mySrd); // shouldn't fail // Check GET - respEntry = resp.getEntry().get(2).getResponse(); + respEntry = resp.getEntry().get(1).getResponse(); assertThat(respEntry.getStatus(), startsWith("404")); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 1f9dbd013b3..8520877dff2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -27,6 +27,7 @@ import org.hl7.fhir.dstu3.model.Condition; import org.hl7.fhir.dstu3.model.Device; import org.hl7.fhir.dstu3.model.DiagnosticReport; import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.Group; import org.hl7.fhir.dstu3.model.Immunization; import org.hl7.fhir.dstu3.model.ImmunizationRecommendation; import org.hl7.fhir.dstu3.model.Location; @@ -77,13 +78,15 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; -import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.dao.data.ITagDefinitionDao; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; +import ca.uhn.fhir.jpa.search.*; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; @@ -104,19 +107,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; private static IFhirResourceDaoValueSet ourValueSetDao; - @Autowired - protected ITagDefinitionDao myTagDefinitionDao; - @Autowired - protected IResourceTagDao myResourceTagDao; - @Autowired - protected ISearchDao mySearchEntityDao; - @Autowired - @Qualifier("mySearchParameterDaoDstu3") - protected IFhirResourceDao mySearchParameterDao; - @Autowired - protected ISearchParamRegistry mySearchParamRegsitry; -// @Autowired -// protected HapiWorkerContext myHapiWorkerContext; + // @Autowired + // protected HapiWorkerContext myHapiWorkerContext; @Autowired @Qualifier("myAllergyIntoleranceDaoDstu3") protected IFhirResourceDao myAllergyIntoleranceDao; @@ -135,9 +127,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myCarePlanDaoDstu3") protected IFhirResourceDao myCarePlanDao; @Autowired - @Qualifier("myProcedureRequestDaoDstu3") - protected IFhirResourceDao myProcedureRequestDao; - @Autowired @Qualifier("myCodeSystemDaoDstu3") protected IFhirResourceDaoCodeSystem myCodeSystemDao; @Autowired @@ -166,6 +155,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired protected FhirContext myFhirCtx; @Autowired + @Qualifier("myGroupDaoDstu3") + protected IFhirResourceDao myGroupDao; + @Autowired @Qualifier("myImmunizationDaoDstu3") protected IFhirResourceDao myImmunizationDao; @Autowired @@ -181,12 +173,12 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myMediaDaoDstu3") protected IFhirResourceDao myMediaDao; @Autowired - @Qualifier("myMedicationDaoDstu3") - protected IFhirResourceDao myMedicationDao; - @Autowired @Qualifier("myMedicationAdministrationDaoDstu3") protected IFhirResourceDao myMedicationAdministrationDao; @Autowired + @Qualifier("myMedicationDaoDstu3") + protected IFhirResourceDao myMedicationDao; + @Autowired @Qualifier("myMedicationRequestDaoDstu3") protected IFhirResourceDao myMedicationRequestDao; @Autowired @@ -202,8 +194,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myOrganizationDaoDstu3") protected IFhirResourceDao myOrganizationDao; @Autowired - @Qualifier("myTaskDaoDstu3") - protected IFhirResourceDao myTaskDao; + protected DatabaseBackedPagingProvider myPagingProvider; @Autowired @Qualifier("myPatientDaoDstu3") protected IFhirResourceDaoPatient myPatientDao; @@ -211,6 +202,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myPractitionerDaoDstu3") protected IFhirResourceDao myPractitionerDao; @Autowired + @Qualifier("myProcedureRequestDaoDstu3") + protected IFhirResourceDao myProcedureRequestDao; + @Autowired @Qualifier("myQuestionnaireDaoDstu3") protected IFhirResourceDao myQuestionnaireDao; @Autowired @@ -222,8 +216,21 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired protected IResourceTableDao myResourceTableDao; @Autowired + protected IResourceTagDao myResourceTagDao; + @Autowired + protected ISearchCoordinatorSvc mySearchCoordinatorSvc; + @Autowired protected IFulltextSearchSvc mySearchDao; @Autowired + protected ISearchDao mySearchEntityDao; + @Autowired + @Qualifier("mySearchParameterDaoDstu3") + protected IFhirResourceDao mySearchParameterDao; + @Autowired + protected ISearchParamPresenceSvc mySearchParamPresenceSvc; + @Autowired + protected ISearchParamRegistry mySearchParamRegsitry; + @Autowired protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc; @Autowired @Qualifier("myStructureDefinitionDaoDstu3") @@ -241,8 +248,15 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("mySystemProviderDstu3") protected JpaSystemProviderDstu3 mySystemProvider; @Autowired + protected ITagDefinitionDao myTagDefinitionDao; + @Autowired + @Qualifier("myTaskDaoDstu3") + protected IFhirResourceDao myTaskDao; + @Autowired protected IHapiTerminologySvc myTermSvc; @Autowired + protected PlatformTransactionManager myTransactionMgr; + @Autowired protected PlatformTransactionManager myTxManager; @Autowired @Qualifier("myJpaValidationSupportChainDstu3") @@ -250,18 +264,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired @Qualifier("myValueSetDaoDstu3") protected IFhirResourceDaoValueSet myValueSetDao; - @Autowired - protected PlatformTransactionManager myTransactionMgr; - @Autowired - protected ISearchParamPresenceSvc mySearchParamPresenceSvc; - @Autowired - protected ISearchCoordinatorSvc mySearchCoordinatorSvc; - - @After() - public void afterGrabCaches() { - ourValueSetDao = myValueSetDao; - ourJpaValidationSupportChainDstu3 = myJpaValidationSupportChainDstu3; - } @After() public void afterCleanupDao() { @@ -271,6 +273,12 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange()); } + @After() + public void afterGrabCaches() { + ourValueSetDao = myValueSetDao; + ourJpaValidationSupportChainDstu3 = myJpaValidationSupportChainDstu3; + } + @Before public void beforeCreateInterceptor() { myInterceptor = mock(IServerInterceptor.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index 748901984e5..ba1619239d1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -43,6 +43,7 @@ import org.hl7.fhir.dstu3.model.Device; import org.hl7.fhir.dstu3.model.DiagnosticReport; import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.Group; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Immunization; import org.hl7.fhir.dstu3.model.ImmunizationRecommendation; @@ -153,6 +154,67 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { assertThat(ids, contains(moId.getValue())); } + @Test + public void testEmptyChain() { + + SearchParameterMap map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().add(new ReferenceParam("subject", "04823543").setChain("identifier")))); + IBundleProvider results = myMedicationAdministrationDao.search(map); + List ids = toUnqualifiedIdValues(results); + + assertThat(ids, empty()); + } + + @Test + public void testChainWithMultipleTypePossibilities() { + + Patient sub1 = new Patient(); + sub1.setActive(true); + sub1.addIdentifier().setSystem("foo").setValue("bar"); + String sub1Id = myPatientDao.create(sub1).getId().toUnqualifiedVersionless().getValue(); + + Group sub2 = new Group(); + sub2.setActive(true); + sub2.addIdentifier().setSystem("foo").setValue("bar"); + String sub2Id = myGroupDao.create(sub2).getId().toUnqualifiedVersionless().getValue(); + + Encounter enc1 = new Encounter(); + enc1.getSubject().setReference(sub1Id); + String enc1Id = myEncounterDao.create(enc1).getId().toUnqualifiedVersionless().getValue(); + + Encounter enc2 = new Encounter(); + enc2.getSubject().setReference(sub2Id); + String enc2Id = myEncounterDao.create(enc2).getId().toUnqualifiedVersionless().getValue(); + + List ids; + SearchParameterMap map; + IBundleProvider results; + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "foo|bar").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, hasItems(enc1Id, enc2Id)); + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Patient", "foo|bar").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, hasItems(enc1Id)); + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Group", "foo|bar").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, hasItems(enc2Id)); + + map = new SearchParameterMap(); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "04823543").setChain("identifier")); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, empty()); + } + @Test public void testEverythingTimings() throws Exception { String methodName = "testEverythingTimings"; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index cfcef8ce1b4..2f3163b3541 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -155,6 +155,24 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { } } + @Test + public void testCreateReferenceToDeletedResource() { + Organization org = new Organization(); + org.setActive(true); + IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + + myOrganizationDao.delete(orgId); + + Patient p = new Patient(); + p.getManagingOrganization().setReferenceElement(orgId); + try { + myPatientDao.create(p); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/" + orgId.getIdPart() + " is deleted, specified in path: Patient.managingOrganization", e.getMessage()); + } + } + @Test public void testCreateDuplicateTagsDoesNotCauseDuplicates() { Patient p = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java index cda43e6288c..01bc711f944 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java @@ -33,11 +33,10 @@ import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.AfterClass; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; import org.mockito.ArgumentCaptor; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -123,6 +122,137 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } + @After + public void afterResetDao() { + myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit()); + } + + @Test + public void testHardMetaCapIsEnforcedOnCreate() { + myDaoConfig.setResourceMetaCountHardLimit(3); + + IIdType id; + { + Patient patient = new Patient(); + patient.getMeta().addTag().setSystem("http://foo").setCode("1"); + patient.getMeta().addTag().setSystem("http://foo").setCode("2"); + patient.getMeta().addTag().setSystem("http://foo").setCode("3"); + patient.getMeta().addTag().setSystem("http://foo").setCode("4"); + patient.setActive(true); + try { + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Resource contains 4 meta entries (tag/profile/security label), maximum is 3", e.getMessage()); + } + } + } + + @Test + public void testHardMetaCapIsEnforcedOnMetaAdd() { + myDaoConfig.setResourceMetaCountHardLimit(3); + + IIdType id; + { + Patient patient = new Patient(); + patient.setActive(true); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + Meta meta = new Meta(); + meta.addTag().setSystem("http://foo").setCode("1"); + meta.addTag().setSystem("http://foo").setCode("2"); + meta.addTag().setSystem("http://foo").setCode("3"); + meta.addTag().setSystem("http://foo").setCode("4"); + try { + myPatientDao.metaAddOperation(id, meta, null); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Resource contains 4 meta entries (tag/profile/security label), maximum is 3", e.getMessage()); + } + + } + } + + @Test + public void testDuplicateTagsOnAddTagsIgnored() { + IIdType id; + { + Patient patient = new Patient(); + patient.setActive(true); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + Meta meta = new Meta(); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val1"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val2"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val3"); + myPatientDao.metaAddOperation(id, meta, null); + + // Do a read + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(1, tl.size()); + assertEquals("http://foo", tl.get(0).getSystem()); + assertEquals("bar", tl.get(0).getCode()); + } + + } + + @Test + public void testDuplicateTagsOnUpdateIgnored() { + IIdType id; + { + Patient patient = new Patient(); + patient.setActive(true); + id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + { + Patient patient = new Patient(); + patient.setId(id); + patient.setActive(true); + patient.getMeta().addTag().setSystem("http://foo").setCode("bar").setDisplay("Val1"); + patient.getMeta().addTag().setSystem("http://foo").setCode("bar").setDisplay("Val2"); + patient.getMeta().addTag().setSystem("http://foo").setCode("bar").setDisplay("Val3"); + myPatientDao.update(patient, mySrd).getId().toUnqualifiedVersionless(); + } + + // Do a read on second version + { + Patient patient = myPatientDao.read(id, mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(1, tl.size()); + assertEquals("http://foo", tl.get(0).getSystem()); + assertEquals("bar", tl.get(0).getCode()); + } + + // Do a read on first version + { + Patient patient = myPatientDao.read(id.withVersion("1"), mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(0, tl.size()); + } + + Meta meta = new Meta(); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val1"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val2"); + meta.addTag().setSystem("http://foo").setCode("bar").setDisplay("Val3"); + myPatientDao.metaAddOperation(id.withVersion("1"), meta, null); + + // Do a read on first version + { + Patient patient = myPatientDao.read(id.withVersion("1"), mySrd); + List tl = patient.getMeta().getTag(); + assertEquals(1, tl.size()); + assertEquals("http://foo", tl.get(0).getSystem()); + assertEquals("bar", tl.get(0).getCode()); + } + + } + @Test public void testMultipleUpdatesWithNoChangesDoesNotResultInAnUpdateForDiscreteUpdates() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index 19ea6a484fb..1090bfdd9d0 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -23,7 +23,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; -import java.util.List; +import java.util.*; import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.Appointment; @@ -68,9 +68,7 @@ import org.springframework.transaction.support.TransactionTemplate; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.TagTypeEnum; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.primitive.IdDt; @@ -488,36 +486,31 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { request.addEntry().getRequest().setMethod(HTTPVerb.GET).setUrl("Patient/THIS_ID_DOESNT_EXIST"); Bundle resp = mySystemDao.transaction(mySrd, request); - assertEquals(3, resp.getEntry().size()); + assertEquals(2, resp.getEntry().size()); assertEquals(BundleType.BATCHRESPONSE, resp.getTypeElement().getValue()); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); BundleEntryResponseComponent respEntry; - // Bundle.entry[0] is operation outcome - assertEquals(OperationOutcome.class, resp.getEntry().get(0).getResource().getClass()); - assertEquals(IssueSeverity.INFORMATION, ((OperationOutcome) resp.getEntry().get(0).getResource()).getIssue().get(0).getSeverityElement().getValue()); - assertThat(((OperationOutcome) resp.getEntry().get(0).getResource()).getIssue().get(0).getDiagnostics(), startsWith("Batch completed in ")); + // Bundle.entry[0] is create response + assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + assertThat(resp.getEntry().get(0).getResponse().getLocation(), startsWith("Patient/")); - // Bundle.entry[1] is create response - assertEquals("201 Created", resp.getEntry().get(1).getResponse().getStatus()); - assertThat(resp.getEntry().get(1).getResponse().getLocation(), startsWith("Patient/")); - - // Bundle.entry[2] is failed read response - assertEquals(OperationOutcome.class, resp.getEntry().get(2).getResource().getClass()); - assertEquals(IssueSeverity.ERROR, ((OperationOutcome) resp.getEntry().get(2).getResource()).getIssue().get(0).getSeverityElement().getValue()); - assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) resp.getEntry().get(2).getResource()).getIssue().get(0).getDiagnostics()); - assertEquals("404 Not Found", resp.getEntry().get(2).getResponse().getStatus()); + // Bundle.entry[1] is failed read response + assertEquals(OperationOutcome.class, resp.getEntry().get(1).getResource().getClass()); + assertEquals(IssueSeverity.ERROR, ((OperationOutcome) resp.getEntry().get(1).getResource()).getIssue().get(0).getSeverityElement().getValue()); + assertEquals("Resource Patient/THIS_ID_DOESNT_EXIST is not known", ((OperationOutcome) resp.getEntry().get(1).getResource()).getIssue().get(0).getDiagnostics()); + assertEquals("404 Not Found", resp.getEntry().get(1).getResponse().getStatus()); // Check POST - respEntry = resp.getEntry().get(1).getResponse(); + respEntry = resp.getEntry().get(0).getResponse(); assertEquals("201 Created", respEntry.getStatus()); IdType createdId = new IdType(respEntry.getLocation()); assertEquals("Patient", createdId.getResourceType()); myPatientDao.read(createdId, mySrd); // shouldn't fail // Check GET - respEntry = resp.getEntry().get(2).getResponse(); + respEntry = resp.getEntry().get(1).getResponse(); assertThat(respEntry.getStatus(), startsWith("404")); } @@ -2081,6 +2074,21 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus()); + + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + Set values = new HashSet(); + for (ResourceTag next : myResourceTagDao.findAll()) { + if (!values.add(next.toString())) { + ourLog.info("Found duplicate tag on resource of type {}", next.getResource().getResourceType()); + ourLog.info("Tag was: {} / {}", next.getTag().getSystem(), next.getTag().getCode()); + } + } + } + }); + } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java index 96673f89ef2..a93551fc9cf 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java @@ -12,9 +12,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; +import org.junit.*; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -25,17 +23,16 @@ import ca.uhn.fhir.jpa.config.WebsocketDstu2Config; import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu2Interceptor; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.TestUtil; @@ -45,10 +42,11 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { protected static CloseableHttpClient ourHttpClient; protected static int ourPort; protected static RestfulServer ourRestServer; - private static Server ourServer; + protected static Server ourServer; protected static String ourServerBase; - private static GenericWebApplicationContext ourWebApplicationContext; + protected static GenericWebApplicationContext ourWebApplicationContext; protected static RestHookSubscriptionDstu2Interceptor ourRestHookSubscriptionInterceptor; + protected static DatabaseBackedPagingProvider ourPagingProvider; public BaseResourceProviderDstu2Test() { super(); @@ -83,7 +81,8 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { confProvider.setImplementationDescription("THIS IS THE DESC"); ourRestServer.setServerConformanceProvider(confProvider); - ourRestServer.setPagingProvider(new FifoMemoryPagingProvider(10)); + ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); + ourRestServer.setPagingProvider(ourPagingProvider); Server server = new Server(ourPort); @@ -126,6 +125,8 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { ourServer = server; } + + ourRestServer.setPagingProvider(ourPagingProvider); } protected List toIdListUnqualifiedVersionless(Bundle found) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index b3d3c83526b..89808556cad 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -5,6 +5,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInRelativeOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.not; @@ -47,11 +48,12 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; 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.Ignore; +import org.junit.*; import org.junit.Test; +import org.springframework.test.util.AopTestUtils; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; @@ -85,10 +87,7 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.IGenericClient; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringAndListParam; -import ca.uhn.fhir.rest.param.StringOrListParam; -import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -100,10 +99,27 @@ import ca.uhn.fhir.util.UrlUtil; public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu2Test.class); + private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw; + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false); + + } @Before public void beforeDisableResultReuse() { myDaoConfig.setReuseCachedSearchResultsForMillis(null); + mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc); } @AfterClass @@ -118,6 +134,92 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { myDaoConfig.setAllowMultipleDelete(true); } + + @Test + public void testPagingOverEverythingSet() throws InterruptedException { + Patient p = new Patient(); + p.setActive(true); + String pid = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + for (int i = 0; i < 20; i++) { + Observation o = new Observation(); + o.getSubject().setReference(pid); + o.addIdentifier().setSystem("foo").setValue(Integer.toString(i)); + myObservationDao.create(o); + } + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + ca.uhn.fhir.model.dstu2.resource.Bundle response = ourClient + .operation() + .onInstance(new IdDt(pid)) + .named("everything") + .withSearchParameter(Parameters.class, "_count", new NumberParam(10)) + .returnResourceType(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .useHttpGet() + .execute(); + + assertEquals(10, response.getEntry().size()); + assertEquals(null, response.getTotalElement().getValueAsString()); + assertThat(response.getLink("next").getUrl(), not(emptyString())); + + // Load page 2 + + String nextUrl = response.getLink("next").getUrl(); + response = ourClient.fetchResourceFromUrl(ca.uhn.fhir.model.dstu2.resource.Bundle.class, nextUrl); + + assertEquals(10, response.getEntry().size()); + assertThat(response.getLink("next").getUrl(), not(emptyString())); + + // Load page 3 + Thread.sleep(2000); + + nextUrl = response.getLink("next").getUrl(); + response = ourClient.fetchResourceFromUrl(ca.uhn.fhir.model.dstu2.resource.Bundle.class, nextUrl); + + assertEquals(1, response.getEntry().size()); + assertEquals(21, response.getTotal().intValue()); + assertEquals(null, response.getLink("next")); + + } + + @Test + public void testPagingOverEverythingSetWithNoPagingProvider() throws InterruptedException { + ourRestServer.setPagingProvider(null); + + Patient p = new Patient(); + p.setActive(true); + String pid = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + for (int i = 0; i < 20; i++) { + Observation o = new Observation(); + o.getSubject().setReference(pid); + o.addIdentifier().setSystem("foo").setValue(Integer.toString(i)); + myObservationDao.create(o); + } + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + ca.uhn.fhir.model.dstu2.resource.Bundle response = ourClient + .operation() + .onInstance(new IdDt(pid)) + .named("everything") + .withSearchParameter(Parameters.class, "_count", new NumberParam(10)) + .returnResourceType(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .useHttpGet() + .execute(); + + assertEquals(21, response.getEntry().size()); + assertEquals(21, response.getTotalElement().getValue().intValue()); + assertEquals(null, response.getLink("next")); + + } + + private void checkParamMissing(String paramName) throws IOException, ClientProtocolException { HttpGet get = new HttpGet(ourServerBase + "/Observation?" + paramName + ":missing=false"); CloseableHttpResponse resp = ourHttpClient.execute(get); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java new file mode 100644 index 00000000000..92df70236e4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java @@ -0,0 +1,304 @@ +package ca.uhn.fhir.jpa.provider; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.*; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; +import ca.uhn.fhir.jpa.rp.dstu2.*; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.valueset.*; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.util.TestUtil; + +public class SystemProviderTransactionSearchDstu2Test extends BaseJpaDstu2Test { + + private static RestfulServer myRestServer; + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderTransactionSearchDstu2Test.class); + private static Server ourServer; + private static String ourServerBase; + private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; + + + + + @SuppressWarnings("deprecation") + @After + public void after() { + myRestServer.setUseBrowserFriendlyContentTypes(true); + ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); + myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction()); + } + + @Before + public void before() { + mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); + ourClient.registerInterceptor(mySimpleHeaderInterceptor); + } + + @Before + public void beforeStartServer() throws Exception { + if (myRestServer == null) { + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + + QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2(); + questionnaireRp.setDao(myQuestionnaireDao); + + ObservationResourceProvider observationRp = new ObservationResourceProvider(); + observationRp.setDao(myObservationDao); + + OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); + organizationRp.setDao(myOrganizationDao); + + RestfulServer restServer = new RestfulServer(ourCtx); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + restServer.setPlainProviders(mySystemProvider); + + int myPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(myPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ourServerBase = "http://localhost:" + myPort + "/fhir/context"; + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(restServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourCtx = FhirContext.forDstu2(); + restServer.setFhirContext(ourCtx); + + ourServer.setHandler(proxyHandler); + ourServer.start(); + + ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000); + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + myRestServer = restServer; + } + + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); + myRestServer.setPagingProvider(myPagingProvider); + } + + + private List create20Patients() { + List ids = new ArrayList(); + for (int i = 0; i < 20; i++) { + Patient patient = new Patient(); + patient.setGender(AdministrativeGenderEnum.MALE); + patient.addIdentifier().setSystem("urn:foo").setValue("A"); + patient.addName().addFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1)); + String id = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue(); + ids.add(id); + } + return ids; + } + + @Test + public void testBatchWithGetHardLimitLargeSynchronous() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleTypeEnum.BATCH); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerbEnum.GET) + .setUrl("Patient?_count=5"); + + myDaoConfig.setMaximumSearchResultCountInTransaction(100); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(null, respBundle.getLink("next")); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + + @Test + public void testBatchWithGetNormalSearch() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleTypeEnum.BATCH); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerbEnum.GET) + .setUrl("Patient?_count=5&_sort=name"); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + + String nextPageLink = respBundle.getLink("next").getUrl(); + output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); + respBundle = output; + assertEquals(5, respBundle.getEntry().size()); + actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0]))); + } + + /** + * 30 searches in one batch! Whoa! + */ + @Test + public void testBatchWithManyGets() throws Exception { + List ids = create20Patients(); + + + Bundle input = new Bundle(); + input.setType(BundleTypeEnum.BATCH); + for (int i = 0; i < 30; i++) { + input + .addEntry() + .getRequest() + .setMethod(HTTPVerbEnum.GET) + .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); + } + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(30, output.getEntry().size()); + for (int i = 0; i < 30; i++) { + Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertThat(respBundle.getLink("next").getUrl(), not(nullValue())); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + } + + @Test + public void testTransactionWithGetHardLimitLargeSynchronous() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleTypeEnum.TRANSACTION); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerbEnum.GET) + .setUrl("Patient?_count=5"); + + myDaoConfig.setMaximumSearchResultCountInTransaction(100); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(null, respBundle.getLink("next")); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + + @Test + public void testTransactionWithGetNormalSearch() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleTypeEnum.TRANSACTION); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerbEnum.GET) + .setUrl("Patient?_count=5&_sort=name"); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + + String nextPageLink = respBundle.getLink("next").getUrl(); + output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); + respBundle = output; + assertEquals(5, respBundle.getEntry().size()); + actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0]))); + } + + /** + * 30 searches in one Transaction! Whoa! + */ + @Test + public void testTransactionWithManyGets() throws Exception { + List ids = create20Patients(); + + + Bundle input = new Bundle(); + input.setType(BundleTypeEnum.TRANSACTION); + for (int i = 0; i < 30; i++) { + input + .addEntry() + .getRequest() + .setMethod(HTTPVerbEnum.GET) + .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); + } + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(30, output.getEntry().size()); + for (int i = 0; i < 30; i++) { + Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertThat(respBundle.getLink("next").getUrl(), not(nullValue())); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + } + + private List toIds(Bundle theRespBundle) { + ArrayList retVal = new ArrayList(); + for (Entry next : theRespBundle.getEntry()) { + retVal.add(next.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + return retVal; + } + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java index ebd7c5c34fd..a5914cf0cf7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java @@ -56,6 +56,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { protected static String ourServerBase; private static GenericWebApplicationContext ourWebApplicationContext; private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider; + protected static DatabaseBackedPagingProvider ourPagingProvider; protected static RestHookSubscriptionDstu3Interceptor ourRestHookSubscriptionInterceptor; protected static ISearchDao mySearchEntityDao; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; @@ -95,7 +96,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { confProvider.setImplementationDescription("THIS IS THE DESC"); ourRestServer.setServerConformanceProvider(confProvider); - ourRestServer.setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); + ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); Server server = new Server(ourPort); @@ -162,6 +163,8 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { ourServer = server; } + + ourRestServer.setPagingProvider(ourPagingProvider); } protected boolean shouldLogClient() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index c4a74a2c204..0b855d95a7e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -6,11 +6,11 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInRelativeOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; @@ -47,9 +47,9 @@ import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; -import org.hl7.fhir.instance.model.Encounter.EncounterState; import org.hl7.fhir.instance.model.api.*; import org.junit.*; +import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -57,6 +57,7 @@ import com.google.common.collect.Lists; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; @@ -76,11 +77,7 @@ import ca.uhn.fhir.util.UrlUtil; public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3Test.class); - - @Before - public void beforeDisableResultReuse() { - myDaoConfig.setReuseCachedSearchResultsForMillis(null); - } + private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw; @Override @After @@ -90,6 +87,11 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false); + } @Override @@ -100,95 +102,10 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { myDaoConfig.setAllowMultipleDelete(true); } - @Test - public void testSearchWithEmptyParameter() throws Exception { - Observation obs= new Observation(); - obs.setStatus(ObservationStatus.FINAL); - obs.getCode().addCoding().setSystem("foo").setCode("bar"); - ourClient.create().resource(obs).execute(); - - testSearchWithEmptyParameter("/Observation?value-quantity="); - testSearchWithEmptyParameter("/Observation?code=bar&value-quantity="); - testSearchWithEmptyParameter("/Observation?value-date="); - testSearchWithEmptyParameter("/Observation?code=bar&value-date="); - testSearchWithEmptyParameter("/Observation?value-concept="); - testSearchWithEmptyParameter("/Observation?code=bar&value-concept="); - } - - private void testSearchWithEmptyParameter(String url) throws IOException, ClientProtocolException { - HttpGet get = new HttpGet(ourServerBase + url); - CloseableHttpResponse resp = ourHttpClient.execute(get); - try { - assertEquals(200, resp.getStatusLine().getStatusCode()); - String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); - assertEquals(1, bundle.getEntry().size()); - } finally { - IOUtils.closeQuietly(resp.getEntity().getContent()); - } - } - - @Test - public void testSearchWithMissingDate2() throws Exception { - MedicationRequest mr1 = new MedicationRequest(); - mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); - mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); - IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); - - MedicationRequest mr2 = new MedicationRequest(); - mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); - IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); - - HttpGet get = new HttpGet(ourServerBase + "/MedicationRequest?date:missing=false"); - CloseableHttpResponse resp = ourHttpClient.execute(get); - try { - assertEquals(200, resp.getStatusLine().getStatusCode()); - Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); - - List ids = toUnqualifiedVersionlessIdValues(bundle); - assertThat(ids, contains(id1.getValue())); - } finally { - IOUtils.closeQuietly(resp); - } - - } - - @Test - public void testEverythingWithOnlyPatient() { - Patient p = new Patient(); - p.setActive(true); - IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); - - myFhirCtx.getRestfulClientFactory().setSocketTimeout(300 * 1000); - - Bundle response = ourClient - .operation() - .onInstance(id) - .named("everything") - .withNoParameters(Parameters.class) - .returnResourceType(Bundle.class) - .execute(); - - assertEquals(1, response.getEntry().size()); - } - - @Test - public void testSaveAndRetrieveResourceWithExtension() { - Patient nextPatient = new Patient(); - nextPatient.setId("Patient/B"); - nextPatient - .addExtension() - .setUrl("http://foo") - .setValue(new Reference("Practitioner/A")); - - ourClient.update().resource(nextPatient).execute(); - - Patient p = ourClient.read().resource(Patient.class).withId("B").execute(); - - String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); - ourLog.info(encoded); - - assertThat(encoded, containsString("http://foo")); + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc); } private void checkParamMissing(String paramName) throws IOException, ClientProtocolException { @@ -197,7 +114,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { IOUtils.closeQuietly(resp.getEntity().getContent()); assertEquals(200, resp.getStatusLine().getStatusCode()); } - + + private ArrayList genResourcesOfType(Bundle theRes, Class theClass) { ArrayList retVal = new ArrayList(); for (BundleEntryComponent next : theRes.getEntry()) { @@ -354,8 +272,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } - - /** * See #438 */ @@ -429,7 +345,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { Validate.notNull(input); ourClient.create().resource(input).execute().getResource(); } - @Test public void testCreateConditional() { @@ -552,6 +467,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } + + @Test public void testCreateResourceConditionalComplex() throws IOException { Patient pt = new Patient(); @@ -614,6 +531,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { response.close(); } } + @Test public void testCreateResourceWithNumericId() throws IOException { @@ -1300,31 +1218,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } - // private void delete(String theResourceType, String theParamName, String theParamValue) { - // Bundle resources; - // do { - // IQuery forResource = ourClient.search().forResource(theResourceType); - // if (theParamName != null) { - // forResource = forResource.where(new StringClientParam(theParamName).matches().value(theParamValue)); - // } - // resources = forResource.execute(); - // for (IResource next : resources.toListOfResources()) { - // ourLog.info("Deleting resource: {}", next.getId()); - // ourClient.delete().resource(next).execute(); - // } - // } while (resources.size() > 0); - // } - // - // private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) - // { - // Bundle resources = ourClient.search().forResource(theResourceType).where(new - // TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute(); - // for (IResource next : resources.toListOfResources()) { - // ourLog.info("Deleting resource: {}", next.getId()); - // ourClient.delete().resource(next).execute(); - // } - // } - @Test public void testEverythingPatientOperation() throws Exception { String methodName = "testEverythingOperation"; @@ -1370,53 +1263,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { ourLog.info(ids.toString()); } - @Test - public void testSearchByLastUpdated() throws Exception { - String methodName = "testSearchByLastUpdated"; - - Patient p = new Patient(); - p.addName().setFamily(methodName+"1"); - IIdType pid1 = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); - - Thread.sleep(10); - long time1 = System.currentTimeMillis(); - Thread.sleep(10); - - Patient p2 = new Patient(); - p2.addName().setFamily(methodName+"2"); - IIdType pid2 = ourClient.create().resource(p2).execute().getId().toUnqualifiedVersionless(); - - HttpGet get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=lt" + new InstantType(new Date(time1)).getValueAsString()); - CloseableHttpResponse response = ourHttpClient.execute(get); - try { - assertEquals(200, response.getStatusLine().getStatusCode()); - String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - IOUtils.closeQuietly(response.getEntity().getContent()); - ourLog.info(output); - List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); - ourLog.info(ids.toString()); - assertThat(ids, containsInAnyOrder(pid1)); - } finally { - response.close(); - } - - get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=gt" + new InstantType(new Date(time1)).getValueAsString()); - response = ourHttpClient.execute(get); - try { - assertEquals(200, response.getStatusLine().getStatusCode()); - String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - IOUtils.closeQuietly(response.getEntity().getContent()); - ourLog.info(output); - List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); - ourLog.info(ids.toString()); - assertThat(ids, containsInAnyOrder(pid2)); - } finally { - response.close(); - } - - } - - @Test public void testEverythingPatientType() throws Exception { String methodName = "testEverythingPatientType"; @@ -1679,6 +1525,50 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { assertEquals(77, ids.size()); } + @Test + public void testEverythingWithOnlyPatient() { + Patient p = new Patient(); + p.setActive(true); + IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + myFhirCtx.getRestfulClientFactory().setSocketTimeout(300 * 1000); + + Bundle response = ourClient + .operation() + .onInstance(id) + .named("everything") + .withNoParameters(Parameters.class) + .returnResourceType(Bundle.class) + .execute(); + + assertEquals(1, response.getEntry().size()); + } + + // private void delete(String theResourceType, String theParamName, String theParamValue) { + // Bundle resources; + // do { + // IQuery forResource = ourClient.search().forResource(theResourceType); + // if (theParamName != null) { + // forResource = forResource.where(new StringClientParam(theParamName).matches().value(theParamValue)); + // } + // resources = forResource.execute(); + // for (IResource next : resources.toListOfResources()) { + // ourLog.info("Deleting resource: {}", next.getId()); + // ourClient.delete().resource(next).execute(); + // } + // } while (resources.size() > 0); + // } + // + // private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) + // { + // Bundle resources = ourClient.search().forResource(theResourceType).where(new + // TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute(); + // for (IResource next : resources.toListOfResources()) { + // ourLog.info("Deleting resource: {}", next.getId()); + // ourClient.delete().resource(next).execute(); + // } + // } + @SuppressWarnings("unused") @Test public void testFullTextSearch() throws RuntimeException, Exception { @@ -1727,7 +1617,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { response.close(); } } - + + @Test public void testHasParameter() throws Exception { IIdType pid0; @@ -2074,6 +1965,91 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } + @Test + public void testPagingOverEverythingSet() throws InterruptedException { + Patient p = new Patient(); + p.setActive(true); + String pid = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + for (int i = 0; i < 20; i++) { + Observation o = new Observation(); + o.getSubject().setReference(pid); + o.addIdentifier().setSystem("foo").setValue(Integer.toString(i)); + myObservationDao.create(o); + } + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + Bundle response = ourClient + .operation() + .onInstance(new IdType(pid)) + .named("everything") + .withSearchParameter(Parameters.class, "_count", new NumberParam(10)) + .returnResourceType(Bundle.class) + .useHttpGet() + .execute(); + + assertEquals(10, response.getEntry().size()); + assertEquals(null, response.getTotalElement().getValueAsString()); + assertThat(response.getLink("next").getUrl(), not(emptyString())); + + // Load page 2 + + String nextUrl = response.getLink("next").getUrl(); + response = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + + assertEquals(10, response.getEntry().size()); + assertEquals(null, response.getTotalElement().getValueAsString()); + assertThat(response.getLink("next").getUrl(), not(emptyString())); + + // Load page 3 + Thread.sleep(2000); + + nextUrl = response.getLink("next").getUrl(); + response = ourClient.fetchResourceFromUrl(Bundle.class, nextUrl); + + assertEquals(1, response.getEntry().size()); + assertEquals(21, response.getTotal()); + assertEquals(null, response.getLink("next")); + + } + + @Test + public void testPagingOverEverythingSetWithNoPagingProvider() throws InterruptedException { + ourRestServer.setPagingProvider(null); + + Patient p = new Patient(); + p.setActive(true); + String pid = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + for (int i = 0; i < 20; i++) { + Observation o = new Observation(); + o.getSubject().setReference(pid); + o.addIdentifier().setSystem("foo").setValue(Integer.toString(i)); + myObservationDao.create(o); + } + + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(50); + mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); + mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + + Bundle response = ourClient + .operation() + .onInstance(new IdType(pid)) + .named("everything") + .withSearchParameter(Parameters.class, "_count", new NumberParam(10)) + .returnResourceType(Bundle.class) + .useHttpGet() + .execute(); + + assertEquals(21, response.getEntry().size()); + assertEquals(21, response.getTotalElement().getValue().intValue()); + assertEquals(null, response.getLink("next")); + + } + @Test public void testPatchUsingJsonPatch() throws Exception { String methodName = "testPatchUsingJsonPatch"; @@ -2297,6 +2273,25 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { assertEquals("
HELLO WORLD
", actual.getText().getDiv().getValueAsString()); } + @Test + public void testSaveAndRetrieveResourceWithExtension() { + Patient nextPatient = new Patient(); + nextPatient.setId("Patient/B"); + nextPatient + .addExtension() + .setUrl("http://foo") + .setValue(new Reference("Practitioner/A")); + + ourClient.update().resource(nextPatient).execute(); + + Patient p = ourClient.read().resource(Patient.class).withId("B").execute(); + + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); + ourLog.info(encoded); + + assertThat(encoded, containsString("http://foo")); + } + @Test public void testSaveAndRetrieveWithContained() { Patient p1 = new Patient(); @@ -2517,6 +2512,52 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } + @Test + public void testSearchByLastUpdated() throws Exception { + String methodName = "testSearchByLastUpdated"; + + Patient p = new Patient(); + p.addName().setFamily(methodName+"1"); + IIdType pid1 = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); + + Thread.sleep(10); + long time1 = System.currentTimeMillis(); + Thread.sleep(10); + + Patient p2 = new Patient(); + p2.addName().setFamily(methodName+"2"); + IIdType pid2 = ourClient.create().resource(p2).execute().getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=lt" + new InstantType(new Date(time1)).getValueAsString()); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(pid1)); + } finally { + response.close(); + } + + get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=gt" + new InstantType(new Date(time1)).getValueAsString()); + response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + List ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output)); + ourLog.info(ids.toString()); + assertThat(ids, containsInAnyOrder(pid2)); + } finally { + response.close(); + } + + } + @Test public void testSearchByReferenceIds() { Organization o1 = new Organization(); @@ -2587,6 +2628,28 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } + @Test + public void testSearchInvalidParam() throws Exception { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("0"); + patient.addName().setFamily("testSearchWithMixedParams").addGiven("Joe"); + myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + // should be subject._id + HttpGet httpPost = new HttpGet(ourServerBase + "/Observation?subject.id=FOO"); + + CloseableHttpResponse resp = ourHttpClient.execute(httpPost); + try { + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(respString); + assertThat(respString, containsString("Invalid parameter chain: subject.id")); + assertEquals(400, resp.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + ourLog.info("Outgoing post: {}", httpPost); + } + @Test public void testSearchLastUpdatedParamRp() throws InterruptedException { String methodName = "testSearchLastUpdatedParamRp"; @@ -3047,6 +3110,34 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { assertEquals(2, response.getEntry().size()); } + @Test + public void testSearchWithEmptyParameter() throws Exception { + Observation obs= new Observation(); + obs.setStatus(ObservationStatus.FINAL); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + ourClient.create().resource(obs).execute(); + + testSearchWithEmptyParameter("/Observation?value-quantity="); + testSearchWithEmptyParameter("/Observation?code=bar&value-quantity="); + testSearchWithEmptyParameter("/Observation?value-date="); + testSearchWithEmptyParameter("/Observation?code=bar&value-date="); + testSearchWithEmptyParameter("/Observation?value-concept="); + testSearchWithEmptyParameter("/Observation?code=bar&value-concept="); + } + + private void testSearchWithEmptyParameter(String url) throws IOException, ClientProtocolException { + HttpGet get = new HttpGet(ourServerBase + url); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); + assertEquals(1, bundle.getEntry().size()); + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } + @Test public void testSearchWithInclude() throws Exception { Organization org = new Organization(); @@ -3211,25 +3302,29 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } @Test - public void testSearchInvalidParam() throws Exception { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("0"); - patient.addName().setFamily("testSearchWithMixedParams").addGiven("Joe"); - myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + public void testSearchWithMissingDate2() throws Exception { + MedicationRequest mr1 = new MedicationRequest(); + mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); + IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); - // should be subject._id - HttpGet httpPost = new HttpGet(ourServerBase + "/Observation?subject.id=FOO"); + MedicationRequest mr2 = new MedicationRequest(); + mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); + IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); - CloseableHttpResponse resp = ourHttpClient.execute(httpPost); + HttpGet get = new HttpGet(ourServerBase + "/MedicationRequest?date:missing=false"); + CloseableHttpResponse resp = ourHttpClient.execute(get); try { - String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); - ourLog.info(respString); - assertThat(respString, containsString("Invalid parameter chain: subject.id")); - assertEquals(400, resp.getStatusLine().getStatusCode()); + assertEquals(200, resp.getStatusLine().getStatusCode()); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); + + List ids = toUnqualifiedVersionlessIdValues(bundle); + assertThat(ids, contains(id1.getValue())); + assertThat(ids, not(contains(id2.getValue()))); } finally { - IOUtils.closeQuietly(resp.getEntity().getContent()); + IOUtils.closeQuietly(resp); } - ourLog.info("Outgoing post: {}", httpPost); + } /** diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java index af577fef5db..1e0d52bba4f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java @@ -58,6 +58,7 @@ import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.rp.dstu3.ObservationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; @@ -202,7 +203,6 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test { organizationRp.setDao(myOrganizationDao); RestfulServer restServer = new RestfulServer(ourCtx); - restServer.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(10)); restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); restServer.setPlainProviders(mySystemProvider); @@ -237,6 +237,7 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test { } myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); + myRestServer.setPagingProvider(myPagingProvider); } @Before diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java new file mode 100644 index 00000000000..2ae03965067 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java @@ -0,0 +1,358 @@ +package ca.uhn.fhir.jpa.provider.dstu3; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +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.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Bundle.*; +import org.hl7.fhir.dstu3.model.DecimalType; +import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.OperationDefinition; +import org.hl7.fhir.dstu3.model.OperationOutcome; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; +import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; +import ca.uhn.fhir.jpa.rp.dstu3.ObservationResourceProvider; +import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider; +import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.ResultSeverityEnum; + +public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test { + + private static RestfulServer myRestServer; + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static CloseableHttpClient ourHttpClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderTransactionSearchDstu3Test.class); + private static Server ourServer; + private static String ourServerBase; + private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; + + + + @SuppressWarnings("deprecation") + @After + public void after() { + myRestServer.setUseBrowserFriendlyContentTypes(true); + ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); + myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction()); + } + + @Before + public void before() { + mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); + ourClient.registerInterceptor(mySimpleHeaderInterceptor); + } + + @Before + public void beforeStartServer() throws Exception { + if (myRestServer == null) { + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + + QuestionnaireResourceProviderDstu3 questionnaireRp = new QuestionnaireResourceProviderDstu3(); + questionnaireRp.setDao(myQuestionnaireDao); + + ObservationResourceProvider observationRp = new ObservationResourceProvider(); + observationRp.setDao(myObservationDao); + + OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); + organizationRp.setDao(myOrganizationDao); + + RestfulServer restServer = new RestfulServer(ourCtx); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + restServer.setPlainProviders(mySystemProvider); + + int myPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(myPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ourServerBase = "http://localhost:" + myPort + "/fhir/context"; + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(restServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourCtx = FhirContext.forDstu3(); + restServer.setFhirContext(ourCtx); + + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourHttpClient = builder.build(); + + ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000); + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.setLogRequestAndResponse(true); + myRestServer = restServer; + } + + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); + myRestServer.setPagingProvider(myPagingProvider); + } + + + private List create20Patients() { + List ids = new ArrayList(); + for (int i = 0; i < 20; i++) { + Patient patient = new Patient(); + patient.setGender(AdministrativeGender.MALE); + patient.addIdentifier().setSystem("urn:foo").setValue("A"); + patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1)); + String id = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue(); + ids.add(id); + } + return ids; + } + + @Test + public void testBatchWithGetHardLimitLargeSynchronous() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.BATCH); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5"); + + myDaoConfig.setMaximumSearchResultCountInTransaction(100); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(null, respBundle.getLink("next")); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + + @Test + public void testBatchWithGetNormalSearch() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.BATCH); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&_sort=name"); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + + String nextPageLink = respBundle.getLink("next").getUrl(); + output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); + respBundle = output; + assertEquals(5, respBundle.getEntry().size()); + actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0]))); + } + + /** + * 30 searches in one batch! Whoa! + */ + @Test + public void testBatchWithManyGets() throws Exception { + List ids = create20Patients(); + + + Bundle input = new Bundle(); + input.setType(BundleType.BATCH); + for (int i = 0; i < 30; i++) { + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); + } + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(30, output.getEntry().size()); + for (int i = 0; i < 30; i++) { + Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertThat(respBundle.getLink("next").getUrl(), not(nullValue())); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + } + + @Test + public void testTransactionWithGetHardLimitLargeSynchronous() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5"); + + myDaoConfig.setMaximumSearchResultCountInTransaction(100); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(null, respBundle.getLink("next")); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + + @Test + public void testTransactionWithGetNormalSearch() throws Exception { + List ids = create20Patients(); + + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&_sort=name"); + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(1, output.getEntry().size()); + Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); + assertEquals(5, respBundle.getEntry().size()); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + + String nextPageLink = respBundle.getLink("next").getUrl(); + output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); + respBundle = output; + assertEquals(5, respBundle.getEntry().size()); + actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(5, 10).toArray(new String[0]))); + } + + /** + * 30 searches in one Transaction! Whoa! + */ + @Test + public void testTransactionWithManyGets() throws Exception { + List ids = create20Patients(); + + + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + for (int i = 0; i < 30; i++) { + input + .addEntry() + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); + } + + Bundle output = ourClient.transaction().withBundle(input).execute(); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals(30, output.getEntry().size()); + for (int i = 0; i < 30; i++) { + Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); + assertEquals(5, respBundle.getEntry().size()); + assertThat(respBundle.getLink("next").getUrl(), not(nullValue())); + List actualIds = toIds(respBundle); + assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); + } + } + + private List toIds(Bundle theRespBundle) { + ArrayList retVal = new ArrayList(); + for (BundleEntryComponent next : theRespBundle.getEntry()) { + retVal.add(next.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + return retVal; + } + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java index e23311281b3..531205ab31c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java @@ -42,8 +42,6 @@ public class PagingMultinodeProviderDstu3Test extends BaseResourceProviderDstu3T myDaoConfig.setAllowMultipleDelete(true); mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc); -// mySearchCoordinatorSvcRaw = (SearchCoordinatorSvcImpl)mySearchCoordinatorSvc; - } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java index 3d7b5b18bac..b8a5fc32084 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java @@ -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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java index d78a716f4d6..b53003fe470 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java @@ -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(); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java index 124b4ec0eb4..f74520d7776 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java @@ -28,37 +28,6 @@ import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; public interface IRestfulServerDefaults { - - /** - * Returns the list of interceptors registered against this server - */ - List 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 Accept header in the request, or a _pretty - * parameter in the request URL. - *

- * The default is false - *

- * - * @return Returns the default pretty print setting - */ - boolean isDefaultPrettyPrint(); - - /** - * @return Returns the server support for ETags (will not be null). 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 @@ -74,6 +43,41 @@ public interface IRestfulServerDefaults { */ EncodingEnum getDefaultResponseEncoding(); + /** + * @return Returns the server support for ETags (will not be null). Default is + * {@link RestfulServer#DEFAULT_ETAG_SUPPORT} + */ + 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 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 Accept header in the request, or a _pretty + * parameter in the request URL. + *

+ * The default is false + *

+ * + * @return Returns the default pretty print setting + */ + boolean isDefaultPrettyPrint(); + /** * @return If true the server will use browser friendly content-types (instead of standard FHIR ones) * when it detects that the request is coming from a browser @@ -81,4 +85,5 @@ public interface IRestfulServerDefaults { */ boolean isUseBrowserFriendlyContentTypes(); + } diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java index 95280e5d37e..6a4373c0884 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.rest.server.provider.dstu2; +import static org.apache.commons.lang3.StringUtils.isBlank; /* * #%L * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0) @@ -10,7 +11,7 @@ package ca.uhn.fhir.rest.server.provider.dstu2; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -62,6 +63,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ResourceReferenceInfo; public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Dstu2BundleFactory.class); private Bundle myBundle; private FhirContext myContext; @@ -223,7 +225,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { entry.getRequest().getMethodElement().setValueAsString(httpVerb.getCode()); } populateBundleEntryFullUrl(next, entry); - + BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next); if (searchMode != null) { entry.getSearch().getModeElement().setValue(searchMode.getCode()); @@ -257,7 +259,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType theLastUpdated) { myBase = theServerBase; - + if (myBundle.getId().isEmpty()) { myBundle.setId(UUID.randomUUID().toString()); } @@ -302,7 +304,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) { myBase = theServerBase; - + int numToReturn; String searchId = null; List resourceList; @@ -327,7 +329,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { if (numTotalResults != null) { numToReturn = Math.min(numToReturn, numTotalResults - theOffset); } - + if (numToReturn > 0) { resourceList = theResult.getResources(theOffset, numToReturn + theOffset); } else { @@ -340,7 +342,9 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { } else { if (numTotalResults == null || numTotalResults > numToReturn) { searchId = pagingProvider.storeResultList(theResult); - Validate.notNull(searchId, "Paging provider returned null searchId"); + if (isBlank(searchId)) { + ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults); + } } } } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java index e4a57134cbc..6ad431c94ca 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java @@ -1,5 +1,6 @@ package org.hl7.fhir.dstu3.hapi.rest.server; +import static org.apache.commons.lang3.StringUtils.isBlank; /* * #%L * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0) @@ -44,7 +45,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ResourceReferenceInfo; public class Dstu3BundleFactory implements IVersionSpecificBundleFactory { - + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Dstu3BundleFactory.class); private Bundle myBundle; private FhirContext myContext; private String myBase; @@ -336,7 +337,9 @@ public class Dstu3BundleFactory implements IVersionSpecificBundleFactory { } else { if (numTotalResults == null || numTotalResults > numToReturn) { searchId = pagingProvider.storeResultList(theResult); - Validate.notNull(searchId, "Paging provider returned null searchId"); + if (isBlank(searchId)) { + ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults); + } } } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 2e7e6ff63d8..209a7012522 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -59,7 +59,7 @@ Add configuration to JPA server DaoConfig that allows a maximum number of search results to be specified. Queries will never return more than this number, which can be good for avoiding accidental - performance problems in situations where lare queries should not be + performance problems in situations where large queries should not be needed @@ -76,6 +76,64 @@ actually notify listeners of the first 10 subscriptions. Thanks to Jeff Chung for the pull request! + + JPA search now uses hibernate ScrollableResults instead of plain JPA List. This + should improve performance over large search results. + + + JPA servers with no paging provider configured, or with a paging provider other than + DatabaseBackedPagingProvider will load all results in a single pass and keep them + in memory. Using this setup is not a good idea unless you know for sure that you + will never have very large queries since it means that all results will be loaded into + memory, but there are valid reasons to need this and it will perform better than + paging to the database in that case. This fix also resolves a NullPointerException + when performing an $everything search. Thanks to Kamal Othman for reporting! + + + Correct an issue in JPA server on Postgres where searches with a long search URL + were not able to be automatically purged from the database after they were scheduled + for deletion. Thanks to Ravi Kuchi for reporting! + + + Add an optional and configurable hard limit on the total number of meta items + (tags, profiles, and security labels) on an individual resource. The default + is 1000. + + + When executing a search (HTTP GET) as a nested operation in in a transaction or + batch operation, the search now returns a normal page of results with a link to + the next page, like any other search would. Previously the search would return + a small number of results with no paging performed, so this change brings transaction + and batch processing in line with other types of search. + + + JPA server no longer returns an OperationOutcome resource as the first resource + in the Bundle for a response to a batch operation. This behaviour was previously + present, but was not specified in the FHIR specification so it caused confusion and + was inconsistent with behaviour in other servers. + + + Fix a regression in HAPI FHIR 2.5 JPA server where executing a search in a + transaction or batch operation caused an exception. Thanks to Ravi Kuchi for + reporting! + + + Correct an issue when processing transactions in JPA server where updates and + creates to resources with tags caused the tags to be created twice in the + database. These duplicates were utomatically filtered upon read so this issue + was not user-visible, but it coule occasionally lead to performance issues + if a resource containing multiple tags was updated many times via + transactions. + + + JPA server should not allow creation of resources that have a reference to + a resource ID that previously existed but is now deleted. Thanks to Artem + Sopin for reporting! + + + JpaConformanceProvider now has a configuration setting to enable and + disable adding resource counts to the server metadata. +