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 3ba2c792a74..07739e125ed 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 @@ -65,6 +65,7 @@ import org.apache.commons.lang3.Validate; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.query.Query; +import org.hl7.fhir.dstu3.model.Location; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; @@ -160,6 +161,10 @@ public class SearchBuilder implements ISearchBuilder { // Remove any empty parameters theParams.clean(); + if (myResourceType == Location.class) { + theParams.setLocationDistance(); + } + /* * Check if there is a unique key associated with the set * of parameters passed in @@ -181,7 +186,6 @@ public class SearchBuilder implements ISearchBuilder { } - @Override public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest) { init(theParams, theSearchUuid); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index 69a03290a8a..47dfa7e9c47 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.provider.r4.ResourceProviderR4Test; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; +import ca.uhn.fhir.jpa.util.CoordCalculatorTest; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; @@ -65,6 +66,7 @@ import java.math.BigDecimal; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -4282,6 +4284,55 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } + @Test + public void testNearSearchApproximate() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + IIdType locId = ourClient.create().resource(loc).execute().getId().toUnqualifiedVersionless(); + + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + String url = "/Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + URLEncoder.encode(":") + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + Location.SP_NEAR_DISTANCE + "=" + bigEnoughDistance + URLEncoder.encode("|http://unitsofmeasure.org|km"); + + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(locId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + String url = "/Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + URLEncoder.encode(":") + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + Location.SP_NEAR_DISTANCE + "=" + tooSmallDistance + URLEncoder.encode("|http://unitsofmeasure.org|km"); + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(0, actual.getEntry().size()); + } + + } + private String toStr(Date theDate) { return new InstantDt(theDate).getValueAsString(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java index c12fad6d306..e79e49fe76e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java @@ -58,6 +58,7 @@ public class MatchUrlServiceTest extends BaseJpaTest { Location.SP_NEAR + "=1000.0:2000.0" + "&" + Location.SP_NEAR_DISTANCE + "=" + kmDistance + "|http://unitsofmeasure.org|km", ourCtx.getResourceDefinition("Location")); + map.setLocationDistance(); QuantityParam nearDistanceParam = map.getNearDistanceParam(); assertEquals(1, map.size()); @@ -74,6 +75,8 @@ public class MatchUrlServiceTest extends BaseJpaTest { "&" + Location.SP_NEAR_DISTANCE + "=2|http://unitsofmeasure.org|km", ourCtx.getResourceDefinition("Location")); + map.setLocationDistance(); + fail(); } catch (IllegalArgumentException e) { assertEquals("Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present", e.getMessage()); @@ -89,7 +92,8 @@ public class MatchUrlServiceTest extends BaseJpaTest { "," + "2|http://unitsofmeasure.org|km", ourCtx.getResourceDefinition("Location")); - map.setLoadSynchronous(true); + map.setLocationDistance(); + fail(); } catch (IllegalArgumentException e) { assertEquals("Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present", e.getMessage()); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java index 6cd49534031..fb77ea8af24 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java @@ -26,13 +26,11 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.dstu2.resource.Location; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParameterUtil; -import ca.uhn.fhir.rest.param.QuantityAndListParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.UrlUtil; @@ -115,9 +113,6 @@ public class MatchUrlService { } else if (Constants.PARAM_SOURCE.equals(nextParamName)) { IQueryParameterAnd param = ParameterUtil.parseQueryParams(myContext, RestSearchParameterTypeEnum.TOKEN, nextParamName, paramList); paramMap.add(nextParamName, param); - } else if (Location.SP_NEAR_DISTANCE.equals(nextParamName)) { - QuantityAndListParam nearDistanceAndListParam = (QuantityAndListParam) ParameterUtil.parseQueryParams(myContext, RestSearchParameterTypeEnum.QUANTITY, nextParamName, paramList); - paramMap.setNearDistanceParam(nearDistanceAndListParam); } else if (nextParamName.startsWith("_")) { // ignore these since they aren't search params (e.g. _sort) } else { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java index a159ce3b6fa..958e58c2f1c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java @@ -5,15 +5,17 @@ import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.dstu2.resource.Location; import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.hl7.fhir.dstu3.model.Location; import java.io.Serializable; import java.util.*; @@ -495,29 +497,38 @@ public class SearchParameterMap implements Serializable { } } - public void setNearDistanceParam(QuantityAndListParam theQuantityAndListParam) { - List orTokens = theQuantityAndListParam.getValuesAsQueryTokens(); - if (orTokens.isEmpty()) { - return; - } - if (orTokens.size() > 1) { - throw new IllegalArgumentException("Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present"); - } - QuantityOrListParam quantityOrListParam = orTokens.get(0); - List tokens = quantityOrListParam.getValuesAsQueryTokens(); - if (tokens.isEmpty()) { - return; - } - if (tokens.size() > 1) { - throw new IllegalArgumentException("Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present"); - } - myNearDistanceParam = tokens.get(0); + public void setNearDistanceParam(QuantityParam theQuantityParam) { + myNearDistanceParam = theQuantityParam; } public QuantityParam getNearDistanceParam() { return myNearDistanceParam; } + public void setLocationDistance() { + if (containsKey(Location.SP_NEAR_DISTANCE)) { + List> paramAndList = get(Location.SP_NEAR_DISTANCE); + + if (paramAndList.isEmpty()) { + return; + } + if (paramAndList.size() > 1) { + throw new IllegalArgumentException("Only one " + ca.uhn.fhir.model.dstu2.resource.Location.SP_NEAR_DISTANCE + " parameter may be present"); + } + List paramOrList = paramAndList.get(0); + if (paramOrList.isEmpty()) { + return; + } + if (paramOrList.size() > 1) { + throw new IllegalArgumentException("Only one " + ca.uhn.fhir.model.dstu2.resource.Location.SP_NEAR_DISTANCE + " parameter may be present"); + } + setNearDistanceParam((QuantityParam) paramOrList.get(0)); + + // Need to remove near-distance or it we'll get a hashcode predicate for it + remove(Location.SP_NEAR_DISTANCE); + } + } + public enum EverythingModeEnum { /* * Don't reorder! We rely on the ordinals diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java index c93c5e14ba8..d83eb5048c9 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java @@ -40,6 +40,7 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.MetaUtil; import ca.uhn.fhir.util.UrlUtil; +import org.hl7.fhir.dstu3.model.Location; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -88,7 +89,7 @@ public class InMemoryResourceMatcher { if (searchParameterMap.getLastUpdated() != null) { return InMemoryMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, InMemoryMatchResult.STANDARD_PARAMETER); } - if (searchParameterMap.getNearDistanceParam() != null) { + if (searchParameterMap.containsKey(Location.SP_NEAR)) { return InMemoryMatchResult.unsupportedFromReason(InMemoryMatchResult.LOCATION_NEAR); }