diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestSearchParameterTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestSearchParameterTypeEnum.java index 0bcce616947..1da52a194c6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestSearchParameterTypeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestSearchParameterTypeEnum.java @@ -89,7 +89,7 @@ public enum RestSearchParameterTypeEnum { */ HAS("string", "http://hl7.org/fhir/search-param-type"), - /** + /** * Code Value: number * * Search parameter SHALL be a number (a whole number, or a decimal). diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java index d58758f9133..19b825ddca4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java @@ -61,43 +61,43 @@ public class ParameterUtil { * This is a utility method intended provided to help the JPA module. */ public static IQueryParameterAnd parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType, - String theUnqualifiedParamName, List theParameters) { + String theUnqualifiedParamName, List theParameters) { QueryParameterAndBinder binder = null; switch (paramType) { - case COMPOSITE: - throw new UnsupportedOperationException(); - case DATE: - binder = new QueryParameterAndBinder(DateAndListParam.class, - Collections.> emptyList()); - break; - case NUMBER: - binder = new QueryParameterAndBinder(NumberAndListParam.class, - Collections.> emptyList()); - break; - case QUANTITY: - binder = new QueryParameterAndBinder(QuantityAndListParam.class, - Collections.> emptyList()); - break; - case REFERENCE: - binder = new QueryParameterAndBinder(ReferenceAndListParam.class, - Collections.> emptyList()); - break; - case STRING: - binder = new QueryParameterAndBinder(StringAndListParam.class, - Collections.> emptyList()); - break; - case TOKEN: - binder = new QueryParameterAndBinder(TokenAndListParam.class, - Collections.> emptyList()); - break; - case URI: - binder = new QueryParameterAndBinder(UriAndListParam.class, - Collections.> emptyList()); - break; - case HAS: - binder = new QueryParameterAndBinder(HasAndListParam.class, - Collections.> emptyList()); - break; + case COMPOSITE: + throw new UnsupportedOperationException(); + case DATE: + binder = new QueryParameterAndBinder(DateAndListParam.class, + Collections.>emptyList()); + break; + case NUMBER: + binder = new QueryParameterAndBinder(NumberAndListParam.class, + Collections.>emptyList()); + break; + case QUANTITY: + binder = new QueryParameterAndBinder(QuantityAndListParam.class, + Collections.>emptyList()); + break; + case REFERENCE: + binder = new QueryParameterAndBinder(ReferenceAndListParam.class, + Collections.>emptyList()); + break; + case STRING: + binder = new QueryParameterAndBinder(StringAndListParam.class, + Collections.>emptyList()); + break; + case TOKEN: + binder = new QueryParameterAndBinder(TokenAndListParam.class, + Collections.>emptyList()); + break; + case URI: + binder = new QueryParameterAndBinder(UriAndListParam.class, + Collections.>emptyList()); + break; + case HAS: + binder = new QueryParameterAndBinder(HasAndListParam.class, + Collections.>emptyList()); + break; } // FIXME null access @@ -108,7 +108,7 @@ public class ParameterUtil { * This is a utility method intended provided to help the JPA module. */ public static IQueryParameterAnd parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef, - String theUnqualifiedParamName, List theParameters) { + String theUnqualifiedParamName, List theParameters) { RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters); } @@ -126,14 +126,14 @@ public class ParameterUtil { for (int i = 0; i < theValue.length(); i++) { char next = theValue.charAt(i); switch (next) { - case '$': - case ',': - case '|': - case '\\': - b.append('\\'); - break; - default: - break; + case '$': + case ',': + case '|': + case '\\': + b.append('\\'); + break; + default: + break; } b.append(next); } @@ -207,7 +207,7 @@ public class ParameterUtil { public static boolean isBindableIntegerType(Class theClass) { return Integer.class.isAssignableFrom(theClass) - || IPrimitiveType.class.isAssignableFrom(theClass); + || IPrimitiveType.class.isAssignableFrom(theClass); } public static String escapeAndJoinOrList(Collection theValues) { @@ -236,7 +236,7 @@ public class ParameterUtil { if (value.charAt(0) == '"') { eTagVersion = value.substring(1, value.length() - 1); } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' - && value.charAt(2) == '"') { + && value.charAt(2) == '"') { eTagVersion = value.substring(3, value.length() - 1); } else { eTagVersion = value; @@ -262,16 +262,16 @@ public class ParameterUtil { @Override public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, - QualifiedParamList theParameters) { + QualifiedParamList theParameters) { if (theParameters.isEmpty()) { return; } if (theParameters.size() > 1) { throw new IllegalArgumentException( - "Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); + "Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); } theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(), - theParameters.get(0)); + theParameters.get(0)); } }; } @@ -351,13 +351,13 @@ public class ParameterUtil { b.append(next); } else { switch (theValue.charAt(i + 1)) { - case '$': - case ',': - case '|': - case '\\': - continue; - default: - b.append(next); + case '$': + case ',': + case '|': + case '\\': + continue; + default: + b.append(next); } } } else { 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 b3ccb922442..1e3f16fcfd6 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 @@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.util.SourceParam; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; import ca.uhn.fhir.jpa.util.*; @@ -861,19 +862,9 @@ public class SearchBuilder implements ISearchBuilder { List codePredicates = new ArrayList<>(); for (IQueryParameterType nextParameter : theList) { - String nextParamValue = nextParameter.getValueAsQueryToken(myContext); - int lastHashValueIndex = nextParamValue.lastIndexOf('#'); - String sourceUri; - String requestId; - if (lastHashValueIndex == -1) { - sourceUri = nextParamValue; - requestId = null; - } else { - sourceUri = nextParamValue.substring(0, lastHashValueIndex); - requestId = nextParamValue.substring(lastHashValueIndex + 1); - } - requestId = left(requestId, Constants.REQUEST_ID_LENGTH); - + SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext)); + String sourceUri = sourceParameter.getSourceUri(); + String requestId = sourceParameter.getRequestId(); Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri); Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId); if (isNotBlank(sourceUri) && isNotBlank(requestId)) { 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 be810cca188..09a260d5f4d 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 @@ -93,16 +93,10 @@ public class MatchUrlService { paramMap.setLastUpdated(p1); } } - continue; - } - - if (Constants.PARAM_HAS.equals(nextParamName)) { + } else if (Constants.PARAM_HAS.equals(nextParamName)) { IQueryParameterAnd param = ParameterUtil.parseQueryParams(myContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList); paramMap.add(nextParamName, param); - continue; - } - - if (Constants.PARAM_COUNT.equals(nextParamName)) { + } else if (Constants.PARAM_COUNT.equals(nextParamName)) { if (paramList.size() > 0 && paramList.get(0).size() > 0) { String intString = paramList.get(0).get(0); try { @@ -111,16 +105,16 @@ public class MatchUrlService { throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString); } } - continue; - } - - if (ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(nextParamName)) { + } else if (ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(nextParamName)) { if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) { throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier()); } IQueryParameterAnd type = newInstanceAnd(nextParamName); type.setValuesAsQueryTokens(myContext, nextParamName, (paramList)); paramMap.add(nextParamName, type); + } else if (Constants.PARAM_SOURCE.equals(nextParamName)) { + IQueryParameterAnd param = ParameterUtil.parseQueryParams(myContext, RestSearchParameterTypeEnum.TOKEN, nextParamName, paramList); + paramMap.add(nextParamName, param); } 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/matcher/InMemoryResourceMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java index 5454a44bba7..e7c509de7eb 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 @@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.util.SourceParam; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; @@ -35,6 +36,7 @@ import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.param.ReferenceParam; 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.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -125,7 +127,6 @@ public class InMemoryResourceMatcher { switch (theParamName) { case IAnyResource.SP_RES_ID: - return InMemoryMatchResult.fromBoolean(matchIdsAndOr(theAndOrParams, theResource)); case IAnyResource.SP_RES_LANGUAGE: @@ -133,16 +134,38 @@ public class InMemoryResourceMatcher { case Constants.PARAM_TAG: case Constants.PARAM_PROFILE: case Constants.PARAM_SECURITY: - return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); - + case Constants.PARAM_SOURCE: + return InMemoryMatchResult.fromBoolean(matchSourcesAndOr(theAndOrParams, theResource)); default: - - return matchResourceParam(theParamName, theAndOrParams, theSearchParams, resourceName, paramDef); } } + private boolean matchSourcesAndOr(List> theAndOrParams, IBaseResource theResource) { + if (theResource == null) { + return true; + } + return theAndOrParams.stream().allMatch(nextAnd -> matchSourcesOr(nextAnd, theResource)); + } + + private boolean matchSourcesOr(List theOrParams, IBaseResource theResource) { + return theOrParams.stream().anyMatch(param -> matchSource(param, theResource)); + } + + private boolean matchSource(IQueryParameterType theSourceParam, IBaseResource theResource) { + SourceParam paramSource = new SourceParam(theSourceParam.getValueAsQueryToken(myFhirContext)); + SourceParam resourceSource = new SourceParam(MetaUtil.getSource(myFhirContext, theResource.getMeta())); + boolean matches = true; + if (paramSource.getSourceUri() != null) { + matches = paramSource.getSourceUri().equals(resourceSource.getSourceUri()); + } + if (paramSource.getRequestId() != null) { + matches &= paramSource.getRequestId().equals(resourceSource.getRequestId()); + } + return matches; + } + private boolean matchIdsAndOr(List> theAndOrParams, IBaseResource theResource) { if (theResource == null) { return true; @@ -151,9 +174,6 @@ public class InMemoryResourceMatcher { } private boolean matchIdsOr(List theOrParams, IBaseResource theResource) { - if (theResource == null) { - return true; - } return theOrParams.stream().anyMatch(param -> param instanceof StringParam && matchId(((StringParam) param).getValue(), theResource.getIdElement())); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/util/SourceParam.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/util/SourceParam.java new file mode 100644 index 00000000000..61c082df9b6 --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/util/SourceParam.java @@ -0,0 +1,62 @@ +package ca.uhn.fhir.jpa.searchparam.util; + +import ca.uhn.fhir.rest.api.Constants; + +import static org.apache.commons.lang3.StringUtils.left; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +/** + * Model of the _source parameter + */ +public class SourceParam { + + private static final long serialVersionUID = 1L; + private final String myParameterValue; + private final String mySourceUri; + private final String myRequestId; + + public SourceParam(String theParameterValue) { + myParameterValue = theParameterValue; + String requestId; + int lastHashValueIndex = theParameterValue.lastIndexOf('#'); + if (lastHashValueIndex == -1) { + mySourceUri = theParameterValue; + requestId = null; + } else { + if (lastHashValueIndex == 0) { + mySourceUri = null; + } else { + mySourceUri = theParameterValue.substring(0, lastHashValueIndex); + } + requestId = theParameterValue.substring(lastHashValueIndex + 1); + } + myRequestId = left(requestId, Constants.REQUEST_ID_LENGTH); + } + + public String getSourceUri() { + return mySourceUri; + } + + public String getRequestId() { + return myRequestId; + } +} diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java index 58f0cd714ef..b151af6232c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.model.primitive.BaseDateTimeDt; +import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.param.TokenParamModifier; @@ -38,10 +39,12 @@ public class InMemoryResourceMatcherR5Test { private static final String EARLY_DATE = "1965-08-09"; private static final String LATE_DATE = "2000-06-29"; public static final String OBSERVATION_CODE = "MATCH"; + private static final String SOURCE_URI = "urn:source:0"; + private static final String REQUEST_ID = "a_request_id"; + private static final String TEST_SOURCE = SOURCE_URI + "#" + REQUEST_ID; @Autowired - private - InMemoryResourceMatcher myInMemoryResourceMatcher; + private InMemoryResourceMatcher myInMemoryResourceMatcher; @MockBean ISearchParamRegistry mySearchParamRegistry; @@ -81,6 +84,7 @@ public class InMemoryResourceMatcherR5Test { when(mySearchParamRegistry.getActiveSearchParam("Observation", "encounter")).thenReturn(encSearchParam); myObservation = new Observation(); + myObservation.getMeta().setSource(TEST_SOURCE); myObservation.setEffective(new DateTimeType(OBSERVATION_DATE)); CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding().setCode(OBSERVATION_CODE); @@ -88,6 +92,26 @@ public class InMemoryResourceMatcherR5Test { mySearchParams = extractDateSearchParam(myObservation); } + @Test + public void testSupportedSource() { + { + InMemoryMatchResult result = myInMemoryResourceMatcher.match(Constants.PARAM_SOURCE + "=" + TEST_SOURCE, myObservation, mySearchParams); + assertTrue(result.matched()); + } + { + InMemoryMatchResult result = myInMemoryResourceMatcher.match(Constants.PARAM_SOURCE + "=" + SOURCE_URI, myObservation, mySearchParams); + assertTrue(result.matched()); + } + { + InMemoryMatchResult result = myInMemoryResourceMatcher.match(Constants.PARAM_SOURCE + "=" + REQUEST_ID, myObservation, mySearchParams); + assertFalse(result.matched()); + } + { + InMemoryMatchResult result = myInMemoryResourceMatcher.match(Constants.PARAM_SOURCE + "=#" + REQUEST_ID, myObservation, mySearchParams); + assertTrue(result.matched()); + } + } + @Test public void testUnsupportedChained() { InMemoryMatchResult result = myInMemoryResourceMatcher.match("encounter.class=FOO", myObservation, mySearchParams);