diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java index a5d1dda2329..08b22da947e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java @@ -138,6 +138,7 @@ public class Constants { * Used in paging links */ public static final String PARAM_BUNDLETYPE = "_bundletype"; + public static final String PARAM_FILTER = "_filter"; public static final String PARAM_CONTENT = "_content"; public static final String PARAM_COUNT = "_count"; public static final String PARAM_DELETE = "_delete"; 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 4cc40737de0..53f4506efff 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 @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao; +import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; @@ -149,6 +150,7 @@ public class DaoConfig { * EXPERIMENTAL - Do not use in production! Do not change default of {@code false}! */ private boolean myPreExpandValueSetsExperimental = false; + private boolean myFilterParameterEnabled = false; /** * Constructor @@ -972,7 +974,7 @@ public class DaoConfig { * and other FHIR features may not behave as expected when referential integrity is not * preserved. Use this feature with caution. *

- * @see ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor + * @see CascadingDeleteInterceptor */ public boolean isEnforceReferentialIntegrityOnDelete() { return myEnforceReferentialIntegrityOnDelete; @@ -986,7 +988,7 @@ public class DaoConfig { * and other FHIR features may not behave as expected when referential integrity is not * preserved. Use this feature with caution. *

- * @see ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor + * @see CascadingDeleteInterceptor */ public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferentialIntegrityOnDelete) { myEnforceReferentialIntegrityOnDelete = theEnforceReferentialIntegrityOnDelete; @@ -1632,6 +1634,28 @@ public class DaoConfig { myPreExpandValueSetsExperimental = thePreExpandValueSetsExperimental; } + /** + * If set to true the _filter search parameter will be enabled on this server. Note that _filter + * is very powerful, but also potentially dangerous as it can allow a user to create a query for which there + * are no indexes or efficient query plans for the database to leverage while performing the query. + * As a result, this feature is recommended only for servers where the querying applications are known in advance + * and a database administrator can properly tune the database for the resulting queries. + */ + public boolean isFilterParameterEnabled() { + return myFilterParameterEnabled; + } + + /** + * If set to true the _filter search parameter will be enabled on this server. Note that _filter + * is very powerful, but also potentially dangerous as it can allow a user to create a query for which there + * are no indexes or efficient query plans for the database to leverage while performing the query. + * As a result, this feature is recommended only for servers where the querying applications are known in advance + * and a database administrator can properly tune the database for the resulting queries. + */ + public void setFilterParameterEnabled(boolean theFilterParameterEnabled) { + myFilterParameterEnabled = theFilterParameterEnabled; + } + public enum IndexEnabledEnum { ENABLED, DISABLED 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 f6f4e2db1c4..d1a755e4228 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 @@ -43,7 +43,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im super(); } - private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequest) { + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { paramMap.setCount(theCount.getValue()); @@ -70,21 +70,21 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im } @Override - public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { + public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { // Notify interceptors ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getResourceName(), null); notifyInterceptors(RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE, requestDetails); - return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); + return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails); } @Override - public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { + public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { // Notify interceptors ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getResourceName(), null); notifyInterceptors(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, requestDetails); - return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); + return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java index d2797eb9544..06b282b28dd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java @@ -1,5 +1,14 @@ package ca.uhn.fhir.jpa.dao; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + import javax.servlet.http.HttpServletRequest; /* @@ -21,19 +30,12 @@ import javax.servlet.http.HttpServletRequest; * limitations under the License. * #L% */ -import org.hl7.fhir.instance.model.api.*; - -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringAndListParam; public interface IFhirResourceDaoPatient extends IFhirResourceDao { - IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdate, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails); + IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdate, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails); - IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSortSpec, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails); + IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSortSpec, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java index 8debddb3fcc..239e8b28f6c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java @@ -28,12 +28,13 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.DateRangeParam; import org.hl7.fhir.instance.model.api.IBaseResource; -import javax.persistence.EntityManager; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; +import javax.persistence.EntityManager; + public interface ISearchBuilder { IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest); 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 575f379983c..124e3668ee1 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 @@ -186,25 +186,44 @@ public class SearchBuilder implements ISearchBuilder { } - private void addPredicateDate(String theResourceName, String theParamName, List theList) { + private Predicate addPredicateDate(String theResourceName, + String theParamName, + List theList) { + + return addPredicateDate(theResourceName, + theParamName, + theList, + null); + } + + private Predicate addPredicateDate(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation) { Join join = createJoin(JoinEnum.DATE, theParamName); if (theList.get(0).getMissing() != null) { Boolean missing = theList.get(0).getMissing(); addPredicateParamMissing(theResourceName, theParamName, missing, join); - return; + return null; } List codePredicates = new ArrayList<>(); for (IQueryParameterType nextOr : theList) { - Predicate p = createPredicateDate(nextOr, theResourceName, theParamName, myBuilder, join); + IQueryParameterType params = nextOr; + Predicate p = createPredicateDate(params, + theResourceName, + theParamName, + myBuilder, + join, + operation); codePredicates.add(p); } Predicate orPredicates = myBuilder.or(toArray(codePredicates)); myPredicates.add(orPredicates); - + return orPredicates; } private void addPredicateHas(List> theHasParameters, RequestDetails theRequest) { @@ -268,7 +287,13 @@ public class SearchBuilder implements ISearchBuilder { } } - private void addPredicateLanguage(List> theList) { + private Predicate addPredicateLanguage(List> theList) { + return addPredicateLanguage(theList, + null); + } + + private Predicate addPredicateLanguage(List> theList, + SearchFilterParser.CompareOperation operation) { for (List nextList : theList) { Set values = new HashSet<>(); @@ -288,19 +313,43 @@ public class SearchBuilder implements ISearchBuilder { continue; } - Predicate predicate = myResourceTableRoot.get("myLanguage").as(String.class).in(values); + Predicate predicate = null; + if ((operation == null) || + (operation == SearchFilterParser.CompareOperation.eq)) { + predicate = myResourceTableRoot.get("myLanguage").as(String.class).in(values); + } else if (operation == SearchFilterParser.CompareOperation.ne) { + predicate = myResourceTableRoot.get("myLanguage").as(String.class).in(values).not(); + } else { + throw new InvalidRequestException("Unsupported operator specified in language query, only \"eq\" and \"ne\" are supported"); + } myPredicates.add(predicate); + if (operation != null) { + return predicate; + } } + return null; } - private void addPredicateNumber(String theResourceName, String theParamName, List theList) { + private Predicate addPredicateNumber(String theResourceName, + String theParamName, + List theList) { + return addPredicateNumber(theResourceName, + theParamName, + theList, + null); + } + + private Predicate addPredicateNumber(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation) { Join join = createJoin(JoinEnum.NUMBER, theParamName); if (theList.get(0).getMissing() != null) { addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); - return; + return null; } List codePredicates = new ArrayList<>(); @@ -316,6 +365,23 @@ public class SearchBuilder implements ISearchBuilder { final Expression fromObj = join.get("myValue"); ParamPrefixEnum prefix = ObjectUtils.defaultIfNull(param.getPrefix(), ParamPrefixEnum.EQUAL); + if (operation == SearchFilterParser.CompareOperation.ne) { + prefix = ParamPrefixEnum.NOT_EQUAL; + } else if (operation == SearchFilterParser.CompareOperation.lt) { + prefix = ParamPrefixEnum.LESSTHAN; + } else if (operation == SearchFilterParser.CompareOperation.le) { + prefix = ParamPrefixEnum.LESSTHAN_OR_EQUALS; + } else if (operation == SearchFilterParser.CompareOperation.gt) { + prefix = ParamPrefixEnum.GREATERTHAN; + } else if (operation == SearchFilterParser.CompareOperation.ge) { + prefix = ParamPrefixEnum.GREATERTHAN_OR_EQUALS; + } else if (operation == SearchFilterParser.CompareOperation.eq) { + prefix = ParamPrefixEnum.EQUAL; + } else if (operation != null) { + throw new IllegalArgumentException("Invalid operator specified for number type"); + } + + String invalidMessageName = "invalidNumberPrefix"; Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myBuilder, nextOr, prefix, value, fromObj, invalidMessageName); @@ -328,7 +394,9 @@ public class SearchBuilder implements ISearchBuilder { } - myPredicates.add(myBuilder.or(toArray(codePredicates))); + Predicate predicate = myBuilder.or(toArray(codePredicates)); + myPredicates.add(predicate); + return predicate; } private void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) { @@ -355,33 +423,73 @@ public class SearchBuilder implements ISearchBuilder { myPredicates.add(myBuilder.equal(theJoin.get("myMissing"), theMissing)); } - private void addPredicateQuantity(String theResourceName, String theParamName, List theList) { + private Predicate addPredicateQuantity(String theResourceName, + String theParamName, + List theList) { + return addPredicateQuantity(theResourceName, + theParamName, + theList, + null); + } + + private Predicate addPredicateQuantity(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation) { Join join = createJoin(JoinEnum.QUANTITY, theParamName); if (theList.get(0).getMissing() != null) { addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); - return; + return null; } - List codePredicates = new ArrayList<>(); + List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { - Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theParamName, myBuilder, join); + Predicate singleCode = createPredicateQuantity(nextOr, + theResourceName, + theParamName, + myBuilder, + join, + operation); codePredicates.add(singleCode); } - myPredicates.add(myBuilder.or(toArray(codePredicates))); + Predicate retVal = myBuilder.or(toArray(codePredicates)); + myPredicates.add(retVal); + return retVal; + } + + private Predicate addPredicateReference(String theResourceName, + String theParamName, + List theList, + RequestDetails theRequest) { + return addPredicateReference(theResourceName, + theParamName, + theList, + null, theRequest); } /** * Add reference predicate to the current search */ - private void addPredicateReference(String theResourceName, String theParamName, List theList, RequestDetails theRequest) { + private Predicate addPredicateReference(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation, + RequestDetails theRequest) { + assert theParamName.contains(".") == false; + if ((operation != null) && + (operation != SearchFilterParser.CompareOperation.eq) && + (operation != SearchFilterParser.CompareOperation.ne)) { + throw new InvalidRequestException("Invalid operator specified for reference predicate. Supported operators for reference predicate are \"eq\" and \"ne\"."); + } + if (theList.get(0).getMissing() != null) { addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing()); - return; + return null; } Join join = createJoin(JoinEnum.REFERENCE, theParamName); @@ -420,8 +528,7 @@ public class SearchBuilder implements ISearchBuilder { * Handle chained search, e.g. Patient?organization.name=Kwik-e-mart */ - addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest); - return; + return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest); } @@ -451,16 +558,19 @@ public class SearchBuilder implements ISearchBuilder { } if (codePredicates.size() > 0) { - myPredicates.add(myBuilder.or(toArray(codePredicates))); + Predicate predicate = myBuilder.or(toArray(codePredicates)); + myPredicates.add(predicate); + return predicate; } else { // Add a predicate that will never match Predicate pidPredicate = join.get("myTargetResourcePid").in(-1L); myPredicates.clear(); myPredicates.add(pidPredicate); + return pidPredicate; } } - private void addPredicateReferenceWithChain(String theResourceName, String theParamName, List theList, Join theJoin, List theCodePredicates, ReferenceParam theRef, RequestDetails theRequest) { + private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List theList, Join theJoin, List theCodePredicates, ReferenceParam theRef, RequestDetails theRequest) { final List> resourceTypes; String resourceId; if (!theRef.getValue().matches("[a-zA-Z]+/.*")) { @@ -597,7 +707,9 @@ public class SearchBuilder implements ISearchBuilder { throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theRef.getChain())); } - myPredicates.add(myBuilder.or(toArray(theCodePredicates))); + Predicate predicate = myBuilder.or(toArray(theCodePredicates)); + myPredicates.add(predicate); + return predicate; } private Subquery createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List theOrValues, RequestDetails theRequest) { @@ -660,7 +772,13 @@ public class SearchBuilder implements ISearchBuilder { return chainValue; } - private void addPredicateResourceId(List> theValues, RequestDetails theRequest) { + private Predicate addPredicateResourceId(List> theValues, RequestDetails theRequest) { + return addPredicateResourceId(theValues, + null, theRequest); + } + + private Predicate addPredicateResourceId(List> theValues, + SearchFilterParser.CompareOperation operation, RequestDetails theRequest) { for (List nextValue : theValues) { Set orPids = new HashSet<>(); for (IQueryParameterType next : nextValue) { @@ -681,35 +799,66 @@ public class SearchBuilder implements ISearchBuilder { } } + Predicate nextPredicate = null; if (orPids.size() > 0) { - Predicate nextPredicate = myResourceTableRoot.get("myId").as(Long.class).in(orPids); + if ((operation == null) || + (operation == SearchFilterParser.CompareOperation.eq)) { + nextPredicate = myResourceTableRoot.get("myId").as(Long.class).in(orPids); + } else if (operation == SearchFilterParser.CompareOperation.ne) { + nextPredicate = myResourceTableRoot.get("myId").as(Long.class).in(orPids).not(); + } else { + throw new InvalidRequestException("Unsupported operator specified in resource ID query, only \"eq\" and \"ne\" are supported"); + } myPredicates.add(nextPredicate); } else { // This will never match - Predicate nextPredicate = myBuilder.equal(myResourceTableRoot.get("myId").as(Long.class), -1); + nextPredicate = myBuilder.equal(myResourceTableRoot.get("myId").as(Long.class), -1); myPredicates.add(nextPredicate); } + if (operation != null) { + return nextPredicate; + } } + return null; } - private void addPredicateString(String theResourceName, String theParamName, List theList) { + private Predicate addPredicateString(String theResourceName, + String theParamName, + List theList) { + return addPredicateString(theResourceName, + theParamName, + theList, + SearchFilterParser.CompareOperation.eq); + } + + private Predicate addPredicateString(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation) { Join join = createJoin(JoinEnum.STRING, theParamName); if (theList.get(0).getMissing() != null) { addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); - return; + return null; } - List codePredicates = new ArrayList<>(); + List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { - Predicate singleCode = createPredicateString(nextOr, theResourceName, theParamName, myBuilder, join); + IQueryParameterType theParameter = nextOr; + Predicate singleCode = createPredicateString(theParameter, + theResourceName, + theParamName, + myBuilder, + join, + operation); codePredicates.add(singleCode); } - myPredicates.add(myBuilder.or(toArray(codePredicates))); - + Predicate retVal = myBuilder.or(toArray(codePredicates)); + myPredicates.add(retVal); + return retVal; } private void addPredicateTag(List> theList, String theParamName) { @@ -840,12 +989,24 @@ public class SearchBuilder implements ISearchBuilder { } - private void addPredicateToken(String theResourceName, String theParamName, List theList) { + private Predicate addPredicateToken(String theResourceName, + String theParamName, + List theList) { + return addPredicateToken(theResourceName, + theParamName, + theList, + null); + } + + private Predicate addPredicateToken(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation) { if (theList.get(0).getMissing() != null) { Join join = createJoin(JoinEnum.TOKEN, theParamName); addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); - return; + return null; } List codePredicates = new ArrayList<>(); @@ -864,24 +1025,38 @@ public class SearchBuilder implements ISearchBuilder { } if (tokens.isEmpty()) { - return; + return null; } Join join = createJoin(JoinEnum.TOKEN, theParamName); - List singleCode = createPredicateToken(tokens, theResourceName, theParamName, myBuilder, join); + Collection singleCode = createPredicateToken(tokens, theResourceName, theParamName, myBuilder, join, operation); + assert singleCode != null; codePredicates.addAll(singleCode); Predicate spPredicate = myBuilder.or(toArray(codePredicates)); myPredicates.add(spPredicate); + return spPredicate; } - private void addPredicateUri(String theResourceName, String theParamName, List theList) { + private Predicate addPredicateUri(String theResourceName, + String theParamName, + List theList) { + return addPredicateUri(theResourceName, + theParamName, + theList, + SearchFilterParser.CompareOperation.eq); + } + + private Predicate addPredicateUri(String theResourceName, + String theParamName, + List theList, + SearchFilterParser.CompareOperation operation) { Join join = createJoin(JoinEnum.URI, theParamName); if (theList.get(0).getMissing() != null) { addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); - return; + return null; } List codePredicates = new ArrayList<>(); @@ -934,18 +1109,42 @@ public class SearchBuilder implements ISearchBuilder { codePredicates.add(hashAndUriPredicate); } else { - if (myDontUseHashesForSearch) { - Predicate predicate = myBuilder.equal(join.get("myUri").as(String.class), value); codePredicates.add(predicate); - } else { - long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value); - Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri); - codePredicates.add(hashPredicate); + Predicate uriPredicate = null; + if (operation == null || operation == SearchFilterParser.CompareOperation.eq) { + long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value); + Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri); + codePredicates.add(hashPredicate); + } else if (operation == SearchFilterParser.CompareOperation.ne) { + uriPredicate = myBuilder.notEqual(join.get("myUri").as(String.class), value); + } else if (operation == SearchFilterParser.CompareOperation.co) { + uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value)); + } else if (operation == SearchFilterParser.CompareOperation.gt) { + uriPredicate = myBuilder.greaterThan(join.get("myUri").as(String.class), value); + } else if (operation == SearchFilterParser.CompareOperation.lt) { + uriPredicate = myBuilder.lessThan(join.get("myUri").as(String.class), value); + } else if (operation == SearchFilterParser.CompareOperation.ge) { + uriPredicate = myBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value); + } else if (operation == SearchFilterParser.CompareOperation.le) { + uriPredicate = myBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value); + } else if (operation == SearchFilterParser.CompareOperation.sw) { + uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); + } else if (operation == SearchFilterParser.CompareOperation.ew) { + uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value)); + } else { + throw new IllegalArgumentException(String.format("Unsupported operator specified in _filter clause, %s", + operation.toString())); + } + if (uriPredicate != null) { + long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName); + Predicate hashIdentityPredicate = myBuilder.equal(join.get("myHashIdentity"), hashIdentity); + codePredicates.add(myBuilder.and(hashIdentityPredicate, uriPredicate)); + } } } @@ -962,11 +1161,17 @@ public class SearchBuilder implements ISearchBuilder { if (codePredicates.isEmpty()) { Predicate predicate = myBuilder.isNull(join.get("myMissing").as(String.class)); myPredicates.add(predicate); - return; + return null; } Predicate orPredicate = myBuilder.or(toArray(codePredicates)); - myPredicates.add(orPredicate); + + Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, + theParamName, + join, + orPredicate); + myPredicates.add(outerPredicate); + return outerPredicate; } private Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From theFrom, Predicate thePredicate) { @@ -993,7 +1198,7 @@ public class SearchBuilder implements ISearchBuilder { case TOKEN: { From tokenJoin = theRoot.join("myParamsToken", JoinType.INNER); List tokens = Collections.singletonList(leftValue); - List tokenPredicates = createPredicateToken(tokens, theResourceName, theParam.getName(), myBuilder, tokenJoin); + Collection tokenPredicates = createPredicateToken(tokens, theResourceName, theParam.getName(), myBuilder, tokenJoin); retVal = myBuilder.and(tokenPredicates.toArray(new Predicate[0])); break; } @@ -1057,20 +1262,45 @@ public class SearchBuilder implements ISearchBuilder { return (Join) join; } - private Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, From theFrom) { + private Predicate createPredicateDate(IQueryParameterType theParam, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, + From theFrom) { + return createPredicateDate(theParam, + theResourceName, + theParamName, + theBuilder, + theFrom, + null); + } + + private Predicate createPredicateDate(IQueryParameterType theParam, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, + From theFrom, + SearchFilterParser.CompareOperation operation) { + Predicate p; if (theParam instanceof DateParam) { DateParam date = (DateParam) theParam; if (!date.isEmpty()) { DateRangeParam range = new DateRangeParam(date); - p = createPredicateDateFromRange(theBuilder, theFrom, range); + p = createPredicateDateFromRange(theBuilder, + theFrom, + range, + operation); } else { // TODO: handle missing date param? p = null; } } else if (theParam instanceof DateRangeParam) { DateRangeParam range = (DateRangeParam) theParam; - p = createPredicateDateFromRange(theBuilder, theFrom, range); + p = createPredicateDateFromRange(theBuilder, + theFrom, + range, + operation); } else { throw new IllegalArgumentException("Invalid token type: " + theParam.getClass()); } @@ -1078,30 +1308,76 @@ public class SearchBuilder implements ISearchBuilder { return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, p); } - private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder, From theFrom, DateRangeParam theRange) { + private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder, + From theFrom, + DateRangeParam theRange, + SearchFilterParser.CompareOperation operation) { Date lowerBound = theRange.getLowerBoundAsInstant(); Date upperBound = theRange.getUpperBoundAsInstant(); - + Predicate lt = null; + Predicate gt = null; Predicate lb = null; - if (lowerBound != null) { - Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); - Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBound); - if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) { - lb = gt; - } else { - lb = theBuilder.or(gt, lt); - } - } - Predicate ub = null; - if (upperBound != null) { - Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBound); - Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); - if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) { - ub = lt; - } else { - ub = theBuilder.or(gt, lt); + + if (operation == SearchFilterParser.CompareOperation.lt) { + if (lowerBound == null) { + throw new InvalidRequestException("lowerBound value not correctly specified for compare operation"); } + lb = theBuilder.lessThan(theFrom.get("myValueLow"), lowerBound); + } else if (operation == SearchFilterParser.CompareOperation.le) { + if (upperBound == null) { + throw new InvalidRequestException("upperBound value not correctly specified for compare operation"); + } + lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); + } else if (operation == SearchFilterParser.CompareOperation.gt) { + if (upperBound == null) { + throw new InvalidRequestException("upperBound value not correctly specified for compare operation"); + } + lb = theBuilder.greaterThan(theFrom.get("myValueHigh"), upperBound); + } else if (operation == SearchFilterParser.CompareOperation.ge) { + if (lowerBound == null) { + throw new InvalidRequestException("lowerBound value not correctly specified for compare operation"); + } + lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); + } else if (operation == SearchFilterParser.CompareOperation.ne) { + if ((lowerBound == null) || + (upperBound == null)) { + throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation"); + } + /*Predicate*/ + lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); + /*Predicate*/ + gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); + lb = theBuilder.or(lt, + gt); + } else if ((operation == SearchFilterParser.CompareOperation.eq) || + (operation == null)) { + if (lowerBound != null) { + /*Predicate*/ + gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); + /*Predicate*/ + lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBound); + if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) { + lb = gt; + } else { + lb = theBuilder.or(gt, lt); + } + } + + if (upperBound != null) { + /*Predicate*/ + gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBound); + /*Predicate*/ + lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); + if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) { + ub = lt; + } else { + ub = theBuilder.or(gt, lt); + } + } + } else { + throw new InvalidRequestException(String.format("Unsupported operator specified, operator=%s", + operation.name())); } ourLog.trace("Date range is {} - {}", lowerBound, upperBound); @@ -1115,9 +1391,37 @@ public class SearchBuilder implements ISearchBuilder { } } - private Predicate createPredicateNumeric(String theResourceName, String theParamName, From theFrom, CriteriaBuilder builder, - IQueryParameterType theParam, ParamPrefixEnum thePrefix, BigDecimal theValue, final Expression thePath, + private Predicate createPredicateNumeric(String theResourceName, + String theParamName, + From theFrom, + CriteriaBuilder builder, + IQueryParameterType theParam, + ParamPrefixEnum thePrefix, + BigDecimal theValue, + final Expression thePath, String invalidMessageName) { + return createPredicateNumeric(theResourceName, + theParamName, + theFrom, + builder, + theParam, + thePrefix, + theValue, + thePath, + invalidMessageName, + null); + } + + private Predicate createPredicateNumeric(String theResourceName, + String theParamName, + From theFrom, + CriteriaBuilder builder, + IQueryParameterType theParam, + ParamPrefixEnum thePrefix, + BigDecimal theValue, + final Expression thePath, + String invalidMessageName, + SearchFilterParser.CompareOperation operation) { Predicate num; switch (thePrefix) { case GREATERTHAN: @@ -1162,27 +1466,64 @@ public class SearchBuilder implements ISearchBuilder { if (theParamName == null) { return num; } - return num; + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num); } - private Predicate createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, + private Predicate createPredicateQuantity(IQueryParameterType theParam, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, From theFrom) { + return createPredicateQuantity(theParam, + theResourceName, + theParamName, + theBuilder, + theFrom, + null); + } + + private Predicate createPredicateQuantity(IQueryParameterType theParam, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, + From theFrom, + SearchFilterParser.CompareOperation operation) { String systemValue; String unitsValue; - ParamPrefixEnum cmpValue; + ParamPrefixEnum cmpValue = null; BigDecimal valueValue; + if (operation == SearchFilterParser.CompareOperation.ne) { + cmpValue = ParamPrefixEnum.NOT_EQUAL; + } else if (operation == SearchFilterParser.CompareOperation.lt) { + cmpValue = ParamPrefixEnum.LESSTHAN; + } else if (operation == SearchFilterParser.CompareOperation.le) { + cmpValue = ParamPrefixEnum.LESSTHAN_OR_EQUALS; + } else if (operation == SearchFilterParser.CompareOperation.gt) { + cmpValue = ParamPrefixEnum.GREATERTHAN; + } else if (operation == SearchFilterParser.CompareOperation.ge) { + cmpValue = ParamPrefixEnum.GREATERTHAN_OR_EQUALS; + } else if (operation == SearchFilterParser.CompareOperation.eq) { + cmpValue = ParamPrefixEnum.EQUAL; + } else if (operation != null) { + throw new IllegalArgumentException("Invalid operator specified for quantity type"); + } + if (theParam instanceof BaseQuantityDt) { BaseQuantityDt param = (BaseQuantityDt) theParam; systemValue = param.getSystemElement().getValueAsString(); unitsValue = param.getUnitsElement().getValueAsString(); - cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString()); + if (operation == null) { + cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString()); + } valueValue = param.getValueElement().getValue(); } else if (theParam instanceof QuantityParam) { QuantityParam param = (QuantityParam) theParam; systemValue = param.getSystem(); unitsValue = param.getUnits(); - cmpValue = param.getPrefix(); + if (operation == null) { + cmpValue = param.getPrefix(); + } valueValue = param.getValue(); } else { throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass()); @@ -1240,8 +1581,25 @@ public class SearchBuilder implements ISearchBuilder { return theBuilder.and(hashPredicate, numericPredicate); } - private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder, + private Predicate createPredicateString(IQueryParameterType theParameter, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, From theFrom) { + return createPredicateString(theParameter, + theResourceName, + theParamName, + theBuilder, + theFrom, + null); + } + + private Predicate createPredicateString(IQueryParameterType theParameter, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, + From theFrom, + SearchFilterParser.CompareOperation operation) { String rawSearchTerm; if (theParameter instanceof TokenParam) { TokenParam id = (TokenParam) theParameter; @@ -1316,11 +1674,37 @@ public class SearchBuilder implements ISearchBuilder { likeExpression = createLeftMatchLikeExpression(normalizedString); } - Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); - Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); - Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); - return theBuilder.and(hashCode, singleCode); + Predicate predicate; + if ((operation == null) || + (operation == SearchFilterParser.CompareOperation.eq) || + (operation == SearchFilterParser.CompareOperation.co) || + (operation == SearchFilterParser.CompareOperation.sw) || + (operation == SearchFilterParser.CompareOperation.ew)) { + Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); + Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); + Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); + predicate = theBuilder.and(hashCode, singleCode); + } else if (operation == SearchFilterParser.CompareOperation.ne) { + Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + } else if (operation == SearchFilterParser.CompareOperation.gt) { + Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + } else if (operation == SearchFilterParser.CompareOperation.lt) { + Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + } else if (operation == SearchFilterParser.CompareOperation.ge) { + Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + } else if (operation == SearchFilterParser.CompareOperation.le) { + Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + } else { + throw new IllegalArgumentException("Unknown operation type: " + operation); + } + + return predicate; } } @@ -1341,8 +1725,26 @@ public class SearchBuilder implements ISearchBuilder { return theBuilder.or(toArray(orPredicates)); } - private List createPredicateToken(Collection theParameters, String theResourceName, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + private Collection createPredicateToken(Collection theParameters, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, + From theFrom) { + return createPredicateToken( + theParameters, + theResourceName, + theParamName, + theBuilder, + theFrom, + null); + } + + private Collection createPredicateToken(Collection theParameters, + String theResourceName, + String theParamName, + CriteriaBuilder theBuilder, + From theFrom, + SearchFilterParser.CompareOperation operation) { final List codes = new ArrayList<>(); TokenParamModifier modifier = null; @@ -2250,6 +2652,127 @@ public class SearchBuilder implements ISearchBuilder { theParams.clean(); } + private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter, + String theResourceName, RequestDetails theRequest) { + + RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, + theFilter.getParamPath().getName()); + + if (searchParam.getName().equals(IAnyResource.SP_RES_ID)) { + if (searchParam.getParamType() == RestSearchParameterTypeEnum.TOKEN) { + TokenParam param = new TokenParam(); + param.setValueAsQueryToken(null, + null, + null, + theFilter.getValue()); + return addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), + theFilter.getOperation(), theRequest); + } else { + throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search"); + } + } else if (searchParam.getName().equals(IAnyResource.SP_RES_LANGUAGE)) { + if (searchParam.getParamType() == RestSearchParameterTypeEnum.STRING) { + return addPredicateLanguage(Collections.singletonList(Collections.singletonList(new StringParam(theFilter.getValue()))), + theFilter.getOperation()); + } else { + throw new InvalidRequestException("Unexpected search parameter type encountered, expected string type for language search"); + } + } +// else if ((searchParam.getName().equals(Constants.PARAM_TAG)) || +// (searchParam.equals(Constants.PARAM_SECURITY))) { +// TokenParam param = new TokenParam(); +// param.setValueAsQueryToken(null, +// null, +// null, +// ((SearchFilterParser.FilterParameter) theFilter).getValue()); +// return addPredicateTag(Collections.singletonList(Collections.singletonList(param)), +// searchParam.getName()); +// } +// else if (searchParam.equals(Constants.PARAM_PROFILE)) { +// addPredicateTag(Collections.singletonList(Collections.singletonList(new UriParam(((SearchFilterParser.FilterParameter) theFilter).getValue()))), +// searchParam.getName()); +// } + else if (searchParam != null) { + RestSearchParameterTypeEnum typeEnum = searchParam.getParamType(); + if (typeEnum == RestSearchParameterTypeEnum.URI) { + return addPredicateUri(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(new UriParam(theFilter.getValue())), + theFilter.getOperation()); + } else if (typeEnum == RestSearchParameterTypeEnum.STRING) { + return addPredicateString(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(new StringParam(theFilter.getValue())), + theFilter.getOperation()); + } else if (typeEnum == RestSearchParameterTypeEnum.DATE) { + return addPredicateDate(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(new DateParam(theFilter.getValue())), + theFilter.getOperation()); + } else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) { + return addPredicateNumber(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(new NumberParam(theFilter.getValue())), + theFilter.getOperation()); + } else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) { + return addPredicateReference(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(new ReferenceParam(theFilter.getParamPath().getName(), + (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() + : null, + theFilter.getValue())), + theFilter.getOperation(), theRequest); + } else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) { + return addPredicateQuantity(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(new QuantityParam(theFilter.getValue())), + theFilter.getOperation()); + } else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) { + throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses"); + } else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) { + TokenParam param = new TokenParam(); + param.setValueAsQueryToken(null, + null, + null, + theFilter.getValue()); + return addPredicateToken(theResourceName, + theFilter.getParamPath().getName(), + Collections.singletonList(param), + theFilter.getOperation()); + } + } else { + throw new InvalidRequestException("Invalid search parameter specified, " + theFilter.getParamPath().getName() + ", for resource type " + theResourceName); + } + return null; + } + + private Predicate processFilter(SearchFilterParser.Filter filter, + String theResourceName, RequestDetails theRequest) { + + if (filter instanceof SearchFilterParser.FilterParameter) { + return processFilterParameter((SearchFilterParser.FilterParameter) filter, + theResourceName, theRequest); + } else if (filter instanceof SearchFilterParser.FilterLogical) { + // Left side + Predicate leftPredicate = processFilter(((SearchFilterParser.FilterLogical) filter).getFilter1(), + theResourceName, theRequest); + + // Right side + Predicate rightPredicate = processFilter(((SearchFilterParser.FilterLogical) filter).getFilter2(), + theResourceName, theRequest); + + if (((SearchFilterParser.FilterLogical) filter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { + return myBuilder.and(leftPredicate, rightPredicate); + } else if (((SearchFilterParser.FilterLogical) filter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { + return myBuilder.or(leftPredicate, rightPredicate); + } + } else if (filter instanceof SearchFilterParser.FilterParameterGroup) { + return processFilter(((SearchFilterParser.FilterParameterGroup) filter).getContained(), + theResourceName, theRequest); + } + return null; + } + private void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams, RequestDetails theRequest) { if (theAndOrParams.isEmpty()) { @@ -2325,6 +2848,35 @@ public class SearchBuilder implements ISearchBuilder { } else { if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) { // These are handled later + } else if (Constants.PARAM_FILTER.equals(theParamName)) { + // Parse the predicates enumerated in the _filter separated by AND or OR... + if (theAndOrParams.get(0).get(0) instanceof StringParam) { + String filterString = ((StringParam) theAndOrParams.get(0).get(0)).getValue(); + SearchFilterParser.Filter filter; + try { + filter = SearchFilterParser.parse(filterString); + } catch (SearchFilterParser.FilterSyntaxException theE) { + throw new InvalidRequestException("Error parsing _filter syntax: " + theE.getMessage()); + } + if (filter != null) { + + if (!myDaoConfig.isFilterParameterEnabled()) { + throw new InvalidRequestException(Constants.PARAM_FILTER + " parameter is disabled on this server"); + } + + // TODO: we clear the predicates below because the filter builds up + // its own collection of predicates. It'd probably be good at some + // point to do something more fancy... + ArrayList holdPredicates = new ArrayList<>(myPredicates); + + Predicate filterPredicate = processFilter(filter, theResourceName, theRequest); + myPredicates.clear(); + myPredicates.addAll(holdPredicates); + myPredicates.add(filterPredicate); + } + } + + } else { throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName); } @@ -2721,6 +3273,12 @@ public class SearchBuilder implements ISearchBuilder { } } + private static String createRightMatchLikeExpression(String likeExpression) { + return "%" + likeExpression.replace("%", "[%]"); + } + + + /** * Figures out the tolerance for a search. For example, if the user is searching for 4.00, this method * returns 0.005 because we shold actually match values which are @@ -2785,6 +3343,8 @@ public class SearchBuilder implements ISearchBuilder { return query.getResultList(); } + + private static Predicate[] toArray(List thePredicates) { return thePredicates.toArray(new Predicate[0]); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchFilterParser.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchFilterParser.java new file mode 100644 index 00000000000..aa24ece6629 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchFilterParser.java @@ -0,0 +1,597 @@ +package ca.uhn.fhir.jpa.dao; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SearchFilterParser { + + private static final String XML_DATE_PATTERN = "[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|([+\\-])((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"; + private static final Pattern XML_DATE_MATCHER = Pattern.compile(XML_DATE_PATTERN); + private static final List CODES_CompareOperation = Arrays.asList("eq", "ne", "co", "sw", "ew", "gt", "lt", "ge", "le", "pr", "po", "ss", "sb", "in", "re"); + private static final List CODES_LogicalOperation = Arrays.asList("and", "or", "not"); + private String original = null; + private int cursor; + + private boolean isDate(String s) { + Matcher m = XML_DATE_MATCHER.matcher(s); + return m.matches(); + } + + private FilterLexType peek() throws FilterSyntaxException { + + FilterLexType result; + while ((cursor < original.length()) && (original.charAt(cursor) == ' ')) { + cursor++; + } + + if (cursor >= original.length()) { + result = FilterLexType.fsltEnded; + } else { + if (((original.charAt(cursor) >= 'a') && (original.charAt(cursor) <= 'z')) || + ((original.charAt(cursor) >= 'A') && (original.charAt(cursor) <= 'Z')) || + (original.charAt(cursor) == '_')) { + result = FilterLexType.fsltName; + } else if ((original.charAt(cursor) >= '0') && (original.charAt(cursor) <= '9')) { + result = FilterLexType.fsltNumber; + } else if (original.charAt(cursor) == '"') { + result = FilterLexType.fsltString; + } else if (original.charAt(cursor) == '.') { + result = FilterLexType.fsltDot; + } else if (original.charAt(cursor) == '(') { + result = FilterLexType.fsltOpen; + } else if (original.charAt(cursor) == ')') { + result = FilterLexType.fsltClose; + } else if (original.charAt(cursor) == '[') { + result = FilterLexType.fsltOpenSq; + } else if (original.charAt(cursor) == ']') { + result = FilterLexType.fsltCloseSq; + } else { + throw new FilterSyntaxException(String.format("Unknown Character \"%s\" at %d", + peekCh(), + cursor)); + } + } + return result; + } + + private String peekCh() { + + String result; + if (cursor > original.length()) { + result = "[end!]"; + } else { + result = original.substring(cursor, cursor + 1); + } + return result; + } + + private String consumeName() { + + String result; + int i = cursor; + do { + i++; + } while ((i <= original.length() - 1) && + (((original.charAt(i) >= 'a') && (original.charAt(i) <= 'z')) || + ((original.charAt(i) >= 'A') && (original.charAt(i) <= 'Z')) || + ((original.charAt(i) >= '0') && (original.charAt(i) <= '9')) || + (original.charAt(i) == '-') || + (original.charAt(i) == '_') || + (original.charAt(i) == ':'))); + + result = original.substring(cursor, + i/* - cursor*/); + cursor = i; + return result; + } + + private String consumeToken() { + + String result; + int i = cursor; + do { + i++; + } while ((i <= original.length() - 1) && + (original.charAt(i) > 32) && + (!StringUtils.isWhitespace(original.substring(i, i + 1))) && + (original.charAt(i) != ')') && + (original.charAt(i) != ']')); + result = original.substring(cursor, + i/* - cursor*/); + cursor = i; + return result; + } + + private String consumeNumberOrDate() { + + String result; + int i = cursor; + do { + i++; + } while ((i <= original.length() - 1) && + (((original.charAt(i) >= '0') && (original.charAt(i) <= '9')) || + (original.charAt(i) == '.') || + (original.charAt(i) == '-') || + (original.charAt(i) == ':') || + (original.charAt(i) == '+') || + (original.charAt(i) == 'T'))); + result = original.substring(cursor, + i/* - cursor*/); + cursor = i; + return result; + } + + private String consumeString() throws FilterSyntaxException { + +// int l = 0; + cursor++; + StringBuilder str = new StringBuilder(original.length()); +// setLength(result, length(original)); // can't be longer than that + while ((cursor <= original.length()) && (original.charAt(cursor) != '"')) { +// l++; + if (original.charAt(cursor) != '\\') { + str.append(original.charAt(cursor)); +// str.setCharAt(l, original.charAt(cursor)); + } else { + cursor++; + if (original.charAt(cursor) == '"') { + str.append('"'); +// str.setCharAt(l, '"'); + } else if (original.charAt(cursor) == 't') { + str.append('\t'); +// str.setCharAt(l, '\t'); + } else if (original.charAt(cursor) == 'r') { + str.append('\r'); +// str.setCharAt(l, '\r'); + } else if (original.charAt(cursor) == 'n') { + str.append('\n'); +// str.setCharAt(l, '\n'); + } else { + throw new FilterSyntaxException(String.format("Unknown escape sequence at %d", + cursor)); + } + } + cursor++; + } +// SetLength(result, l); + if ((cursor > original.length()) || (original.charAt(cursor) != '"')) { + throw new FilterSyntaxException(String.format("Problem with string termination at %d", + cursor)); + } + + if (str.length() == 0) { + throw new FilterSyntaxException(String.format("Problem with string at %d cannot be empty", + cursor)); + } + + cursor++; + return str.toString(); + } + + private Filter parse() throws FilterSyntaxException { + + Filter result = parseOpen(); + if (cursor < original.length()) { + throw new FilterSyntaxException(String.format("Expression did not terminate at %d", + cursor)); + } + return result; + } + + private Filter parseOpen() throws FilterSyntaxException { + + Filter result; + String s; + FilterParameterGroup grp; + if (peek() == FilterLexType.fsltOpen) { + cursor++; + grp = new FilterParameterGroup(); + grp.setContained(parseOpen()); + if (peek() != FilterLexType.fsltClose) { + throw new FilterSyntaxException(String.format("Expected ')' at %d but found %s", + cursor, + peekCh())); + } + cursor++; + FilterLexType lexType = peek(); + if (lexType == FilterLexType.fsltName) { + result = parseLogical(grp); + } else if ((lexType == FilterLexType.fsltEnded) || (lexType == FilterLexType.fsltClose) || (lexType == FilterLexType.fsltCloseSq)) { + result = grp; + } else { + throw new FilterSyntaxException(String.format("Unexpected Character %s at %d", + peekCh(), + cursor)); + } + } else { + s = consumeName(); + if (s.compareToIgnoreCase("not") == 0) { + result = parseLogical(null); + } else { + result = parseParameter(s); + } + } + return result; + } + + private Filter parseLogical(Filter filter) throws FilterSyntaxException { + + Filter result = null; + String s; + FilterLogical logical; + if (filter == null) { + s = "not"; + } else { + s = consumeName(); + if ((!s.equals("or")) && (!s.equals("and")) && (!s.equals("not"))) { + throw new FilterSyntaxException(String.format("Unexpected Name %s at %d", + s, + cursor)); + } + + logical = new FilterLogical(); + logical.setFilter1(filter); + if (s.compareToIgnoreCase("or") == 0) { + logical.setOperation(FilterLogicalOperation.or); + } else if (s.compareToIgnoreCase("not") == 0) { + logical.setOperation(FilterLogicalOperation.not); + } else { + logical.setOperation(FilterLogicalOperation.and); + } + + logical.setFilter2(parseOpen()); + result = logical; + } + return result; + } + + private FilterParameterPath parsePath(String name) throws FilterSyntaxException { + + FilterParameterPath result = new FilterParameterPath(); + result.setName(name); + if (peek() == FilterLexType.fsltOpenSq) { + cursor++; + result.setFilter(parseOpen()); + if (peek() != FilterLexType.fsltCloseSq) { + throw new FilterSyntaxException(String.format("Expected ']' at %d but found %c", + cursor, + peekCh())); + } + cursor++; + } + + if (peek() == FilterLexType.fsltDot) { + cursor++; + if (peek() != FilterLexType.fsltName) { + throw new FilterSyntaxException(String.format("Unexpected Character %c at %d", + peekCh(), + cursor)); + } + result.setNext(parsePath(consumeName())); + } else if (result.getFilter() != null) { + throw new FilterSyntaxException(String.format("Expected '.' at %d but found %c", + cursor, + peekCh())); + } + + return result; + } + + private Filter parseParameter(String name) throws FilterSyntaxException { + + Filter result; + String s; + FilterParameter filter = new FilterParameter(); + + // 1. the path + filter.setParamPath(parsePath(name)); + + if (peek() != FilterLexType.fsltName) { + throw new FilterSyntaxException(String.format("Unexpected Character %s at %d", + peekCh(), + cursor)); + } + s = consumeName(); + int index = CODES_CompareOperation.indexOf(s); + if (index == -1) { + throw new FilterSyntaxException(String.format("Unknown operation %s at %d", + s, + cursor)); + } + filter.setOperation(CompareOperation.values()[index]); + + FilterLexType lexType = peek(); + if (lexType == FilterLexType.fsltName) { + filter.setValue(consumeToken()); + filter.setValueType(FilterValueType.token); + } else if (lexType == FilterLexType.fsltNumber) { + filter.setValue(consumeNumberOrDate()); + filter.setValueType(FilterValueType.numberOrDate); + } else if (lexType == FilterLexType.fsltString) { + filter.setValue(consumeString()); + filter.setValueType(FilterValueType.string); + } else { + throw new FilterSyntaxException(String.format("Unexpected Character %s at %d", + peekCh(), + cursor)); + } + + // check operation / value type results + if (filter.getOperation() == CompareOperation.pr) { + if ((filter.getValue().compareToIgnoreCase("true") != 0) && + (filter.getValue().compareToIgnoreCase("false") != 0)) { + throw new FilterSyntaxException(String.format("Value %s not valid for operation %s at %d", + filter.getValue(), + CODES_CompareOperation.get(filter.getOperation().ordinal()), + cursor)); + } + } else if (filter.getOperation() == CompareOperation.po) { + if (!isDate(filter.getValue())) { + throw new FilterSyntaxException(String.format("Value %s not valid for operation %s at %d", + filter.getValue(), + CODES_CompareOperation.get(filter.getOperation().ordinal()), + cursor)); + } + } + + lexType = peek(); + if (lexType == FilterLexType.fsltName) { + result = parseLogical(filter); + } else if ((lexType == FilterLexType.fsltEnded) || (lexType == FilterLexType.fsltClose) || (lexType == FilterLexType.fsltCloseSq)) { + result = filter; + } else { + throw new FilterSyntaxException(String.format("Unexpected Character %s at %d", + peekCh(), + cursor)); + } + return result; + } + + public enum CompareOperation { + eq, + ne, + co, + sw, + ew, + gt, + lt, + ge, + le, + pr, + po, + ss, + sb, + in, + re + } + + public enum FilterLogicalOperation { + and, + or, + not + } + + public enum FilterItemType { + parameter, + logical, + parameterGroup + } + + public enum FilterValueType { + token, + string, + numberOrDate + } + + public enum FilterLexType { + fsltEnded, + fsltName, + fsltString, + fsltNumber, + fsltDot, + fsltOpen, + fsltClose, + fsltOpenSq, + fsltCloseSq + } + + abstract public static class Filter { + + private FilterItemType itemType; + + public FilterItemType getFilterItemType() { + return itemType; + } + } + + public static class FilterParameterPath { + + private String FName; + private Filter FFilter; + private FilterParameterPath FNext; + + public String getName() { + + return FName; + } + + public void setName(String value) { + + FName = value; + } + + public Filter getFilter() { + + return FFilter; + } + + public void setFilter(Filter value) { + + FFilter = value; + } + + public FilterParameterPath getNext() { + + return FNext; + } + + public void setNext(FilterParameterPath value) { + + FNext = value; + } + + @Override + public String toString() { + String result; + if (getFilter() != null) { + result = getName() + "[" + getFilter().toString() + "]"; + } else { + result = getName(); + } + if (getNext() != null) { + result += "." + getNext().toString(); + } + return result; + } + } + + public static class FilterParameterGroup extends Filter { + + private Filter FContained; + + public Filter getContained() { + + return FContained; + } + + public void setContained(Filter value) { + + FContained = value; + } + + + @Override + public String toString() { + + return "(" + FContained.toString() + ")"; + } + } + + public static class FilterParameter extends Filter { + + private FilterParameterPath FParamPath; + private CompareOperation FOperation; + private String FValue; + private FilterValueType FValueType; + + FilterParameterPath getParamPath() { + + return FParamPath; + } + + void setParamPath(FilterParameterPath value) { + + FParamPath = value; + } + + + public CompareOperation getOperation() { + + return FOperation; + } + + public void setOperation(CompareOperation value) { + + FOperation = value; + } + + public String getValue() { + + return FValue; + } + + public void setValue(String value) { + + FValue = value; + } + + public FilterValueType getValueType() { + + return FValueType; + } + + public void setValueType(FilterValueType FValueType) { + + this.FValueType = FValueType; + } + + @Override + public String toString() { + if (FValueType == FilterValueType.string) { + return getParamPath().toString() + " " + CODES_CompareOperation.get(getOperation().ordinal()) + " \"" + getValue() + "\""; + } else { + return getParamPath().toString() + " " + CODES_CompareOperation.get(getOperation().ordinal()) + " " + getValue(); + } + } + } + + public static class FilterLogical extends Filter { + + private Filter FFilter1; + private FilterLogicalOperation FOperation; + private Filter FFilter2; + + + Filter getFilter1() { + + return FFilter1; + } + + void setFilter1(Filter FFilter1) { + + this.FFilter1 = FFilter1; + } + + public FilterLogicalOperation getOperation() { + + return FOperation; + } + + public void setOperation(FilterLogicalOperation FOperation) { + + this.FOperation = FOperation; + } + + Filter getFilter2() { + + return FFilter2; + } + + void setFilter2(Filter FFilter2) { + + this.FFilter2 = FFilter2; + } + + @Override + public String toString() { + return FFilter1.toString() + " " + CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter2.toString(); + } + } + + static class FilterSyntaxException extends Exception { + FilterSyntaxException(String theMessage) { + super(theMessage); + } + } + + static public Filter parse(String expression) throws FilterSyntaxException { + SearchFilterParser parser = new SearchFilterParser(); + parser.original = expression; + parser.cursor = 0; + return parser.parse(); + } +} 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 f79ed8d2418..87c433c8161 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 @@ -41,7 +41,7 @@ import ca.uhn.fhir.rest.param.*; public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3implements IFhirResourceDaoPatient { - private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequest) { + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { paramMap.setCount(theCount.getValue()); @@ -68,13 +68,13 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3im } @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, theRequestDetails); + public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { + return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, 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, theRequestDetails); + public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { + return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java index d081bcf4a9b..d3460f7fa57 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java @@ -41,7 +41,7 @@ import ca.uhn.fhir.rest.param.*; public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4implements IFhirResourceDaoPatient { - private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequest) { + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { paramMap.setCount(theCount.getValue()); @@ -68,13 +68,13 @@ public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4implemen } @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, theRequestDetails); + public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { + return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, 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, theRequestDetails); + public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { + return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java index 476501f2c80..49405daf922 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java @@ -68,12 +68,12 @@ public class FhirResourceDaoPatientR5 extends FhirResourceDaoR5 impleme } @Override - public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) { + public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { 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) { + public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) { return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java index c4527298ce6..8f93558c07f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java @@ -66,6 +66,10 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu @OperationParam(name = Constants.PARAM_TEXT, min=0, max=OperationParam.MAX_UNLIMITED) List theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -74,7 +78,7 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu startRequest(theServletRequest); try { - return ((IFhirResourceDaoPatient) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } @@ -104,6 +108,10 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu @OperationParam(name = Constants.PARAM_TEXT, min=0, max=OperationParam.MAX_UNLIMITED) List theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -112,7 +120,7 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu startRequest(theServletRequest); try { - return ((IFhirResourceDaoPatient) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java index acc254b5f01..90ad56cf0a5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; @@ -23,8 +25,6 @@ import org.hl7.fhir.dstu3.model.UnsignedIntType; import java.util.List; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - /* * #%L * HAPI FHIR JPA Server @@ -74,6 +74,10 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu @OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED) List theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -82,7 +86,7 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu startRequest(theServletRequest); try { - return ((IFhirResourceDaoPatient) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } @@ -112,6 +116,10 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu @OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED) List theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -120,7 +128,7 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu startRequest(theServletRequest); try { - return ((IFhirResourceDaoPatient) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java index dab5a522e2d..b291c2aa827 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; @@ -23,8 +25,6 @@ import org.hl7.fhir.r4.model.UnsignedIntType; import java.util.List; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - /* * #%L * HAPI FHIR JPA Server @@ -74,6 +74,10 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4 theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -82,7 +86,7 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } @@ -112,6 +116,10 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4 theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -120,7 +128,7 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java index 0fbc945b698..9bbe26a11c0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java @@ -74,6 +74,10 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5 theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -82,7 +86,7 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } @@ -112,6 +116,10 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5 theNarrative, + @Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)") + @OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED) + List theFilter, + @Sort SortSpec theSortSpec, @@ -120,7 +128,7 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails); + return ((IFhirResourceDaoPatient) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails); } finally { endRequest(theServletRequest); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java new file mode 100644 index 00000000000..0ac6efd5669 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java @@ -0,0 +1,79 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.util.TestUtil; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +public class SearchFilterSyntaxTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchFilterSyntaxTest.class); + + private void testParse(String theExpression) throws SearchFilterParser.FilterSyntaxException { + SearchFilterParser.Filter filter = SearchFilterParser.parse(theExpression); + ourLog.info("Source: {}", theExpression); + ourLog.info("Parsed: {}", filter.toString()); + Assert.assertNotNull("Parsing failed - returned null", + filter); + Assert.assertEquals(String.format("Expression mismatch: found %s, expecting %s", + filter.toString(), + theExpression), + theExpression, + filter.toString()); + } + + @Test + public void testString() throws SearchFilterParser.FilterSyntaxException { + testParse("userName eq \"bjensen\""); + } + + @Test + public void testToken() throws SearchFilterParser.FilterSyntaxException { + testParse("name eq loinc|1234"); + } + + @Test + public void testUrl() throws SearchFilterParser.FilterSyntaxException { + testParse("name in http://loinc.org/vs/LP234"); + } + + @Test + public void testDate() throws SearchFilterParser.FilterSyntaxException { + testParse("date ge 2010-10-10"); + } + + @Test + public void testSubsumes() throws SearchFilterParser.FilterSyntaxException { + testParse("code sb snomed|diabetes"); + } + + @Test + public void testSubsumesId() throws SearchFilterParser.FilterSyntaxException { + testParse("code ss snomed|diabetes-NIDDM-stage-1"); + } + + @Test + public void testFilter() throws SearchFilterParser.FilterSyntaxException { + testParse("related[type eq comp].target pr false"); + } + + @Test + public void testFilter2() throws SearchFilterParser.FilterSyntaxException { + testParse("related[type eq comp and this lt that].target pr false"); + } + + @Test + public void testParentheses() throws SearchFilterParser.FilterSyntaxException { + testParse("((userName eq \"bjensen\") or (userName eq \"jdoe\")) and (code sb snomed|diabetes)"); + } + + @Test + public void testPrecedence() throws SearchFilterParser.FilterSyntaxException { + testParse("this eq that and this1 eq that1"); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchFtTest.java index 8dc63e77113..46c0c28d0bd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchFtTest.java @@ -251,16 +251,16 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1)); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1)); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId2, devId1)); /* @@ -276,7 +276,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId4, devId1)); /* @@ -292,7 +292,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId4)); } @@ -343,11 +343,11 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1)); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId2, devId1, ptId2, obsId3)); /* @@ -363,7 +363,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId4, devId1)); /* @@ -379,7 +379,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mySrd)); + actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mySrd)); assertThat(actual, containsInAnyOrder(ptId1, obsId4)); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java index 364af610675..b7e675a32b1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java @@ -92,11 +92,11 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { IIdType moId = myMedicationOrderDao.create(mo, mySrd).getId().toUnqualifiedVersionless(); HttpServletRequest request = mock(HttpServletRequest.class); - IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd); + IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2)); request = mock(HttpServletRequest.class); - resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd); + resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId)); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java index 25396e158b9..48a2c578794 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchFtTest.java @@ -334,16 +334,16 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1))); /* @@ -359,7 +359,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); /* @@ -375,7 +375,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); } @@ -426,11 +426,11 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1, ptId2, obsId3))); /* @@ -446,7 +446,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); /* @@ -462,7 +462,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); } 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 19431b8cadd..441de7433ee 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 @@ -171,11 +171,11 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless(); HttpServletRequest request = mock(HttpServletRequest.class); - IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd); + IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2)); request = mock(HttpServletRequest.class); - resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd); + resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId)); } @@ -202,7 +202,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { SearchParameterMap map = new SearchParameterMap(); map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE); IPrimitiveType count = new IntegerType(1000); - IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd); + IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, null, mySrd); TreeSet ids = new TreeSet<>(toUnqualifiedVersionlessIdValues(everything)); assertThat(ids, hasItem("List/A161444")); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java index 78870af144f..83eae406ace 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java @@ -464,12 +464,6 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { ourLog.info("Allowing IDs: {}", nonBlocked); - try { - throw new Exception(); - } catch (Exception e) { - ourLog.error("Trace", e); - } - } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java new file mode 100644 index 00000000000..cfecc19072f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java @@ -0,0 +1,95 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.util.TestUtil; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; + +@SuppressWarnings({"Duplicates"}) +public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test { + + @After + public void after() { + myDaoConfig.setFilterParameterEnabled(new DaoConfig().isFilterParameterEnabled()); + } + + @Before + public void before() { + myDaoConfig.setFilterParameterEnabled(true); + } + + @Test + public void testMalformedFilter() { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name eq smith))")); + try { + myPatientDao.search(map); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Error parsing _filter syntax: Expression did not terminate at 13", e.getMessage()); + } + } + + @Test + public void testChainWithMultipleTypePossibilities() { + + Patient p= new Patient(); + p.addName().setFamily("Smith").addGiven("John"); + p.setActive(true); + String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + p = new Patient(); + p.addName().setFamily("Jones").addGiven("Frank"); + p.setActive(false); + String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map; + List found; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name eq smith")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("(name eq smith) or (name eq jones)")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1,id2)); + + } + + @Test + public void testFilterDisabled() { + myDaoConfig.setFilterParameterEnabled(false); + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name eq smith")); + try { + myPatientDao.search(map); + } catch (InvalidRequestException e) { + assertEquals("_filter parameter is disabled on this server", e.getMessage()); + } + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java index b266726a904..3f4fe575d4e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchFtTest.java @@ -345,16 +345,16 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1))); /* @@ -370,7 +370,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); /* @@ -386,7 +386,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); } @@ -437,11 +437,11 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1))); request = mock(HttpServletRequest.class); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1, ptId2, obsId3))); /* @@ -457,7 +457,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1))); /* @@ -473,7 +473,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test { param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); - actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd())); + actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd())); assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4))); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index e52a29c81fb..6c9c0cd571a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -470,11 +470,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless(); HttpServletRequest request = mock(HttpServletRequest.class); - IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd); + IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2)); request = mock(HttpServletRequest.class); - resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd); + resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId)); } @@ -502,7 +502,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { SearchParameterMap map = new SearchParameterMap(); map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE); IPrimitiveType count = new IntegerType(1000); - IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd); + IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, null, mySrd); TreeSet ids = new TreeSet<>(toUnqualifiedVersionlessIdValues(everything)); assertThat(ids, hasItem("List/A161444")); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java index 3e7548260fa..f7257a82f63 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java @@ -292,11 +292,11 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test { IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless(); HttpServletRequest request = mock(HttpServletRequest.class); - IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd); + IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2)); request = mock(HttpServletRequest.class); - resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd); + resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd); assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId)); } @@ -324,9 +324,9 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test { SearchParameterMap map = new SearchParameterMap(); map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE); IPrimitiveType count = new IntegerType(1000); - IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd); + IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, null, mySrd); - TreeSet ids = new TreeSet(toUnqualifiedVersionlessIdValues(everything)); + TreeSet ids = new TreeSet<>(toUnqualifiedVersionlessIdValues(everything)); assertThat(ids, hasItem("List/A161444")); assertThat(ids, hasItem("List/A161468")); assertThat(ids, hasItem("List/A161500")); @@ -335,7 +335,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test { ourLog.info("Actual {} - {}", ids.size(), ids); assertEquals(allIds, ids); - ids = new TreeSet(); + ids = new TreeSet<>(); for (int i = 0; i < everything.size(); i++) { for (IBaseResource next : everything.getResources(i, i + 1)) { ids.add(next.getIdElement().toUnqualifiedVersionless().getValue()); diff --git a/hapi-fhir-jpaserver-example/pom.xml b/hapi-fhir-jpaserver-example/pom.xml index aadefc4ddfb..c47d1a1f21b 100644 --- a/hapi-fhir-jpaserver-example/pom.xml +++ b/hapi-fhir-jpaserver-example/pom.xml @@ -141,6 +141,41 @@ commons-dbcp2 + + org.postgresql + postgresql + 42.2.2 + + + + org.bitbucket.b_c + jose4j + 0.6.3 + + + + org.json + json + 20180130 + + + + org.springframework.boot + spring-boot-starter-security + 2.0.2.RELEASE + + + org.springframework.security.oauth + spring-security-oauth2 + 2.3.3.RELEASE + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.9.4 + + @@ -244,8 +279,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 @@ -288,8 +323,8 @@ - integration-test - verify + + diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java index 676320cd1ba..20e3a1a37db 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java @@ -127,7 +127,7 @@ public class FhirPathEngineR4Test { } @Test - public void testConcatenationFunction() throws FHIRException { + public void testStringCompare() throws FHIRException { String exp = "element.first().path.startsWith(%resource.type) and element.tail().all(path.startsWith(%resource.type&'.'))"; StructureDefinition sd = new StructureDefinition(); @@ -138,9 +138,9 @@ public class FhirPathEngineR4Test { Patient p = new Patient(); p.addName().setFamily("TEST"); - List result = ourEngine.evaluate(null, p, diff, exp); + List result = ourEngine.evaluate(null, p, null, diff, exp); ourLog.info(result.toString()); -// assertEquals("TEST.", result); + assertEquals(true, ((BooleanType)result.get(0)).booleanValue()); } diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index 4f14f481622..f643b680b16 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -42,7 +42,11 @@ public class ${className}ResourceProvider extends javax.servlet.http.HttpServletResponse theServletResponse, ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails, - + + @Description(shortDefinition="Search the contents of the resource's data using a filter") + @OptionalParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER) + StringAndListParam theFtFilter, + @Description(shortDefinition="Search the contents of the resource's data using a fulltext search") @OptionalParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT) StringAndListParam theFtContent, @@ -142,6 +146,7 @@ public class ${className}ResourceProvider extends startRequest(theServletRequest); try { SearchParameterMap paramMap = new SearchParameterMap(); + paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter); paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent); paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText); paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TAG, theSearchForTag); diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java index 6b296b30bb5..f97ea4efb8d 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java @@ -147,34 +147,6 @@ public class Dstu2ResourceValidatorDstu2Test { assertEquals(1, operationOutcome.getIssue().size()); } - @SuppressWarnings("deprecation") - @Test - public void testSchemaResourceValidator() throws IOException { - String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("patient-example-dicom.json")); - Patient p = ourCtx.newJsonParser().parseResource(Patient.class, res); - - ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p)); - - FhirValidator val = ourCtx.newValidator(); - val.setValidateAgainstStandardSchema(true); - if (ValidationConstants.SCHEMATRON_ENABLED) { - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - } - - val.validate(p); - - p.getAnimal().getBreed().setText("The Breed"); - try { - val.validate(p); - fail(); - } catch (ValidationFailureException e) { - OperationOutcome operationOutcome = (OperationOutcome) e.getOperationOutcome(); - ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome)); - assertEquals(1, operationOutcome.getIssue().size()); - assertThat(operationOutcome.getIssueFirstRep().getDetailsElement().getValue(), containsString("cvc-complex-type")); - } - } - /** * Make sure that the elements that appear in all resources (meta, language, extension, etc) * all appear in the correct order diff --git a/pom.xml b/pom.xml index 434861b8c2b..c74f5e98399 100755 --- a/pom.xml +++ b/pom.xml @@ -550,7 +550,7 @@ - 3.8.23-SNAPSHOT + 3.8.24-SNAPSHOT 1.0.2