
001/*- 002 * #%L 003 * HAPI FHIR Storage api 004 * %% 005 * Copyright (C) 2014 - 2025 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.dao.index; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.FhirContext; 026import ca.uhn.fhir.context.RuntimeResourceDefinition; 027import ca.uhn.fhir.i18n.Msg; 028import ca.uhn.fhir.interceptor.api.HookParams; 029import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 030import ca.uhn.fhir.interceptor.api.Pointcut; 031import ca.uhn.fhir.interceptor.model.RequestPartitionId; 032import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 033import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 034import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 035import ca.uhn.fhir.jpa.api.svc.IIdHelperService; 036import ca.uhn.fhir.jpa.api.svc.ResolveIdentityMode; 037import ca.uhn.fhir.jpa.dao.BaseStorageDao; 038import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService; 039import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; 040import ca.uhn.fhir.jpa.model.cross.IResourceLookup; 041import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; 042import ca.uhn.fhir.jpa.model.entity.StorageSettings; 043import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; 044import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef; 045import ca.uhn.fhir.rest.api.Constants; 046import ca.uhn.fhir.rest.api.QualifiedParamList; 047import ca.uhn.fhir.rest.api.server.RequestDetails; 048import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 049import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; 050import ca.uhn.fhir.rest.param.TokenParam; 051import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 052import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 053import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 054import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 055import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 056import ca.uhn.fhir.storage.interceptor.AutoCreatePlaceholderReferenceTargetRequest; 057import ca.uhn.fhir.storage.interceptor.AutoCreatePlaceholderReferenceTargetResponse; 058import ca.uhn.fhir.util.CanonicalIdentifier; 059import ca.uhn.fhir.util.HapiExtensions; 060import ca.uhn.fhir.util.TerserUtil; 061import ca.uhn.fhir.util.UrlUtil; 062import jakarta.annotation.Nonnull; 063import jakarta.annotation.Nullable; 064import org.apache.commons.lang3.Validate; 065import org.hl7.fhir.instance.model.api.IBase; 066import org.hl7.fhir.instance.model.api.IBaseExtension; 067import org.hl7.fhir.instance.model.api.IBaseHasExtensions; 068import org.hl7.fhir.instance.model.api.IBaseReference; 069import org.hl7.fhir.instance.model.api.IBaseResource; 070import org.hl7.fhir.instance.model.api.IIdType; 071import org.hl7.fhir.instance.model.api.IPrimitiveType; 072import org.springframework.beans.factory.annotation.Autowired; 073 074import java.util.ArrayList; 075import java.util.Date; 076import java.util.List; 077import java.util.Map; 078import java.util.Optional; 079 080import static org.apache.commons.lang3.StringUtils.isBlank; 081import static org.apache.commons.lang3.StringUtils.isNotBlank; 082 083public class DaoResourceLinkResolver<T extends IResourcePersistentId<?>> implements IResourceLinkResolver { 084 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class); 085 086 @Autowired 087 private JpaStorageSettings myStorageSettings; 088 089 @Autowired 090 private FhirContext myContext; 091 092 @Autowired 093 private IIdHelperService<T> myIdHelperService; 094 095 @Autowired 096 private DaoRegistry myDaoRegistry; 097 098 @Autowired 099 private IHapiTransactionService myTransactionService; 100 101 @Autowired 102 private IInterceptorBroadcaster myInterceptorBroadcaster; 103 104 @Override 105 public IResourceLookup findTargetResource( 106 @Nonnull RequestPartitionId theRequestPartitionId, 107 String theSourceResourceName, 108 PathAndRef thePathAndRef, 109 RequestDetails theRequest, 110 TransactionDetails theTransactionDetails) { 111 112 IBaseReference targetReference = thePathAndRef.getRef(); 113 String sourcePath = thePathAndRef.getPath(); 114 115 IIdType targetResourceId = targetReference.getReferenceElement(); 116 if (targetResourceId.isEmpty() && targetReference.getResource() != null) { 117 targetResourceId = targetReference.getResource().getIdElement(); 118 } 119 120 String resourceType = targetResourceId.getResourceType(); 121 RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(resourceType); 122 Class<? extends IBaseResource> type = resourceDef.getImplementingClass(); 123 124 T persistentId = null; 125 if (theTransactionDetails != null) { 126 T resolvedResourceId = (T) theTransactionDetails.getResolvedResourceId(targetResourceId); 127 if (resolvedResourceId != null && resolvedResourceId.getId() != null) { 128 persistentId = resolvedResourceId; 129 130 if (resolvedResourceId.getAssociatedResourceId() == null) { 131 resolvedResourceId.setAssociatedResourceId(targetResourceId); 132 } 133 } 134 } 135 136 IResourceLookup<?> resolvedResource; 137 String idPart = targetResourceId.getIdPart(); 138 try { 139 if (persistentId == null) { 140 141 // If we previously looked up the ID, and it was not found, don't bother 142 // looking it up again 143 if (theTransactionDetails != null 144 && theTransactionDetails.hasNullResolvedResourceId(targetResourceId)) { 145 throw new ResourceNotFoundException(Msg.code(2602) + "Resource " + resourceType + "/" + idPart 146 + " previously cached in transaction as not-found, specified in path: " + sourcePath); 147 } 148 149 resolvedResource = myIdHelperService.resolveResourceIdentity( 150 theRequestPartitionId, 151 resourceType, 152 idPart, 153 ResolveIdentityMode.excludeDeleted().noCacheUnlessDeletesDisabled()); 154 ourLog.trace("Translated {}/{} to resource PID {}", type, idPart, resolvedResource); 155 } else { 156 resolvedResource = new ResourceLookupPersistentIdWrapper<>(persistentId); 157 } 158 } catch (ResourceNotFoundException e) { 159 160 Optional<IBasePersistedResource> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo( 161 type, targetReference, idPart, theRequest, theTransactionDetails); 162 if (!createdTableOpt.isPresent()) { 163 164 if (!myStorageSettings.isEnforceReferentialIntegrityOnWrite()) { 165 return null; 166 } 167 168 RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(type); 169 String resName = missingResourceDef.getName(); 170 171 // Check if this was a deleted resource 172 try { 173 resolvedResource = myIdHelperService.resolveResourceIdentity( 174 theRequestPartitionId, 175 resourceType, 176 idPart, 177 ResolveIdentityMode.includeDeleted().noCacheUnlessDeletesDisabled()); 178 handleDeletedTarget(resourceType, idPart, sourcePath); 179 } catch (ResourceNotFoundException e2) { 180 resolvedResource = null; 181 } 182 183 if (resolvedResource == null) { 184 throw new InvalidRequestException(Msg.code(1094) + "Resource " + resName + "/" + idPart 185 + " not found, specified in path: " + sourcePath); 186 } 187 } 188 189 resolvedResource = createdTableOpt.get(); 190 } 191 192 ourLog.trace( 193 "Resolved resource of type {} as PID: {}", 194 resolvedResource.getResourceType(), 195 resolvedResource.getPersistentId()); 196 if (!validateResolvedResourceOrThrow(resourceType, resolvedResource, targetResourceId, idPart, sourcePath)) { 197 return null; 198 } 199 200 if (persistentId == null) { 201 Object id = resolvedResource.getPersistentId().getId(); 202 Integer partitionId = null; 203 if (resolvedResource.getPartitionId() != null) { 204 partitionId = resolvedResource.getPartitionId().getPartitionId(); 205 } 206 persistentId = myIdHelperService.newPid(id, partitionId); 207 persistentId.setAssociatedResourceId(targetResourceId); 208 if (theTransactionDetails != null) { 209 theTransactionDetails.addResolvedResourceId(targetResourceId, persistentId); 210 } 211 } 212 213 return resolvedResource; 214 } 215 216 /** 217 * Validates the resolved resource. 218 * If 'Enforce Referential Integrity on Write' is enabled: 219 * Throws <code>UnprocessableEntityException</code> when resource types do not match 220 * Throws <code>InvalidRequestException</code> when the resolved resource was deleted 221 * <p> 222 * Otherwise, return false when resource types do not match or resource was deleted 223 * and return true if the resolved resource is valid. 224 */ 225 private boolean validateResolvedResourceOrThrow( 226 String resourceType, 227 IResourceLookup resolvedResource, 228 IIdType targetResourceId, 229 String idPart, 230 String sourcePath) { 231 if (!resourceType.equals(resolvedResource.getResourceType())) { 232 ourLog.error( 233 "Resource with PID {} was of type {} and wanted {}", 234 resolvedResource.getPersistentId(), 235 resourceType, 236 resolvedResource.getResourceType()); 237 if (!myStorageSettings.isEnforceReferentialIntegrityOnWrite()) { 238 return false; 239 } 240 throw new UnprocessableEntityException(Msg.code(1095) 241 + "Resource contains reference to unknown resource ID " + targetResourceId.getValue()); 242 } 243 244 if (resolvedResource.getDeleted() != null) { 245 return handleDeletedTarget(resolvedResource.getResourceType(), idPart, sourcePath); 246 } 247 return true; 248 } 249 250 private boolean handleDeletedTarget(String resType, String idPart, String sourcePath) { 251 if (!myStorageSettings.isEnforceReferentialIntegrityOnWrite()) { 252 return false; 253 } 254 String resName = resType; 255 throw new InvalidRequestException(Msg.code(1096) + "Resource " + resName + "/" + idPart 256 + " is deleted, specified in path: " + sourcePath); 257 } 258 259 @Nullable 260 @Override 261 public IBaseResource loadTargetResource( 262 @Nonnull RequestPartitionId theRequestPartitionId, 263 String theSourceResourceName, 264 PathAndRef thePathAndRef, 265 RequestDetails theRequest, 266 TransactionDetails theTransactionDetails) { 267 return myTransactionService 268 .withRequest(theRequest) 269 .withTransactionDetails(theTransactionDetails) 270 .withRequestPartitionId(theRequestPartitionId) 271 .execute(() -> { 272 IIdType targetId = thePathAndRef.getRef().getReferenceElement(); 273 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(targetId.getResourceType()); 274 return dao.read(targetId, theRequest); 275 }); 276 } 277 278 /** 279 * @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID 280 */ 281 public <T extends IBaseResource> Optional<IBasePersistedResource> createPlaceholderTargetIfConfiguredToDoSo( 282 Class<T> theType, 283 IBaseReference theReference, 284 @Nullable String theIdToAssignToPlaceholder, 285 RequestDetails theRequest, 286 TransactionDetails theTransactionDetails) { 287 IBasePersistedResource placeholderEntity = null; 288 289 if (myStorageSettings.isAutoCreatePlaceholderReferenceTargets()) { 290 RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType); 291 String resName = missingResourceDef.getName(); 292 293 @SuppressWarnings("unchecked") 294 T newResource = (T) missingResourceDef.newInstance(); 295 296 tryToAddPlaceholderExtensionToResource(newResource); 297 298 IFhirResourceDao<T> placeholderResourceDao = myDaoRegistry.getResourceDao(theType); 299 ourLog.debug( 300 "Automatically creating empty placeholder resource: {}", 301 newResource.getIdElement().getValue()); 302 303 boolean urlIdentifiersCopied = false; 304 if (myStorageSettings.isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets()) { 305 urlIdentifiersCopied = 306 tryToCopyIdentifierFromReferenceToTargetResource(theReference, missingResourceDef, newResource); 307 } 308 309 if (isBlank(theIdToAssignToPlaceholder) && !urlIdentifiersCopied) { 310 String msg = myContext 311 .getLocalizer() 312 .getMessage( 313 BaseStorageDao.class, 314 "invalidMatchUrlCantUseForAutoCreatePlaceholder", 315 theReference.getReferenceElement().getValue()); 316 throw new ResourceNotFoundException(Msg.code(2746) + msg); 317 } 318 319 if (theIdToAssignToPlaceholder != null) { 320 if (theTransactionDetails != null) { 321 String existingId = newResource.getIdElement().getValue(); 322 theTransactionDetails.addRollbackUndoAction(() -> newResource.setId(existingId)); 323 } 324 newResource.setId(resName + "/" + theIdToAssignToPlaceholder); 325 } 326 327 // Interceptor: STORAGE_PRE_AUTO_CREATE_PLACEHOLDER_REFERENCE 328 IInterceptorBroadcaster interceptorBroadcaster = 329 CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest); 330 if (interceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRE_AUTO_CREATE_PLACEHOLDER_REFERENCE)) { 331 AutoCreatePlaceholderReferenceTargetRequest request = 332 new AutoCreatePlaceholderReferenceTargetRequest(newResource); 333 HookParams params = new HookParams() 334 .add(AutoCreatePlaceholderReferenceTargetRequest.class, request) 335 .add(RequestDetails.class, theRequest) 336 .addIfMatchesType(ServletRequestDetails.class, theRequest); 337 AutoCreatePlaceholderReferenceTargetResponse response = 338 (AutoCreatePlaceholderReferenceTargetResponse) interceptorBroadcaster.callHooksAndReturnObject( 339 Pointcut.STORAGE_PRE_AUTO_CREATE_PLACEHOLDER_REFERENCE, params); 340 if (response != null) { 341 if (response.isDoNotCreateTarget()) { 342 return Optional.empty(); 343 } 344 } 345 346 // Sanity check: Make sure that interceptors haven't changed the ID 347 if (theIdToAssignToPlaceholder != null) { 348 Validate.isTrue( 349 theIdToAssignToPlaceholder.equals( 350 newResource.getIdElement().getIdPart()), 351 "Interceptors must not modify the ID of auto-created placeholder reference targets"); 352 } else { 353 Validate.isTrue( 354 isBlank(newResource.getIdElement().getIdPart()), 355 "Interceptors must not modify the ID of auto-created placeholder reference targets"); 356 } 357 } 358 359 if (theIdToAssignToPlaceholder != null) { 360 placeholderEntity = placeholderResourceDao 361 .update(newResource, null, true, false, theRequest, theTransactionDetails) 362 .getEntity(); 363 } else { 364 placeholderEntity = 365 placeholderResourceDao.create(newResource, theRequest).getEntity(); 366 } 367 368 verifyPlaceholderCanBeCreated(theType, theIdToAssignToPlaceholder, theReference, placeholderEntity); 369 370 IResourcePersistentId persistentId = placeholderEntity.getPersistentId(); 371 persistentId = myIdHelperService.newPid(persistentId.getId()); 372 persistentId.setAssociatedResourceId(placeholderEntity.getIdDt()); 373 theTransactionDetails.addResolvedResourceId(persistentId.getAssociatedResourceId(), persistentId); 374 theTransactionDetails.addAutoCreatedPlaceholderResource(newResource.getIdElement()); 375 } 376 377 return Optional.ofNullable(placeholderEntity); 378 } 379 380 /** 381 * Subclasses may override 382 */ 383 protected void verifyPlaceholderCanBeCreated( 384 Class<? extends IBaseResource> theType, 385 String theIdToAssignToPlaceholder, 386 IBaseReference theReference, 387 IBasePersistedResource theStoredEntity) {} 388 389 private <T extends IBaseResource> void tryToAddPlaceholderExtensionToResource(T newResource) { 390 if (newResource instanceof IBaseHasExtensions) { 391 IBaseExtension<?, ?> extension = ((IBaseHasExtensions) newResource).addExtension(); 392 extension.setUrl(HapiExtensions.EXT_RESOURCE_PLACEHOLDER); 393 extension.setValue(myContext.newPrimitiveBoolean(true)); 394 } 395 } 396 397 /** 398 * This method returns false if the reference contained a conditional reference, but 399 * the reference couldn't be resolved into one or more identifiers (and only one or 400 * more identifiers) according to the rules in {@link #extractIdentifierFromUrl(String)}. 401 */ 402 private <T extends IBaseResource> boolean tryToCopyIdentifierFromReferenceToTargetResource( 403 IBaseReference theSourceReference, RuntimeResourceDefinition theTargetResourceDef, T theTargetResource) { 404 String urlValue = theSourceReference.getReferenceElement().getValue(); 405 List<CanonicalIdentifier> referenceMatchUrlIdentifiers; 406 if (urlValue.contains("?")) { 407 referenceMatchUrlIdentifiers = extractIdentifierFromUrl(urlValue); 408 for (CanonicalIdentifier identifier : referenceMatchUrlIdentifiers) { 409 addMatchUrlIdentifierToTargetResource(theTargetResourceDef, theTargetResource, identifier); 410 } 411 412 if (referenceMatchUrlIdentifiers.isEmpty()) { 413 return false; 414 } 415 416 } else { 417 referenceMatchUrlIdentifiers = List.of(); 418 } 419 420 CanonicalIdentifier referenceIdentifier = extractIdentifierReference(theSourceReference); 421 if (referenceIdentifier != null && !referenceMatchUrlIdentifiers.contains(referenceIdentifier)) { 422 addSubjectIdentifierToTargetResource(theSourceReference, theTargetResourceDef, theTargetResource); 423 } 424 425 return true; 426 } 427 428 private <T extends IBaseResource> void addSubjectIdentifierToTargetResource( 429 IBaseReference theSourceReference, RuntimeResourceDefinition theTargetResourceDef, T theTargetResource) { 430 BaseRuntimeChildDefinition targetIdentifier = theTargetResourceDef.getChildByName("identifier"); 431 if (targetIdentifier != null) { 432 BaseRuntimeElementDefinition<?> identifierElement = targetIdentifier.getChildByName("identifier"); 433 String identifierElementName = identifierElement.getName(); 434 boolean targetHasIdentifierElement = identifierElementName.equals("Identifier"); 435 if (targetHasIdentifierElement) { 436 437 BaseRuntimeElementCompositeDefinition<?> referenceElement = (BaseRuntimeElementCompositeDefinition<?>) 438 myContext.getElementDefinition(theSourceReference.getClass()); 439 BaseRuntimeChildDefinition referenceIdentifierChild = referenceElement.getChildByName("identifier"); 440 Optional<IBase> identifierOpt = 441 referenceIdentifierChild.getAccessor().getFirstValueOrNull(theSourceReference); 442 identifierOpt.ifPresent( 443 theIBase -> targetIdentifier.getMutator().addValue(theTargetResource, theIBase)); 444 } 445 } 446 } 447 448 private <T extends IBaseResource> void addMatchUrlIdentifierToTargetResource( 449 RuntimeResourceDefinition theTargetResourceDef, 450 T theTargetResource, 451 CanonicalIdentifier referenceMatchUrlIdentifier) { 452 BaseRuntimeChildDefinition identifierDefinition = theTargetResourceDef.getChildByName("identifier"); 453 IBase identifierIBase = identifierDefinition 454 .getChildByName("identifier") 455 .newInstance(identifierDefinition.getInstanceConstructorArguments()); 456 IBase systemIBase = TerserUtil.newElement( 457 myContext, "uri", referenceMatchUrlIdentifier.getSystemElement().getValueAsString()); 458 IBase valueIBase = TerserUtil.newElement( 459 myContext, 460 "string", 461 referenceMatchUrlIdentifier.getValueElement().getValueAsString()); 462 // Set system in the IBase Identifier 463 464 BaseRuntimeElementDefinition<?> elementDefinition = myContext.getElementDefinition(identifierIBase.getClass()); 465 466 BaseRuntimeChildDefinition systemDefinition = elementDefinition.getChildByName("system"); 467 systemDefinition.getMutator().setValue(identifierIBase, systemIBase); 468 469 BaseRuntimeChildDefinition valueDefinition = elementDefinition.getChildByName("value"); 470 valueDefinition.getMutator().setValue(identifierIBase, valueIBase); 471 472 // Set Value in the IBase identifier 473 identifierDefinition.getMutator().addValue(theTargetResource, identifierIBase); 474 } 475 476 private CanonicalIdentifier extractIdentifierReference(IBaseReference theSourceReference) { 477 Optional<IBase> identifier = 478 myContext.newFhirPath().evaluateFirst(theSourceReference, "identifier", IBase.class); 479 if (!identifier.isPresent()) { 480 return null; 481 } else { 482 CanonicalIdentifier canonicalIdentifier = new CanonicalIdentifier(); 483 Optional<IPrimitiveType> system = 484 myContext.newFhirPath().evaluateFirst(identifier.get(), "system", IPrimitiveType.class); 485 Optional<IPrimitiveType> value = 486 myContext.newFhirPath().evaluateFirst(identifier.get(), "value", IPrimitiveType.class); 487 488 system.ifPresent(theIPrimitiveType -> canonicalIdentifier.setSystem(theIPrimitiveType.getValueAsString())); 489 value.ifPresent(theIPrimitiveType -> canonicalIdentifier.setValue(theIPrimitiveType.getValueAsString())); 490 return canonicalIdentifier; 491 } 492 } 493 494 /** 495 * Extracts the identifier(s) from a query URL. This method is quite strict, as it is intended only for 496 * use when creating {@link StorageSettings#isAutoCreatePlaceholderReferenceTargets() auto-created placeholder reference targets}. 497 * As such, it will: 498 * <ul> 499 * <li>Ignore any <code>_tag:not</code> parameters</li> 500 * <li>Add an entry to the returned list for each <code>identifier</code> parameter containing a single valid value</li> 501 * <li>Return an empty list if any <code>identifier</code> parameters have modifiers or multiple OR values in a single parameter instance</li> 502 * <li>Return an empty list if any other parameters are found</li> 503 * </ul> 504 * 505 * @param theValue Part of the URL to extract identifiers from 506 */ 507 protected List<CanonicalIdentifier> extractIdentifierFromUrl(String theValue) { 508 Map<String, String[]> parsedQuery = UrlUtil.parseQueryString(theValue); 509 510 ArrayList<CanonicalIdentifier> retVal = new ArrayList<>(2); 511 512 for (String paramName : parsedQuery.keySet()) { 513 switch (paramName) { 514 case "identifier" -> { 515 String[] values = parsedQuery.get(paramName); 516 for (String value : values) { 517 QualifiedParamList paramList = 518 QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, value); 519 if (paramList.size() > 1) { 520 return List.of(); 521 } else if (!paramList.isEmpty()) { 522 TokenParam tokenParam = new TokenParam(); 523 tokenParam.setValueAsQueryToken(myContext, "identifier", null, paramList.get(0)); 524 if (isNotBlank(tokenParam.getSystem()) || isNotBlank(tokenParam.getValue())) { 525 CanonicalIdentifier identifier = new CanonicalIdentifier(); 526 identifier.setSystem(tokenParam.getSystem()); 527 identifier.setValue(tokenParam.getValue()); 528 retVal.add(identifier); 529 } 530 } 531 } 532 } 533 case Constants.PARAM_TAG + Constants.PARAMQUALIFIER_TOKEN_NOT -> { 534 // We ignore _tag:not expressions since any auto-created placeholder 535 // won't have tags so they will match this parameter 536 } 537 default -> { 538 return List.of(); 539 } 540 } 541 } 542 543 return retVal; 544 } 545 546 @Override 547 public void validateTypeOrThrowException(Class<? extends IBaseResource> theType) { 548 myDaoRegistry.getDaoOrThrow(theType); 549 } 550 551 private static class ResourceLookupPersistentIdWrapper<P extends IResourcePersistentId> implements IResourceLookup { 552 private final P myPersistentId; 553 554 public ResourceLookupPersistentIdWrapper(P thePersistentId) { 555 myPersistentId = thePersistentId; 556 } 557 558 @Override 559 public String getResourceType() { 560 return myPersistentId.getAssociatedResourceId().getResourceType(); 561 } 562 563 @Override 564 public String getFhirId() { 565 return myPersistentId.getAssociatedResourceId().getIdPart(); 566 } 567 568 @Override 569 public Date getDeleted() { 570 return null; 571 } 572 573 @Override 574 public P getPersistentId() { 575 return myPersistentId; 576 } 577 578 @Override 579 public PartitionablePartitionId getPartitionId() { 580 return new PartitionablePartitionId(myPersistentId.getPartitionId(), null); 581 } 582 } 583}