
001/*- 002 * #%L 003 * HAPI FHIR Search Parameters 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.searchparam.matcher; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.context.RuntimeResourceDefinition; 025import ca.uhn.fhir.context.RuntimeSearchParam; 026import ca.uhn.fhir.context.support.ConceptValidationOptions; 027import ca.uhn.fhir.context.support.IValidationSupport; 028import ca.uhn.fhir.context.support.ValidationSupportContext; 029import ca.uhn.fhir.i18n.Msg; 030import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; 031import ca.uhn.fhir.jpa.model.entity.StorageSettings; 032import ca.uhn.fhir.jpa.searchparam.MatchUrlService; 033import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 034import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; 035import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; 036import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; 037import ca.uhn.fhir.jpa.searchparam.util.SourceParam; 038import ca.uhn.fhir.model.api.IQueryParameterType; 039import ca.uhn.fhir.rest.api.Constants; 040import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 041import ca.uhn.fhir.rest.api.server.RequestDetails; 042import ca.uhn.fhir.rest.param.BaseParamWithPrefix; 043import ca.uhn.fhir.rest.param.ParamPrefixEnum; 044import ca.uhn.fhir.rest.param.ReferenceParam; 045import ca.uhn.fhir.rest.param.StringParam; 046import ca.uhn.fhir.rest.param.TokenParam; 047import ca.uhn.fhir.rest.param.TokenParamModifier; 048import ca.uhn.fhir.rest.param.UriParam; 049import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 050import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 051import ca.uhn.fhir.util.MetaUtil; 052import ca.uhn.fhir.util.UrlUtil; 053import com.google.common.collect.Sets; 054import org.apache.commons.lang3.Validate; 055import org.hl7.fhir.dstu3.model.Location; 056import org.hl7.fhir.instance.model.api.IAnyResource; 057import org.hl7.fhir.instance.model.api.IBaseCoding; 058import org.hl7.fhir.instance.model.api.IBaseResource; 059import org.hl7.fhir.instance.model.api.IIdType; 060import org.hl7.fhir.instance.model.api.IPrimitiveType; 061import org.slf4j.LoggerFactory; 062import org.springframework.beans.BeansException; 063import org.springframework.beans.factory.annotation.Autowired; 064import org.springframework.context.ApplicationContext; 065 066import javax.annotation.Nonnull; 067import javax.annotation.Nullable; 068import java.util.List; 069import java.util.Map; 070import java.util.Set; 071import java.util.stream.Collectors; 072 073import static org.apache.commons.lang3.StringUtils.isBlank; 074import static org.apache.commons.lang3.StringUtils.isNotBlank; 075 076public class InMemoryResourceMatcher { 077 078 public static final Set<String> UNSUPPORTED_PARAMETER_NAMES = Sets.newHashSet(Constants.PARAM_HAS); 079 private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(InMemoryResourceMatcher.class); 080 @Autowired 081 ApplicationContext myApplicationContext; 082 @Autowired 083 ISearchParamRegistry mySearchParamRegistry; 084 @Autowired 085 StorageSettings myStorageSettings; 086 @Autowired 087 FhirContext myFhirContext; 088 @Autowired 089 SearchParamExtractorService mySearchParamExtractorService; 090 @Autowired 091 IndexedSearchParamExtractor myIndexedSearchParamExtractor; 092 @Autowired 093 private MatchUrlService myMatchUrlService; 094 private ValidationSupportInitializationState validationSupportState = ValidationSupportInitializationState.NOT_INITIALIZED; 095 private IValidationSupport myValidationSupport = null; 096 public InMemoryResourceMatcher() { 097 } 098 099 /** 100 * Lazy loads a {@link IValidationSupport} implementation just-in-time. 101 * If no suitable bean is available, or if a {@link ca.uhn.fhir.context.ConfigurationException} is thrown, matching 102 * can proceed, but the qualifiers that depend on the validation support will be disabled. 103 * 104 * @return A bean implementing {@link IValidationSupport} if one is available, otherwise null 105 */ 106 private IValidationSupport getValidationSupportOrNull() { 107 if (validationSupportState == ValidationSupportInitializationState.NOT_INITIALIZED) { 108 try { 109 myValidationSupport = myApplicationContext.getBean(IValidationSupport.class); 110 validationSupportState = ValidationSupportInitializationState.INITIALIZED; 111 } catch (BeansException | ConfigurationException ignore) { 112 // We couldn't get a validation support bean, and we don't want to waste cycles trying again 113 ourLog.warn(Msg.code(2100) + "No bean satisfying IValidationSupport could be initialized. Qualifiers dependent on IValidationSupport will not be supported."); 114 validationSupportState = ValidationSupportInitializationState.FAILED; 115 } 116 } 117 return myValidationSupport; 118 } 119 120 /** 121 * @deprecated Use {@link #match(String, IBaseResource, ResourceIndexedSearchParams, RequestDetails)} 122 */ 123 @Deprecated 124 public InMemoryMatchResult match(String theCriteria, IBaseResource theResource, @Nullable ResourceIndexedSearchParams theIndexedSearchParams) { 125 return match(theCriteria, theResource, theIndexedSearchParams, null); 126 } 127 128 129 /** 130 * This method is called in two different scenarios. With a null theResource, it determines whether database matching might be required. 131 * Otherwise, it tries to perform the match in-memory, returning UNSUPPORTED if it's not possible. 132 * <p> 133 * Note that there will be cases where it returns UNSUPPORTED with a null resource, but when a non-null resource it returns supported and no match. 134 * This is because an earlier parameter may be matchable in-memory in which case processing stops and we never get to the parameter 135 * that would have required a database call. 136 * 137 * @param theIndexedSearchParams If the search params have already been calculated for the given resource, 138 * they can be passed in. Passing in {@literal null} is also fine, in which 139 * case they will be calculated for the resource. It can be preferable to 140 * pass in {@literal null} unless you already actually had to calculate the 141 * indexes for another reason, since we can be efficient here and only calculate 142 * the params that are actually relevant for the given search expression. 143 */ 144 public InMemoryMatchResult match(String theCriteria, IBaseResource theResource, @Nullable ResourceIndexedSearchParams theIndexedSearchParams, RequestDetails theRequestDetails) { 145 RuntimeResourceDefinition resourceDefinition; 146 if (theResource == null) { 147 Validate.isTrue(!theCriteria.startsWith("?"), "Invalid match URL format (must match \"[resourceType]?[params]\")"); 148 Validate.isTrue(theCriteria.contains("?"), "Invalid match URL format (must match \"[resourceType]?[params]\")"); 149 resourceDefinition = UrlUtil.parseUrlResourceType(myFhirContext, theCriteria); 150 } else { 151 resourceDefinition = myFhirContext.getResourceDefinition(theResource); 152 } 153 SearchParameterMap searchParameterMap; 154 try { 155 searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, resourceDefinition); 156 } catch (UnsupportedOperationException e) { 157 return InMemoryMatchResult.unsupportedFromReason(InMemoryMatchResult.PARSE_FAIL); 158 } 159 searchParameterMap.clean(); 160 161 ResourceIndexedSearchParams relevantSearchParams = null; 162 if (theIndexedSearchParams != null) { 163 relevantSearchParams = theIndexedSearchParams; 164 } else if (theResource != null) { 165 // Don't index search params we don't actully need for the given criteria 166 ISearchParamExtractor.ISearchParamFilter filter = theSearchParams -> theSearchParams 167 .stream() 168 .filter(t -> searchParameterMap.containsKey(t.getName())) 169 .collect(Collectors.toList()); 170 relevantSearchParams = myIndexedSearchParamExtractor.extractIndexedSearchParams(theResource, theRequestDetails, filter); 171 } 172 173 return match(searchParameterMap, theResource, resourceDefinition, relevantSearchParams); 174 } 175 176 /** 177 * @param theCriteria 178 * @return result.supported() will be true if theCriteria can be evaluated in-memory 179 */ 180 public InMemoryMatchResult canBeEvaluatedInMemory(String theCriteria) { 181 return match(theCriteria, null, null, null); 182 } 183 184 /** 185 * @param theSearchParameterMap 186 * @param theResourceDefinition 187 * @return result.supported() will be true if theSearchParameterMap can be evaluated in-memory 188 */ 189 public InMemoryMatchResult canBeEvaluatedInMemory(SearchParameterMap theSearchParameterMap, RuntimeResourceDefinition theResourceDefinition) { 190 return match(theSearchParameterMap, null, theResourceDefinition, null); 191 } 192 193 @Nonnull 194 public InMemoryMatchResult match(SearchParameterMap theSearchParameterMap, IBaseResource theResource, RuntimeResourceDefinition theResourceDefinition, ResourceIndexedSearchParams theSearchParams) { 195 if (theSearchParameterMap.getLastUpdated() != null) { 196 return InMemoryMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, InMemoryMatchResult.STANDARD_PARAMETER); 197 } 198 if (theSearchParameterMap.containsKey(Location.SP_NEAR)) { 199 return InMemoryMatchResult.unsupportedFromReason(InMemoryMatchResult.LOCATION_NEAR); 200 } 201 202 for (Map.Entry<String, List<List<IQueryParameterType>>> entry : theSearchParameterMap.entrySet()) { 203 String theParamName = entry.getKey(); 204 List<List<IQueryParameterType>> theAndOrParams = entry.getValue(); 205 InMemoryMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, theResourceDefinition, theResource, theSearchParams); 206 if (!result.matched()) { 207 return result; 208 } 209 } 210 return InMemoryMatchResult.successfulMatch(); 211 } 212 213 // This method is modelled from SearchBuilder.searchForIdsWithAndOr() 214 private InMemoryMatchResult matchIdsWithAndOr(String theParamName, List<List<IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { 215 if (theAndOrParams.isEmpty()) { 216 return InMemoryMatchResult.successfulMatch(); 217 } 218 219 String resourceName = theResourceDefinition.getName(); 220 RuntimeSearchParam paramDef = mySearchParamRegistry.getActiveSearchParam(resourceName, theParamName); 221 InMemoryMatchResult checkUnsupportedResult = checkForUnsupportedParameters(theParamName, paramDef, theAndOrParams); 222 if (!checkUnsupportedResult.supported()) { 223 return checkUnsupportedResult; 224 } 225 226 switch (theParamName) { 227 case IAnyResource.SP_RES_ID: 228 return InMemoryMatchResult.fromBoolean(matchIdsAndOr(theAndOrParams, theResource)); 229 case Constants.PARAM_SOURCE: 230 return InMemoryMatchResult.fromBoolean(matchSourcesAndOr(theAndOrParams, theResource)); 231 case Constants.PARAM_TAG: 232 return InMemoryMatchResult.fromBoolean(matchTagsOrSecurityAndOr(theAndOrParams, theResource, true)); 233 case Constants.PARAM_SECURITY: 234 return InMemoryMatchResult.fromBoolean(matchTagsOrSecurityAndOr(theAndOrParams, theResource, false)); 235 case Constants.PARAM_PROFILE: 236 return InMemoryMatchResult.fromBoolean(matchProfilesAndOr(theAndOrParams, theResource)); 237 default: 238 return matchResourceParam(myStorageSettings, theParamName, theAndOrParams, theSearchParams, resourceName, paramDef); 239 } 240 } 241 242 private InMemoryMatchResult checkForUnsupportedParameters(String theParamName, RuntimeSearchParam theParamDef, List<List<IQueryParameterType>> theAndOrParams) { 243 244 if (UNSUPPORTED_PARAMETER_NAMES.contains(theParamName)) { 245 return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); 246 } 247 248 for (List<IQueryParameterType> orParams : theAndOrParams) { 249 // The list should never be empty, but better safe than sorry 250 if (orParams.size() > 0) { 251 // The params in each OR list all share the same qualifier, prefix, etc., so we only need to check the first one 252 InMemoryMatchResult checkUnsupportedResult = checkOneParameterForUnsupportedModifiers(theParamName, theParamDef, orParams.get(0)); 253 if (!checkUnsupportedResult.supported()) { 254 return checkUnsupportedResult; 255 } 256 } 257 } 258 259 return InMemoryMatchResult.successfulMatch(); 260 } 261 262 private InMemoryMatchResult checkOneParameterForUnsupportedModifiers(String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) { 263 // Assume we're ok until we find evidence we aren't 264 InMemoryMatchResult checkUnsupportedResult = InMemoryMatchResult.successfulMatch(); 265 266 if (hasChain(theParam)) { 267 checkUnsupportedResult = InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName + "." + ((ReferenceParam) theParam).getChain(), InMemoryMatchResult.CHAIN); 268 } 269 270 if (checkUnsupportedResult.supported()) { 271 checkUnsupportedResult = checkUnsupportedQualifiers(theParamName, theParamDef, theParam); 272 } 273 274 if (checkUnsupportedResult.supported()) { 275 checkUnsupportedResult = checkUnsupportedPrefixes(theParamName, theParamDef, theParam); 276 } 277 278 return checkUnsupportedResult; 279 } 280 281 private boolean matchProfilesAndOr(List<List<IQueryParameterType>> theAndOrParams, IBaseResource theResource) { 282 if (theResource == null) { 283 return true; 284 } 285 return theAndOrParams.stream().allMatch(nextAnd -> matchProfilesOr(nextAnd, theResource)); 286 } 287 288 private boolean matchProfilesOr(List<IQueryParameterType> theOrParams, IBaseResource theResource) { 289 return theOrParams.stream().anyMatch(param -> matchProfile(param, theResource)); 290 } 291 292 private boolean matchProfile(IQueryParameterType theProfileParam, IBaseResource theResource) { 293 UriParam paramProfile = new UriParam(theProfileParam.getValueAsQueryToken(myFhirContext)); 294 295 String paramProfileValue = paramProfile.getValue(); 296 if (isBlank(paramProfileValue)) { 297 return false; 298 } else { 299 return theResource.getMeta().getProfile().stream() 300 .map(IPrimitiveType::getValueAsString) 301 .anyMatch(profileValue -> profileValue != null && profileValue.equals(paramProfileValue)); 302 } 303 } 304 305 private boolean matchSourcesAndOr(List<List<IQueryParameterType>> theAndOrParams, IBaseResource theResource) { 306 if (theResource == null) { 307 return true; 308 } 309 return theAndOrParams.stream().allMatch(nextAnd -> matchSourcesOr(nextAnd, theResource)); 310 } 311 312 private boolean matchSourcesOr(List<IQueryParameterType> theOrParams, IBaseResource theResource) { 313 return theOrParams.stream().anyMatch(param -> matchSource(param, theResource)); 314 } 315 316 private boolean matchSource(IQueryParameterType theSourceParam, IBaseResource theResource) { 317 SourceParam paramSource = new SourceParam(theSourceParam.getValueAsQueryToken(myFhirContext)); 318 SourceParam resourceSource = new SourceParam(MetaUtil.getSource(myFhirContext, theResource.getMeta())); 319 boolean matches = true; 320 if (paramSource.getSourceUri() != null) { 321 matches = paramSource.getSourceUri().equals(resourceSource.getSourceUri()); 322 } 323 if (paramSource.getRequestId() != null) { 324 matches &= paramSource.getRequestId().equals(resourceSource.getRequestId()); 325 } 326 return matches; 327 } 328 329 private boolean matchTagsOrSecurityAndOr(List<List<IQueryParameterType>> theAndOrParams, IBaseResource theResource, boolean theTag) { 330 if (theResource == null) { 331 return true; 332 } 333 return theAndOrParams.stream().allMatch(nextAnd -> matchTagsOrSecurityOr(nextAnd, theResource, theTag)); 334 } 335 336 private boolean matchTagsOrSecurityOr(List<IQueryParameterType> theOrParams, IBaseResource theResource, boolean theTag) { 337 return theOrParams.stream().anyMatch(param -> matchTagOrSecurity(param, theResource, theTag)); 338 } 339 340 private boolean matchTagOrSecurity(IQueryParameterType theParam, IBaseResource theResource, boolean theTag) { 341 TokenParam param = (TokenParam) theParam; 342 343 List<? extends IBaseCoding> list; 344 if (theTag) { 345 list = theResource.getMeta().getTag(); 346 } else { 347 list = theResource.getMeta().getSecurity(); 348 } 349 boolean haveMatch = false; 350 boolean haveCandidate = false; 351 for (IBaseCoding next : list) { 352 if (param.getSystem() == null && param.getValue() == null) { 353 continue; 354 } 355 haveCandidate = true; 356 if (isNotBlank(param.getSystem())) { 357 if (!param.getSystem().equals(next.getSystem())) { 358 continue; 359 } 360 } 361 if (isNotBlank(param.getValue())) { 362 if (!param.getValue().equals(next.getCode())) { 363 continue; 364 } 365 } 366 haveMatch = true; 367 break; 368 } 369 370 if (param.getModifier() == TokenParamModifier.NOT) { 371 haveMatch = !haveMatch; 372 } 373 374 return haveMatch && haveCandidate; 375 } 376 377 private boolean matchIdsAndOr(List<List<IQueryParameterType>> theAndOrParams, IBaseResource theResource) { 378 if (theResource == null) { 379 return true; 380 } 381 return theAndOrParams.stream().allMatch(nextAnd -> matchIdsOr(nextAnd, theResource)); 382 } 383 384 private boolean matchIdsOr(List<IQueryParameterType> theOrParams, IBaseResource theResource) { 385 return theOrParams.stream().anyMatch(param -> param instanceof StringParam && matchId(((StringParam) param).getValue(), theResource.getIdElement())); 386 } 387 388 private boolean matchId(String theValue, IIdType theId) { 389 return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart()); 390 } 391 392 private InMemoryMatchResult matchResourceParam(StorageSettings theStorageSettings, String theParamName, List<List<IQueryParameterType>> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { 393 if (theParamDef != null) { 394 switch (theParamDef.getParamType()) { 395 case QUANTITY: 396 case TOKEN: 397 case STRING: 398 case NUMBER: 399 case URI: 400 case DATE: 401 case REFERENCE: 402 if (theSearchParams == null) { 403 return InMemoryMatchResult.successfulMatch(); 404 } else { 405 return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().allMatch(nextAnd -> matchParams(theStorageSettings, theResourceName, theParamName, theParamDef, nextAnd, theSearchParams))); 406 } 407 case COMPOSITE: 408 case HAS: 409 case SPECIAL: 410 default: 411 return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); 412 } 413 } else { 414 if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) { 415 return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); 416 } else { 417 throw new InvalidRequestException(Msg.code(509) + "Unknown search parameter " + theParamName + " for resource type " + theResourceName); 418 } 419 } 420 } 421 422 private boolean matchParams(StorageSettings theStorageSettings, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theOrList, ResourceIndexedSearchParams theSearchParams) { 423 424 boolean isNegativeTest = isNegative(theParamDef, theOrList); 425 // negative tests like :not and :not-in must not match any or-clause, so we invert the quantifier. 426 if (isNegativeTest) { 427 return theOrList.stream().allMatch(token -> matchParam(theStorageSettings, theResourceName, theParamName, theParamDef, theSearchParams, token)); 428 } else { 429 return theOrList.stream().anyMatch(token -> matchParam(theStorageSettings, theResourceName, theParamName, theParamDef, theSearchParams, token)); 430 } 431 } 432 433 /** 434 * Some modifiers are negative, and must match NONE of their or-list 435 */ 436 private boolean isNegative(RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theOrList) { 437 if (theParamDef.getParamType().equals(RestSearchParameterTypeEnum.TOKEN)) { 438 TokenParam tokenParam = (TokenParam) theOrList.get(0); 439 TokenParamModifier modifier = tokenParam.getModifier(); 440 return modifier != null && modifier.isNegative(); 441 } else { 442 return false; 443 } 444 445 } 446 447 private boolean matchParam(StorageSettings theStorageSettings, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, ResourceIndexedSearchParams theSearchParams, IQueryParameterType theToken) { 448 if (theParamDef.getParamType().equals(RestSearchParameterTypeEnum.TOKEN)) { 449 return matchTokenParam(theStorageSettings, theResourceName, theParamName, theParamDef, theSearchParams, (TokenParam) theToken); 450 } else { 451 return theSearchParams.matchParam(theStorageSettings, theResourceName, theParamName, theParamDef, theToken); 452 } 453 } 454 455 /** 456 * Checks whether a query parameter of type token matches one of the search parameters of an in-memory resource. 457 * The :not modifier is supported. 458 * The :in and :not-in qualifiers are supported only if a bean implementing IValidationSupport is available. 459 * Any other qualifier will be ignored and the match will be treated as unqualified. 460 * 461 * @param theStorageSettings a model configuration 462 * @param theResourceName the name of the resource type being matched 463 * @param theParamName the name of the parameter 464 * @param theParamDef the definition of the search parameter 465 * @param theSearchParams the search parameters derived from the target resource 466 * @param theQueryParam the query parameter to compare with theSearchParams 467 * @return true if theQueryParam matches the collection of theSearchParams, otherwise false 468 */ 469 private boolean matchTokenParam(StorageSettings theStorageSettings, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, ResourceIndexedSearchParams theSearchParams, TokenParam theQueryParam) { 470 if (theQueryParam.getModifier() != null) { 471 switch (theQueryParam.getModifier()) { 472 case IN: 473 return theSearchParams.myTokenParams.stream() 474 .filter(t -> t.getParamName().equals(theParamName)) 475 .anyMatch(t -> systemContainsCode(theQueryParam, t)); 476 case NOT_IN: 477 return theSearchParams.myTokenParams.stream() 478 .filter(t -> t.getParamName().equals(theParamName)) 479 .noneMatch(t -> systemContainsCode(theQueryParam, t)); 480 case NOT: 481 return !theSearchParams.matchParam(theStorageSettings, theResourceName, theParamName, theParamDef, theQueryParam); 482 default: 483 return theSearchParams.matchParam(theStorageSettings, theResourceName, theParamName, theParamDef, theQueryParam); 484 } 485 } else { 486 return theSearchParams.matchParam(theStorageSettings, theResourceName, theParamName, theParamDef, theQueryParam); 487 } 488 } 489 490 private boolean systemContainsCode(TokenParam theQueryParam, ResourceIndexedSearchParamToken theSearchParamToken) { 491 IValidationSupport validationSupport = getValidationSupportOrNull(); 492 if (validationSupport == null) { 493 ourLog.error(Msg.code(2096) + "Attempting to evaluate an unsupported qualifier. This should not happen."); 494 return false; 495 } 496 497 IValidationSupport.CodeValidationResult codeValidationResult = validationSupport.validateCode(new ValidationSupportContext(validationSupport), new ConceptValidationOptions(), theSearchParamToken.getSystem(), theSearchParamToken.getValue(), null, theQueryParam.getValue()); 498 if (codeValidationResult != null) { 499 return codeValidationResult.isOk(); 500 } else { 501 return false; 502 } 503 } 504 505 private boolean hasChain(IQueryParameterType theParam) { 506 return theParam instanceof ReferenceParam && ((ReferenceParam) theParam).getChain() != null; 507 } 508 509 private boolean hasQualifiers(IQueryParameterType theParam) { 510 return theParam.getQueryParameterQualifier() != null; 511 } 512 513 private InMemoryMatchResult checkUnsupportedPrefixes(String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) { 514 if (theParamDef != null && theParam instanceof BaseParamWithPrefix) { 515 ParamPrefixEnum prefix = ((BaseParamWithPrefix<?>) theParam).getPrefix(); 516 RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); 517 if (!supportedPrefix(prefix, paramType)) { 518 return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, String.format("The prefix %s is not supported for param type %s", prefix, paramType)); 519 } 520 } 521 return InMemoryMatchResult.successfulMatch(); 522 } 523 524 @SuppressWarnings("EnumSwitchStatementWhichMissesCases") 525 private boolean supportedPrefix(ParamPrefixEnum theParam, RestSearchParameterTypeEnum theParamType) { 526 if (theParam == null || theParamType == null) { 527 return true; 528 } 529 switch (theParamType) { 530 case DATE: 531 switch (theParam) { 532 case GREATERTHAN: 533 case GREATERTHAN_OR_EQUALS: 534 case LESSTHAN: 535 case LESSTHAN_OR_EQUALS: 536 case EQUAL: 537 return true; 538 } 539 break; 540 default: 541 return false; 542 } 543 return false; 544 } 545 546 private InMemoryMatchResult checkUnsupportedQualifiers(String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) { 547 if (hasQualifiers(theParam) && !supportedQualifier(theParamDef, theParam)) { 548 return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName + theParam.getQueryParameterQualifier(), InMemoryMatchResult.QUALIFIER); 549 } 550 return InMemoryMatchResult.successfulMatch(); 551 } 552 553 private boolean supportedQualifier(RuntimeSearchParam theParamDef, IQueryParameterType theParam) { 554 if (theParamDef == null || theParam == null) { 555 return true; 556 } 557 switch (theParamDef.getParamType()) { 558 case TOKEN: 559 TokenParam tokenParam = (TokenParam) theParam; 560 switch (tokenParam.getModifier()) { 561 case IN: 562 case NOT_IN: 563 // Support for these qualifiers is dependent on an implementation of IValidationSupport being available to delegate the check to 564 return getValidationSupportOrNull() != null; 565 case NOT: 566 return true; 567 default: 568 return false; 569 } 570 default: 571 return false; 572 } 573 } 574 575 private enum ValidationSupportInitializationState {NOT_INITIALIZED, INITIALIZED, FAILED} 576 577}