
001package ca.uhn.fhir.jpa.dao.predicate; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.ConfigurationException; 026import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; 027import ca.uhn.fhir.context.RuntimeChildResourceDefinition; 028import ca.uhn.fhir.context.RuntimeResourceDefinition; 029import ca.uhn.fhir.context.RuntimeSearchParam; 030import ca.uhn.fhir.i18n.Msg; 031import ca.uhn.fhir.interceptor.api.HookParams; 032import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 033import ca.uhn.fhir.interceptor.api.Pointcut; 034import ca.uhn.fhir.interceptor.model.RequestPartitionId; 035import ca.uhn.fhir.jpa.api.config.DaoConfig; 036import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 037import ca.uhn.fhir.jpa.api.dao.IDao; 038import ca.uhn.fhir.jpa.api.svc.IIdHelperService; 039import ca.uhn.fhir.jpa.dao.BaseStorageDao; 040import ca.uhn.fhir.jpa.dao.LegacySearchBuilder; 041import ca.uhn.fhir.jpa.model.config.PartitionSettings; 042import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity; 043import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; 044import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; 045import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 046import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; 047import ca.uhn.fhir.jpa.model.entity.ResourceLink; 048import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; 049import ca.uhn.fhir.jpa.searchparam.MatchUrlService; 050import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; 051import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; 052import ca.uhn.fhir.jpa.searchparam.util.SourceParam; 053import ca.uhn.fhir.model.api.IQueryParameterAnd; 054import ca.uhn.fhir.model.api.IQueryParameterOr; 055import ca.uhn.fhir.model.api.IQueryParameterType; 056import ca.uhn.fhir.model.primitive.IdDt; 057import ca.uhn.fhir.parser.DataFormatException; 058import ca.uhn.fhir.rest.api.Constants; 059import ca.uhn.fhir.rest.api.QualifiedParamList; 060import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 061import ca.uhn.fhir.rest.api.server.RequestDetails; 062import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; 063import ca.uhn.fhir.rest.param.CompositeParam; 064import ca.uhn.fhir.rest.param.DateParam; 065import ca.uhn.fhir.rest.param.HasParam; 066import ca.uhn.fhir.rest.param.NumberParam; 067import ca.uhn.fhir.rest.param.QuantityParam; 068import ca.uhn.fhir.rest.param.ReferenceParam; 069import ca.uhn.fhir.rest.param.SpecialParam; 070import ca.uhn.fhir.rest.param.StringParam; 071import ca.uhn.fhir.rest.param.TokenParam; 072import ca.uhn.fhir.rest.param.UriParam; 073import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 074import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 075import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; 076import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 077import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 078import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 079import com.google.common.collect.Lists; 080import org.hl7.fhir.instance.model.api.IAnyResource; 081import org.hl7.fhir.instance.model.api.IBaseResource; 082import org.hl7.fhir.instance.model.api.IIdType; 083import org.slf4j.Logger; 084import org.slf4j.LoggerFactory; 085import org.springframework.beans.factory.annotation.Autowired; 086import org.springframework.context.annotation.Scope; 087import org.springframework.stereotype.Component; 088 089import javax.annotation.Nonnull; 090import javax.annotation.Nullable; 091import javax.persistence.criteria.From; 092import javax.persistence.criteria.Join; 093import javax.persistence.criteria.JoinType; 094import javax.persistence.criteria.Predicate; 095import javax.persistence.criteria.Root; 096import javax.persistence.criteria.Subquery; 097import java.util.ArrayList; 098import java.util.Collection; 099import java.util.Collections; 100import java.util.List; 101import java.util.ListIterator; 102import java.util.Set; 103import java.util.stream.Collectors; 104 105import static ca.uhn.fhir.jpa.search.builder.QueryStack.fromOperation; 106import static org.apache.commons.lang3.StringUtils.isBlank; 107import static org.apache.commons.lang3.StringUtils.isNotBlank; 108import static org.apache.commons.lang3.StringUtils.trim; 109 110@Component 111@Scope("prototype") 112public 113class PredicateBuilderReference extends BasePredicateBuilder { 114 private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class); 115 private final PredicateBuilder myPredicateBuilder; 116 @Autowired 117 IIdHelperService myIdHelperService; 118 @Autowired 119 ISearchParamRegistry mySearchParamRegistry; 120 @Autowired 121 MatchUrlService myMatchUrlService; 122 @Autowired 123 DaoRegistry myDaoRegistry; 124 @Autowired 125 PartitionSettings myPartitionSettings; 126 @Autowired 127 private IInterceptorBroadcaster myInterceptorBroadcaster; 128 129 public PredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) { 130 super(theSearchBuilder); 131 myPredicateBuilder = thePredicateBuilder; 132 } 133 134 /** 135 * Add reference predicate to the current search 136 */ 137 138 public Predicate addPredicate(String theResourceName, 139 String theParamName, 140 List<? extends IQueryParameterType> theList, 141 SearchFilterParser.CompareOperation operation, 142 RequestDetails theRequest, 143 RequestPartitionId theRequestPartitionId) { 144 145 // This just to ensure the chain has been split correctly 146 assert theParamName.contains(".") == false; 147 148 if ((operation != null) && 149 (operation != SearchFilterParser.CompareOperation.eq) && 150 (operation != SearchFilterParser.CompareOperation.ne)) { 151 throw new InvalidRequestException(Msg.code(1008) + "Invalid operator specified for reference predicate. Supported operators for reference predicate are \"eq\" and \"ne\"."); 152 } 153 154 if (theList.get(0).getMissing() != null) { 155 addPredicateParamMissingForReference(theResourceName, theParamName, theList.get(0).getMissing(), theRequestPartitionId); 156 return null; 157 } 158 159 From<?, ResourceLink> join = myQueryStack.createJoin(SearchBuilderJoinEnum.REFERENCE, theParamName); 160 161 List<IIdType> targetIds = new ArrayList<>(); 162 List<String> targetQualifiedUrls = new ArrayList<>(); 163 164 for (int orIdx = 0; orIdx < theList.size(); orIdx++) { 165 IQueryParameterType nextOr = theList.get(orIdx); 166 167 if (nextOr instanceof ReferenceParam) { 168 ReferenceParam ref = (ReferenceParam) nextOr; 169 170 if (isBlank(ref.getChain())) { 171 172 /* 173 * Handle non-chained search, e.g. Patient?organization=Organization/123 174 */ 175 176 IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null); 177 178 if (dt.hasBaseUrl()) { 179 if (myDaoConfig.getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) { 180 dt = dt.toUnqualified(); 181 targetIds.add(dt); 182 } else { 183 targetQualifiedUrls.add(dt.getValue()); 184 } 185 } else { 186 targetIds.add(dt); 187 } 188 189 } else { 190 191 /* 192 * Handle chained search, e.g. Patient?organization.name=Kwik-e-mart 193 */ 194 195 return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest, theRequestPartitionId); 196 197 } 198 199 } else { 200 throw new IllegalArgumentException(Msg.code(1009) + "Invalid token type (expecting ReferenceParam): " + nextOr.getClass()); 201 } 202 203 } 204 205 List<Predicate> codePredicates = new ArrayList<>(); 206 addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); 207 208 for (IIdType next : targetIds) { 209 if (!next.hasResourceType()) { 210 warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, null); 211 } 212 } 213 214 // Resources by ID 215 List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIdsWithCache(theRequestPartitionId, targetIds); 216 if (!targetPids.isEmpty()) { 217 ourLog.debug("Searching for resource link with target PIDs: {}", targetPids); 218 Predicate pathPredicate; 219 if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { 220 pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join); 221 } else { 222 pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join).not(); 223 } 224 Predicate pidPredicate; 225 if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { 226 if (targetPids.size() == 1) { 227 pidPredicate = myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), targetPids.get(0).getIdAsLong()); 228 } else { 229 pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)); 230 } 231 } else { 232 pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)).not(); 233 } 234 codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate)); 235 } 236 237 // Resources by fully qualified URL 238 if (!targetQualifiedUrls.isEmpty()) { 239 ourLog.debug("Searching for resource link with target URLs: {}", targetQualifiedUrls); 240 Predicate pathPredicate; 241 if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { 242 pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join); 243 } else { 244 pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join).not(); 245 } 246 Predicate pidPredicate; 247 if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { 248 pidPredicate = join.get("myTargetResourceUrl").in(targetQualifiedUrls); 249 } else { 250 pidPredicate = join.get("myTargetResourceUrl").in(targetQualifiedUrls).not(); 251 } 252 codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate)); 253 } 254 255 if (codePredicates.size() > 0) { 256 Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates)); 257 myQueryStack.addPredicateWithImplicitTypeSelection(predicate); 258 return predicate; 259 } else { 260 return myQueryStack.addNeverMatchingPredicate(); 261 } 262 } 263 264 /** 265 * This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain 266 * on the device. 267 */ 268 private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, From<?, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { 269 270 /* 271 * Which resource types can the given chained parameter actually link to? This might be a list 272 * where the chain is unqualified, as in: Observation?subject.identifier=(...) 273 * since subject can link to several possible target types. 274 * 275 * If the user has qualified the chain, as in: Observation?subject:Patient.identifier=(...) 276 * this is just a simple 1-entry list. 277 */ 278 final List<Class<? extends IBaseResource>> resourceTypes = determineCandidateResourceTypesForChain(theResourceName, theParamName, theReferenceParam); 279 280 /* 281 * Handle chain on _type 282 */ 283 if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) { 284 return createChainPredicateOnType(theResourceName, theParamName, theJoin, theReferenceParam, resourceTypes); 285 } 286 287 boolean foundChainMatch = false; 288 List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>(); 289 for (Class<? extends IBaseResource> nextType : resourceTypes) { 290 String chain = theReferenceParam.getChain(); 291 292 String remainingChain = null; 293 int chainDotIndex = chain.indexOf('.'); 294 if (chainDotIndex != -1) { 295 remainingChain = chain.substring(chainDotIndex + 1); 296 chain = chain.substring(0, chainDotIndex); 297 } 298 299 RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType); 300 String subResourceName = typeDef.getName(); 301 302 IDao dao = myDaoRegistry.getResourceDao(nextType); 303 if (dao == null) { 304 ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName()); 305 continue; 306 } 307 308 int qualifierIndex = chain.indexOf(':'); 309 String qualifier = null; 310 if (qualifierIndex != -1) { 311 qualifier = chain.substring(qualifierIndex); 312 chain = chain.substring(0, qualifierIndex); 313 } 314 315 boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain); 316 RuntimeSearchParam param = null; 317 if (!isMeta) { 318 param = mySearchParamRegistry.getActiveSearchParam(subResourceName, chain); 319 if (param == null) { 320 ourLog.debug("Type {} doesn't have search param {}", subResourceName, param); 321 continue; 322 } 323 } 324 325 ArrayList<IQueryParameterType> orValues = Lists.newArrayList(); 326 327 for (IQueryParameterType next : theList) { 328 String nextValue = next.getValueAsQueryToken(myContext); 329 IQueryParameterType chainValue = mapReferenceChainToRawParamType(remainingChain, param, theParamName, qualifier, nextType, chain, isMeta, nextValue); 330 if (chainValue == null) { 331 continue; 332 } 333 foundChainMatch = true; 334 orValues.add(chainValue); 335 } 336 337 // If this is false, we throw an exception below so no sense doing any further processing 338 if (foundChainMatch) { 339 Subquery<Long> subQ = createLinkSubquery(chain, subResourceName, orValues, theRequest, theRequestPartitionId); 340 341 Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin); 342 Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ); 343 Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, pidPredicate); 344 theCodePredicates.add(andPredicate); 345 candidateTargetTypes.add(nextType); 346 } 347 } 348 349 if (!foundChainMatch) { 350 throw new InvalidRequestException(Msg.code(1010) + myContext.getLocalizer().getMessage(BaseStorageDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain())); 351 } 352 353 if (candidateTargetTypes.size() > 1) { 354 warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, candidateTargetTypes); 355 } 356 357 Predicate predicate = myCriteriaBuilder.or(toArray(theCodePredicates)); 358 myQueryStack.addPredicateWithImplicitTypeSelection(predicate); 359 return predicate; 360 } 361 362 private Predicate createChainPredicateOnType(String theResourceName, String theParamName, From<?, ResourceLink> theJoin, ReferenceParam theReferenceParam, List<Class<? extends IBaseResource>> theResourceTypes) { 363 String typeValue = theReferenceParam.getValue(); 364 365 Class<? extends IBaseResource> wantedType; 366 try { 367 wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass(); 368 } catch (DataFormatException e) { 369 throw newInvalidResourceTypeException(typeValue); 370 } 371 if (!theResourceTypes.contains(wantedType)) { 372 throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue); 373 } 374 375 Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin); 376 Predicate sourceTypeParameter = myCriteriaBuilder.equal(theJoin.get("mySourceResourceType"), myResourceName); 377 Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue); 378 379 Predicate composite = myCriteriaBuilder.and(pathPredicate, sourceTypeParameter, targetTypeParameter); 380 myQueryStack.addPredicate(composite); 381 return composite; 382 } 383 384 @Nonnull 385 private List<Class<? extends IBaseResource>> determineCandidateResourceTypesForChain(String theResourceName, String theParamName, ReferenceParam theReferenceParam) { 386 final List<Class<? extends IBaseResource>> resourceTypes; 387 if (!theReferenceParam.hasResourceType()) { 388 389 RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); 390 resourceTypes = new ArrayList<>(); 391 392 if (param.hasTargets()) { 393 Set<String> targetTypes = param.getTargets(); 394 for (String next : targetTypes) { 395 resourceTypes.add(myContext.getResourceDefinition(next).getImplementingClass()); 396 } 397 } 398 399 if (resourceTypes.isEmpty()) { 400 RuntimeSearchParam searchParamByName = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); 401 if (searchParamByName == null) { 402 throw new InternalErrorException(Msg.code(1011) + "Could not find parameter " + theParamName); 403 } 404 String paramPath = searchParamByName.getPath(); 405 if (paramPath.endsWith(".as(Reference)")) { 406 paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference"; 407 } 408 409 if (paramPath.contains(".extension(")) { 410 int startIdx = paramPath.indexOf(".extension("); 411 int endIdx = paramPath.indexOf(')', startIdx); 412 if (startIdx != -1 && endIdx != -1) { 413 paramPath = paramPath.substring(0, startIdx + 10) + paramPath.substring(endIdx + 1); 414 } 415 } 416 417 BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, paramPath); 418 if (def instanceof RuntimeChildChoiceDefinition) { 419 RuntimeChildChoiceDefinition choiceDef = (RuntimeChildChoiceDefinition) def; 420 resourceTypes.addAll(choiceDef.getResourceTypes()); 421 } else if (def instanceof RuntimeChildResourceDefinition) { 422 RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def; 423 resourceTypes.addAll(resDef.getResourceTypes()); 424 if (resourceTypes.size() == 1) { 425 if (resourceTypes.get(0).isInterface()) { 426 throw new InvalidRequestException(Msg.code(1012) + "Unable to perform search for unqualified chain '" + theParamName + "' as this SearchParameter does not declare any target types. Add a qualifier of the form '" + theParamName + ":[ResourceType]' to perform this search."); 427 } 428 } 429 } else { 430 throw new ConfigurationException(Msg.code(1013) + "Property " + paramPath + " of type " + myResourceName + " is not a resource: " + def.getClass()); 431 } 432 } 433 434 if (resourceTypes.isEmpty()) { 435 for (BaseRuntimeElementDefinition<?> next : myContext.getElementDefinitions()) { 436 if (next instanceof RuntimeResourceDefinition) { 437 RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition) next; 438 resourceTypes.add(nextResDef.getImplementingClass()); 439 } 440 } 441 } 442 443 } else { 444 445 try { 446 RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType()); 447 resourceTypes = new ArrayList<>(1); 448 resourceTypes.add(resDef.getImplementingClass()); 449 } catch (DataFormatException e) { 450 throw newInvalidResourceTypeException(theReferenceParam.getResourceType()); 451 } 452 453 } 454 return resourceTypes; 455 } 456 457 private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, @Nullable List<Class<? extends IBaseResource>> theCandidateTargetTypes) { 458 StringBuilder builder = new StringBuilder(); 459 builder.append("This search uses an unqualified resource(a parameter in a chain without a resource type). "); 460 builder.append("This is less efficient than using a qualified type. "); 461 if (theCandidateTargetTypes != null) { 462 builder.append("[" + theParamName + "] resolves to [" + theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) + "]."); 463 builder.append("If you know what you're looking for, try qualifying it using the form "); 464 builder.append(theCandidateTargetTypes.stream().map(cls -> "[" + cls.getSimpleName() + ":" + theParamName + "]").collect(Collectors.joining(" or "))); 465 } else { 466 builder.append("If you know what you're looking for, try qualifying it using the form: '"); 467 builder.append(theParamName).append(":[resourceType]"); 468 builder.append("'"); 469 } 470 String message = builder 471 .toString(); 472 StorageProcessingMessage msg = new StorageProcessingMessage() 473 .setMessage(message); 474 HookParams params = new HookParams() 475 .add(RequestDetails.class, theRequest) 476 .addIfMatchesType(ServletRequestDetails.class, theRequest) 477 .add(StorageProcessingMessage.class, msg); 478 CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params); 479 } 480 481 Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From<?, ? extends ResourceLink> from) { 482 RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); 483 List<String> path = param.getPathsSplit(); 484 485 /* 486 * SearchParameters can declare paths on multiple resource 487 * types. Here we only want the ones that actually apply. 488 */ 489 path = new ArrayList<>(path); 490 491 ListIterator<String> iter = path.listIterator(); 492 while (iter.hasNext()) { 493 String nextPath = trim(iter.next()); 494 if (!nextPath.contains(theResourceName + ".")) { 495 iter.remove(); 496 } 497 } 498 499 // multiple values 500 return from.get("mySourcePath").in(path); 501 } 502 503 private IQueryParameterType mapReferenceChainToRawParamType(String remainingChain, RuntimeSearchParam param, String theParamName, String qualifier, Class<? extends IBaseResource> nextType, String chain, boolean isMeta, String resourceId) { 504 IQueryParameterType chainValue; 505 if (remainingChain != null) { 506 if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) { 507 ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", nextType.getSimpleName(), chain, remainingChain); 508 return null; 509 } 510 511 chainValue = new ReferenceParam(); 512 chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId); 513 ((ReferenceParam) chainValue).setChain(remainingChain); 514 } else if (isMeta) { 515 IQueryParameterType type = myMatchUrlService.newInstanceType(chain); 516 type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId); 517 chainValue = type; 518 } else { 519 chainValue = toParameterType(param, qualifier, resourceId); 520 } 521 522 return chainValue; 523 } 524 525 Subquery<Long> createLinkSubquery(String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { 526 527 /* 528 * We're doing a chain call, so push the current query root 529 * and predicate list down and put new ones at the top of the 530 * stack and run a subquery 531 */ 532 RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theSubResourceName, theChain); 533 if (nextParamDef != null && !theChain.startsWith("_")) { 534 myQueryStack.pushIndexTableSubQuery(); 535 } else { 536 myQueryStack.pushResourceTableSubQuery(theSubResourceName); 537 } 538 539 List<List<IQueryParameterType>> andOrParams = new ArrayList<>(); 540 andOrParams.add(theOrValues); 541 542 searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, theRequestPartitionId); 543 544 /* 545 * Pop the old query root and predicate list back 546 */ 547 return (Subquery<Long>) myQueryStack.pop(); 548 549 } 550 551 void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { 552 553 if (theAndOrParams.isEmpty()) { 554 return; 555 } 556 557 switch (theParamName) { 558 case IAnyResource.SP_RES_ID: 559 myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, theRequestPartitionId); 560 break; 561 562 case Constants.PARAM_HAS: 563 addPredicateHas(theResourceName, theAndOrParams, theRequest, theRequestPartitionId); 564 break; 565 566 case Constants.PARAM_TAG: 567 case Constants.PARAM_PROFILE: 568 case Constants.PARAM_SECURITY: 569 myPredicateBuilder.addPredicateTag(theAndOrParams, theParamName, theRequestPartitionId); 570 break; 571 572 case Constants.PARAM_SOURCE: 573 addPredicateSource(theAndOrParams, theRequest); 574 break; 575 576 default: 577 578 RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); 579 if (nextParamDef != null) { 580 581 if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.isIncludePartitionInSearchHashes()) { 582 if (theRequestPartitionId.isAllPartitions()) { 583 throw new PreconditionFailedException(Msg.code(1014) + "This server is not configured to support search against all partitions"); 584 } 585 } 586 587 switch (nextParamDef.getParamType()) { 588 case DATE: 589 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 590 // FT: 2021-01-18 use operation 'gt', 'ge', 'le' or 'lt' 591 // to create the predicateDate instead of generic one with operation = null 592 SearchFilterParser.CompareOperation operation = null; 593 if (nextAnd.size() > 0) { 594 DateParam param = (DateParam) nextAnd.get(0); 595 operation = ca.uhn.fhir.jpa.search.builder.QueryStack.toOperation(param.getPrefix()); 596 } 597 myPredicateBuilder.addPredicateDate(theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId); 598 } 599 break; 600 case QUANTITY: 601 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 602 myPredicateBuilder.addPredicateQuantity(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); 603 } 604 break; 605 case REFERENCE: 606 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 607 addPredicate(theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId); 608 } 609 break; 610 case STRING: 611 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 612 myPredicateBuilder.addPredicateString(theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); 613 } 614 break; 615 case TOKEN: 616 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 617 if ("Location.position".equals(nextParamDef.getPath())) { 618 myPredicateBuilder.addPredicateCoords(theResourceName, nextParamDef, nextAnd, theRequestPartitionId); 619 } else { 620 myPredicateBuilder.addPredicateToken(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); 621 } 622 } 623 break; 624 case NUMBER: 625 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 626 myPredicateBuilder.addPredicateNumber(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); 627 } 628 break; 629 case COMPOSITE: 630 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 631 addPredicateComposite(theResourceName, nextParamDef, nextAnd, theRequestPartitionId); 632 } 633 break; 634 case URI: 635 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 636 myPredicateBuilder.addPredicateUri(theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.eq, theRequestPartitionId); 637 } 638 break; 639 case HAS: 640 case SPECIAL: 641 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 642 if ("Location.position".equals(nextParamDef.getPath())) { 643 myPredicateBuilder.addPredicateCoords(theResourceName, nextParamDef, nextAnd, theRequestPartitionId); 644 } 645 } 646 break; 647 } 648 } else { 649 if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) { 650 // These are handled later 651 } else if (Constants.PARAM_FILTER.equals(theParamName)) { 652 // Parse the predicates enumerated in the _filter separated by AND or OR... 653 if (theAndOrParams.get(0).get(0) instanceof StringParam) { 654 String filterString = ((StringParam) theAndOrParams.get(0).get(0)).getValue(); 655 SearchFilterParser.Filter filter; 656 try { 657 filter = SearchFilterParser.parse(filterString); 658 } catch (SearchFilterParser.FilterSyntaxException theE) { 659 throw new InvalidRequestException(Msg.code(1015) + "Error parsing _filter syntax: " + theE.getMessage()); 660 } 661 if (filter != null) { 662 663 if (!myDaoConfig.isFilterParameterEnabled()) { 664 throw new InvalidRequestException(Msg.code(1016) + Constants.PARAM_FILTER + " parameter is disabled on this server"); 665 } 666 667 // TODO: we clear the predicates below because the filter builds up 668 // its own collection of predicates. It'd probably be good at some 669 // point to do something more fancy... 670 ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryStack.getPredicates()); 671 672 Predicate filterPredicate = processFilter(filter, theResourceName, theRequest, theRequestPartitionId); 673 myQueryStack.clearPredicates(); 674 myQueryStack.addPredicates(holdPredicates); 675 myQueryStack.addPredicate(filterPredicate); 676 677 // Because filters can have an OR at the root, we never know for sure that we haven't done an optimized 678 // search that doesn't check the resource type. This could be improved in the future, but for now it's 679 // safest to just clear this flag. The test "testRetrieveDifferentTypeEq" will fail if we don't clear 680 // this here. 681 myQueryStack.clearHasImplicitTypeSelection(); 682 } 683 } 684 685 } else { 686 Collection<String> validNames = mySearchParamRegistry.getValidSearchParameterNamesIncludingMeta(theResourceName); 687 String msg = myContext.getLocalizer().getMessageSanitized(BaseStorageDao.class, "invalidSearchParameter", theParamName, theResourceName, validNames); 688 throw new InvalidRequestException(Msg.code(1017) + msg); 689 } 690 } 691 break; 692 } 693 } 694 695 private Predicate processFilter(SearchFilterParser.Filter theFilter, String theResourceName, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { 696 697 if (theFilter instanceof SearchFilterParser.FilterParameter) { 698 return processFilterParameter((SearchFilterParser.FilterParameter) theFilter, theResourceName, theRequest, theRequestPartitionId); 699 } else if (theFilter instanceof SearchFilterParser.FilterLogical) { 700 // Left side 701 Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), theResourceName, theRequest, theRequestPartitionId); 702 703 // Right side 704 Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, theRequest, theRequestPartitionId); 705 706 if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { 707 return myCriteriaBuilder.and(xPredicate, yPredicate); 708 } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { 709 return myCriteriaBuilder.or(xPredicate, yPredicate); 710 } 711 } else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) { 712 return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), theResourceName, theRequest, theRequestPartitionId); 713 } 714 return null; 715 } 716 717 private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter, 718 String theResourceName, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { 719 720 if (theFilter.getParamPath().getName().equals(Constants.PARAM_SOURCE)) { 721 TokenParam param = new TokenParam(); 722 param.setValueAsQueryToken(null, null, null, theFilter.getValue()); 723 return addPredicateSource(Collections.singletonList(param), theFilter.getOperation(), theRequest); 724 } else if (theFilter.getParamPath().getName().equals(IAnyResource.SP_RES_ID)) { 725 TokenParam param = new TokenParam(); 726 param.setValueAsQueryToken(null, 727 null, 728 null, 729 theFilter.getValue()); 730 return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequestPartitionId); 731 } 732 733 RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName()); 734 735 if (searchParam == null) { 736 throw new InvalidRequestException(Msg.code(1018) + "Invalid search parameter specified, " + theFilter.getParamPath().getName() + ", for resource type " + theResourceName); 737 } 738 739 RestSearchParameterTypeEnum typeEnum = searchParam.getParamType(); 740 if (typeEnum == RestSearchParameterTypeEnum.URI) { 741 return myPredicateBuilder.addPredicateUri(theResourceName, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); 742 } else if (typeEnum == RestSearchParameterTypeEnum.STRING) { 743 return myPredicateBuilder.addPredicateString(theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); 744 } else if (typeEnum == RestSearchParameterTypeEnum.DATE) { 745 return myPredicateBuilder.addPredicateDate(theResourceName, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); 746 } else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) { 747 return myPredicateBuilder.addPredicateNumber(theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); 748 } else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) { 749 String paramName = theFilter.getParamPath().getName(); 750 SearchFilterParser.CompareOperation operation = theFilter.getOperation(); 751 String resourceType = null; // The value can either have (Patient/123) or not have (123) a resource type, either way it's not needed here 752 String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null; 753 String value = theFilter.getValue(); 754 ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value); 755 return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId); 756 } else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) { 757 return myPredicateBuilder.addPredicateQuantity(theResourceName, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); 758 } else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) { 759 throw new InvalidRequestException(Msg.code(1019) + "Composite search parameters not currently supported with _filter clauses"); 760 } else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) { 761 TokenParam param = new TokenParam(); 762 param.setValueAsQueryToken(null, 763 null, 764 null, 765 theFilter.getValue()); 766 return myPredicateBuilder.addPredicateToken(theResourceName, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId); 767 } 768 769 return null; 770 } 771 772 private IQueryParameterType toParameterType(RuntimeSearchParam theParam) { 773 IQueryParameterType qp; 774 switch (theParam.getParamType()) { 775 case DATE: 776 qp = new DateParam(); 777 break; 778 case NUMBER: 779 qp = new NumberParam(); 780 break; 781 case QUANTITY: 782 qp = new QuantityParam(); 783 break; 784 case STRING: 785 qp = new StringParam(); 786 break; 787 case TOKEN: 788 qp = new TokenParam(); 789 break; 790 case COMPOSITE: 791 List<RuntimeSearchParam> compositeOf = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParam); 792 if (compositeOf.size() != 2) { 793 throw new InternalErrorException(Msg.code(1020) + "Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this."); 794 } 795 IQueryParameterType leftParam = toParameterType(compositeOf.get(0)); 796 IQueryParameterType rightParam = toParameterType(compositeOf.get(1)); 797 qp = new CompositeParam<>(leftParam, rightParam); 798 break; 799 case REFERENCE: 800 qp = new ReferenceParam(); 801 break; 802 case SPECIAL: 803 if ("Location.position".equals(theParam.getPath())) { 804 qp = new SpecialParam(); 805 break; 806 } 807 throw new InternalErrorException(Msg.code(1021) + "Don't know how to convert param type: " + theParam.getParamType()); 808 case URI: 809 case HAS: 810 default: 811 throw new InternalErrorException(Msg.code(1022) + "Don't know how to convert param type: " + theParam.getParamType()); 812 } 813 return qp; 814 } 815 816 private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) { 817 IQueryParameterType qp = toParameterType(theParam); 818 819 qp.setValueAsQueryToken(myContext, theParam.getName(), theQualifier, theValueAsQueryToken); 820 return qp; 821 } 822 823 private void addPredicateSource(List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest) { 824 for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { 825 addPredicateSource(nextAnd, SearchFilterParser.CompareOperation.eq, theRequest); 826 } 827 } 828 829 private Predicate addPredicateSource(List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { 830 // Required for now 831 assert theOperation == SearchFilterParser.CompareOperation.eq; 832 833 if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) { 834 String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "sourceParamDisabled"); 835 throw new InvalidRequestException(Msg.code(1023) + msg); 836 } 837 838 From<?, ResourceHistoryProvenanceEntity> join = myQueryStack.createJoin(SearchBuilderJoinEnum.PROVENANCE, Constants.PARAM_SOURCE); 839 840 List<Predicate> codePredicates = new ArrayList<>(); 841 842 for (IQueryParameterType nextParameter : theList) { 843 SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext)); 844 String sourceUri = sourceParameter.getSourceUri(); 845 String requestId = sourceParameter.getRequestId(); 846 Predicate sourceUriPredicate = myCriteriaBuilder.equal(join.get("mySourceUri"), sourceUri); 847 Predicate requestIdPredicate = myCriteriaBuilder.equal(join.get("myRequestId"), requestId); 848 if (isNotBlank(sourceUri) && isNotBlank(requestId)) { 849 codePredicates.add(myCriteriaBuilder.and(sourceUriPredicate, requestIdPredicate)); 850 } else if (isNotBlank(sourceUri)) { 851 codePredicates.add(sourceUriPredicate); 852 } else if (isNotBlank(requestId)) { 853 codePredicates.add(requestIdPredicate); 854 } 855 } 856 857 Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates)); 858 myQueryStack.addPredicate(retVal); 859 return retVal; 860 } 861 862 private void addPredicateHas(String theResourceType, List<List<IQueryParameterType>> theHasParameters, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { 863 864 for (List<? extends IQueryParameterType> nextOrList : theHasParameters) { 865 866 String targetResourceType = null; 867 String paramReference = null; 868 String parameterName = null; 869 870 String paramName = null; 871 List<QualifiedParamList> parameters = new ArrayList<>(); 872 for (IQueryParameterType nextParam : nextOrList) { 873 HasParam next = (HasParam) nextParam; 874 targetResourceType = next.getTargetResourceType(); 875 paramReference = next.getReferenceFieldName(); 876 parameterName = next.getParameterName(); 877 paramName = parameterName.replaceAll("\\..*", ""); 878 parameters.add(QualifiedParamList.singleton(null, next.getValueAsQueryToken(myContext))); 879 } 880 881 if (paramName == null) { 882 continue; 883 } 884 885 RuntimeResourceDefinition targetResourceDefinition; 886 try { 887 targetResourceDefinition = myContext.getResourceDefinition(targetResourceType); 888 } catch (DataFormatException e) { 889 throw new InvalidRequestException(Msg.code(1024) + "Invalid resource type: " + targetResourceType); 890 } 891 892 ArrayList<IQueryParameterType> orValues = Lists.newArrayList(); 893 894 if (paramName.startsWith("_has:")) { 895 896 ourLog.trace("Handing double _has query: {}", paramName); 897 898 String qualifier = paramName.substring(4); 899 paramName = Constants.PARAM_HAS; 900 for (IQueryParameterType next : nextOrList) { 901 HasParam nextHasParam = new HasParam(); 902 nextHasParam.setValueAsQueryToken(myContext, Constants.PARAM_HAS, qualifier, next.getValueAsQueryToken(myContext)); 903 orValues.add(nextHasParam); 904 } 905 906 } else { 907 908 //Ensure that the name of the search param 909 // (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val) 910 // exists on the target resource type. 911 RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getActiveSearchParam(targetResourceType, paramName); 912 if (owningParameterDef == null) { 913 throw new InvalidRequestException(Msg.code(1025) + "Unknown parameter name: " + targetResourceType + ':' + parameterName); 914 } 915 916 //Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val) 917 //exists on the target resource. 918 RuntimeSearchParam joiningParameterDef = mySearchParamRegistry.getActiveSearchParam(targetResourceType, paramReference); 919 if (joiningParameterDef == null) { 920 throw new InvalidRequestException(Msg.code(1026) + "Unknown parameter name: " + targetResourceType + ':' + paramReference); 921 } 922 923 IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>> parsedParam = (IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>>) JpaParamUtil.parseQueryParams(mySearchParamRegistry, myContext, owningParameterDef, paramName, parameters); 924 925 for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) { 926 orValues.addAll(next.getValuesAsQueryTokens()); 927 } 928 929 } 930 931 //Handle internal chain inside the has. 932 if (parameterName.contains(".")) { 933 String chainedPartOfParameter = getChainedPart(parameterName); 934 orValues.stream() 935 .filter(qp -> qp instanceof ReferenceParam) 936 .map(qp -> (ReferenceParam) qp) 937 .forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter))); 938 } 939 940 Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest, theRequestPartitionId); 941 Join<?, ResourceLink> join = (Join) myQueryStack.createJoin(SearchBuilderJoinEnum.HAS, "_has"); 942 943 Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join); 944 Predicate sourceTypePredicate = myCriteriaBuilder.equal(join.get("myTargetResourceType"), theResourceType); 945 Predicate sourcePidPredicate = join.get("mySourceResourcePid").in(subQ); 946 Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, sourcePidPredicate, sourceTypePredicate); 947 myQueryStack.addPredicate(andPredicate); 948 } 949 } 950 951 private String getChainedPart(String parameter) { 952 return parameter.substring(parameter.indexOf(".") + 1); 953 } 954 955 private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) { 956 // TODO: fail if missing is set for a composite query 957 958 IQueryParameterType or = theNextAnd.get(0); 959 if (!(or instanceof CompositeParam<?, ?>)) { 960 throw new InvalidRequestException(Msg.code(1027) + "Invalid type for composite param (must be " + CompositeParam.class.getSimpleName() + ": " + or.getClass()); 961 } 962 CompositeParam<?, ?> cp = (CompositeParam<?, ?>) or; 963 964 List<RuntimeSearchParam> componentParams = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParamDef); 965 RuntimeSearchParam left = componentParams.get(0); 966 IQueryParameterType leftValue = cp.getLeftValue(); 967 myQueryStack.addPredicate(createCompositeParamPart(theResourceName, myQueryStack.getRootForComposite(), left, leftValue, theRequestPartitionId)); 968 969 RuntimeSearchParam right = componentParams.get(1); 970 IQueryParameterType rightValue = cp.getRightValue(); 971 myQueryStack.addPredicate(createCompositeParamPart(theResourceName, myQueryStack.getRootForComposite(), right, rightValue, theRequestPartitionId)); 972 973 } 974 975 private Predicate createCompositeParamPart(String theResourceName, Root<?> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue, RequestPartitionId theRequestPartitionId) { 976 Predicate retVal = null; 977 switch (theParam.getParamType()) { 978 case STRING: { 979 From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER); 980 retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam, myCriteriaBuilder, stringJoin, theRequestPartitionId); 981 break; 982 } 983 case TOKEN: { 984 From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER); 985 List<IQueryParameterType> tokens = Collections.singletonList(leftValue); 986 Collection<Predicate> tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam, myCriteriaBuilder, tokenJoin, theRequestPartitionId); 987 retVal = myCriteriaBuilder.and(tokenPredicates.toArray(new Predicate[0])); 988 break; 989 } 990 case DATE: { 991 From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER); 992 retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin, theRequestPartitionId); 993 break; 994 } 995 case QUANTITY: { 996 From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER); 997 retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin, theRequestPartitionId); 998 break; 999 } 1000 case COMPOSITE: 1001 case HAS: 1002 case NUMBER: 1003 case REFERENCE: 1004 case URI: 1005 case SPECIAL: 1006 break; 1007 } 1008 1009 if (retVal == null) { 1010 throw new InvalidRequestException(Msg.code(1028) + "Don't know how to handle composite parameter with type of " + theParam.getParamType()); 1011 } 1012 1013 return retVal; 1014 } 1015 1016 @Nonnull 1017 private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) { 1018 String searchParamName = theResourceName + ":" + theParamName; 1019 String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName); 1020 return new InvalidRequestException(msg); 1021 } 1022 1023 @Nonnull 1024 private InvalidRequestException newInvalidResourceTypeException(String theResourceType) { 1025 String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType); 1026 throw new InvalidRequestException(Msg.code(1029) + msg); 1027 } 1028 1029}