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 9f6491c6b86..4a9fb531754 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 @@ -17,9 +17,9 @@ import java.util.*; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -60,6 +60,9 @@ public class DaoConfig { * update setter javadoc if default changes */ private boolean myAllowExternalReferences = false; + /** + * update setter javadoc if default changes + */ private boolean myAllowContainsSearches = true; /** @@ -137,6 +140,7 @@ public class DaoConfig { myTreatReferencesAsLogical.add(theTreatReferencesAsLogical); } + /** * Specifies the highest number that a client is permitted to use in a * Cache-Control: nostore, max-results=NNN @@ -376,9 +380,9 @@ public class DaoConfig { * of higher importance than raw write performance *

*

- * Note that this setting also has an impact on sorting (i.e. using the - * _sort parameter on searches): If the server is configured - * to not index missing field. + * Note that this setting also has an impact on sorting (i.e. using the + * _sort parameter on searches): If the server is configured + * to not index missing field. *

*/ public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) { @@ -650,6 +654,26 @@ public class DaoConfig { return this; } + /** + * If enabled, the server will support the use of :contains searches, + * which are helpful but can have adverse effects on performance. + * + * Default is true + */ + public boolean isAllowContainsSearches() { + return myAllowContainsSearches; + } + + /** + * If enabled, the server will support the use of :contains searches, + * which are helpful but can have adverse effects on performance. + * + * Default is true + */ + public void setAllowContainsSearches(boolean theAllowContainsSearches) { + this.myAllowContainsSearches = theAllowContainsSearches; + } + /** * If set to true (default is false) the server will allow * resources to have references to external servers. For example if this server is @@ -1099,16 +1123,6 @@ public class DaoConfig { } - public boolean allowContainsSearches() { - - return myAllowContainsSearches; - } - - public void setAllowContainsSearches(boolean myAllowContainsSearches) { - - this.myAllowContainsSearches = myAllowContainsSearches; - } - public enum IndexEnabledEnum { ENABLED, DISABLED 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 293514954fa..7f9a004a4c9 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 @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,8 +28,6 @@ import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; import ca.uhn.fhir.jpa.util.BaseIterator; -import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; @@ -45,7 +43,9 @@ import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; @@ -1091,10 +1091,10 @@ public class SearchBuilder implements ISearchBuilder { } else if (theParameter instanceof StringParam) { StringParam id = (StringParam) theParameter; rawSearchTerm = id.getValue(); - if ((id.isContains()) && - (!myCallingDao.getConfig().allowContainsSearches())) - { - throw new MethodNotAllowedException(":contains modifier is disabled on this server"); + if (id.isContains()) { + if (!myCallingDao.getConfig().isAllowContainsSearches()) { + throw new MethodNotAllowedException(":contains modifier is disabled on this server"); + } } } else if (theParameter instanceof IPrimitiveDatatype) { IPrimitiveDatatype id = (IPrimitiveDatatype) theParameter; @@ -1109,18 +1109,11 @@ public class SearchBuilder implements ISearchBuilder { } String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm); - if (myCallingDao.getConfig().allowContainsSearches()) { - if (theParameter instanceof StringParam) { - if (((StringParam) theParameter).isContains()) { - likeExpression = createLeftAndRightMatchLikeExpression(likeExpression); - } else { - likeExpression = createLeftMatchLikeExpression(likeExpression); - } - } else { - likeExpression = createLeftMatchLikeExpression(likeExpression); - } - } - else { + if (theParameter instanceof StringParam && + ((StringParam) theParameter).isContains() && + myCallingDao.getConfig().isAllowContainsSearches()) { + likeExpression = createLeftAndRightMatchLikeExpression(likeExpression); + } else { likeExpression = createLeftMatchLikeExpression(likeExpression); } @@ -2020,14 +2013,14 @@ public class SearchBuilder implements ISearchBuilder { return lastUpdatedPredicates; } - private static String createLeftMatchLikeExpression(String likeExpression) { - return likeExpression.replace("%", "[%]") + "%"; - } - private static String createLeftAndRightMatchLikeExpression(String likeExpression) { return "%" + likeExpression.replace("%", "[%]") + "%"; } + private static String createLeftMatchLikeExpression(String likeExpression) { + return likeExpression.replace("%", "[%]") + "%"; + } + private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From theFrom, String theResourceType) { RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType); 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 05185a32150..514a71d2b5a 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 @@ -11,6 +11,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -54,6 +55,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { public void afterResetSearchSize() { myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum()); + myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches()); } @Before @@ -1404,43 +1406,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } } - /** - * See #819 - */ - @Test - public void testSearchTokenWithNotModifier() { - String male, female; - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("001"); - patient.addName().setFamily("Tester").addGiven("Joe"); - patient.setGender(AdministrativeGender.MALE); - male = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); - } - { - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("002"); - patient.addName().setFamily("Tester").addGiven("Jane"); - patient.setGender(AdministrativeGender.FEMALE); - female = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); - } - - List patients; - SearchParameterMap params; - - params = new SearchParameterMap(); - params.add(Patient.SP_GENDER, new TokenParam(null, "male")); - params.setLoadSynchronous(true); - patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params)); - assertThat(patients, contains(male)); - - params = new SearchParameterMap(); - params.add(Patient.SP_GENDER, new TokenParam(null, "male").setModifier(TokenParamModifier.NOT)); - params.setLoadSynchronous(true); - patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params)); - assertThat(patients, contains(female)); - } - @Test public void testSearchNumberParam() { ImmunizationRecommendation e1 = new ImmunizationRecommendation(); @@ -2122,6 +2087,43 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } } + /** + * See #819 + */ + @Test + public void testSearchTokenWithNotModifier() { + String male, female; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + patient.setGender(AdministrativeGender.MALE); + male = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); + } + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("002"); + patient.addName().setFamily("Tester").addGiven("Jane"); + patient.setGender(AdministrativeGender.FEMALE); + female = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); + } + + List patients; + SearchParameterMap params; + + params = new SearchParameterMap(); + params.add(Patient.SP_GENDER, new TokenParam(null, "male")); + params.setLoadSynchronous(true); + patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params)); + assertThat(patients, contains(male)); + + params = new SearchParameterMap(); + params.add(Patient.SP_GENDER, new TokenParam(null, "male").setModifier(TokenParamModifier.NOT)); + params.setLoadSynchronous(true); + patients = toUnqualifiedVersionlessIdValues(myPatientDao.search(params)); + assertThat(patients, contains(female)); + } + @Test public void testSearchTokenWrongParam() { Patient p1 = new Patient(); @@ -2257,6 +2259,67 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } + @Test + public void testSearchWithContains() { + + Patient pt1 = new Patient(); + pt1.addName().setFamily("ABCDEFGHIJK"); + String pt1id = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue(); + + Patient pt2 = new Patient(); + pt2.addName().setFamily("FGHIJK"); + String pt2id = myPatientDao.create(pt2).getId().toUnqualifiedVersionless().getValue(); + + Patient pt3 = new Patient(); + pt3.addName().setFamily("ZZZZZ"); + myPatientDao.create(pt3).getId().toUnqualifiedVersionless().getValue(); + + + List ids; + SearchParameterMap map; + IBundleProvider results; + + // Contains = true + map = new SearchParameterMap(); + map.add(Patient.SP_NAME, new StringParam("FGHIJK").setContains(true)); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, containsInAnyOrder(pt1id, pt2id)); + + // Contains = false + map = new SearchParameterMap(); + map.add(Patient.SP_NAME, new StringParam("FGHIJK").setContains(false)); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, containsInAnyOrder(pt2id)); + + // No contains + map = new SearchParameterMap(); + map.add(Patient.SP_NAME, new StringParam("FGHIJK")); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, containsInAnyOrder(pt2id)); + } + + @Test + public void testSearchWithContainsDisabled() { + myDaoConfig.setAllowContainsSearches(false); + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Patient.SP_NAME, new StringParam("FGHIJK").setContains(true)); + + try { + myPatientDao.search(map); + fail(); + } catch (MethodNotAllowedException e) { + assertEquals(":contains modifier is disabled on this server", e.getMessage()); + } + } + @Test public void testSearchWithDate() { IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId();