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.replacereferences; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 024import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 025import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; 026import ca.uhn.fhir.model.primitive.IdDt; 027import ca.uhn.fhir.rest.api.server.RequestDetails; 028import ca.uhn.fhir.util.BundleBuilder; 029import ca.uhn.fhir.util.ResourceReferenceInfo; 030import jakarta.annotation.Nonnull; 031import org.hl7.fhir.instance.model.api.IBaseResource; 032import org.hl7.fhir.instance.model.api.IIdType; 033import org.hl7.fhir.r4.model.Bundle; 034import org.hl7.fhir.r4.model.CodeType; 035import org.hl7.fhir.r4.model.Meta; 036import org.hl7.fhir.r4.model.Parameters; 037import org.hl7.fhir.r4.model.Reference; 038import org.hl7.fhir.r4.model.StringType; 039import org.hl7.fhir.r4.model.Type; 040 041import java.util.List; 042import java.util.UUID; 043 044import static ca.uhn.fhir.jpa.patch.FhirPatch.OPERATION_REPLACE; 045import static ca.uhn.fhir.jpa.patch.FhirPatch.PARAMETER_OPERATION; 046import static ca.uhn.fhir.jpa.patch.FhirPatch.PARAMETER_PATH; 047import static ca.uhn.fhir.jpa.patch.FhirPatch.PARAMETER_TYPE; 048import static ca.uhn.fhir.jpa.patch.FhirPatch.PARAMETER_VALUE; 049 050public class ReplaceReferencesPatchBundleSvc { 051 052 private final FhirContext myFhirContext; 053 private final DaoRegistry myDaoRegistry; 054 055 public ReplaceReferencesPatchBundleSvc(DaoRegistry theDaoRegistry) { 056 myDaoRegistry = theDaoRegistry; 057 myFhirContext = theDaoRegistry.getFhirContext(); 058 } 059 060 /** 061 * Build a bundle of PATCH entries that make the requested reference updates 062 * @param theReplaceReferencesRequest source and target for reference switch 063 * @param theResourceIds the ids of the resource to create the patch entries for (they will all have references to the source resource) 064 * @param theRequestDetails 065 * @return 066 */ 067 public Bundle patchReferencingResources( 068 ReplaceReferencesRequest theReplaceReferencesRequest, 069 List<IdDt> theResourceIds, 070 RequestDetails theRequestDetails) { 071 Bundle patchBundle = buildPatchBundle(theReplaceReferencesRequest, theResourceIds, theRequestDetails); 072 IFhirSystemDao<Bundle, Meta> systemDao = myDaoRegistry.getSystemDao(); 073 Bundle result = systemDao.transaction(theRequestDetails, patchBundle); 074 075 result.setId(UUID.randomUUID().toString()); 076 return result; 077 } 078 079 private Bundle buildPatchBundle( 080 ReplaceReferencesRequest theReplaceReferencesRequest, 081 List<IdDt> theResourceIds, 082 RequestDetails theRequestDetails) { 083 BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext); 084 085 theResourceIds.forEach(referencingResourceId -> { 086 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(referencingResourceId.getResourceType()); 087 IBaseResource resource = dao.read(referencingResourceId, theRequestDetails); 088 Parameters patchParams = buildPatchParams(theReplaceReferencesRequest, resource); 089 IIdType resourceId = resource.getIdElement(); 090 bundleBuilder.addTransactionFhirPatchEntry(resourceId, patchParams); 091 }); 092 return bundleBuilder.getBundleTyped(); 093 } 094 095 private @Nonnull Parameters buildPatchParams( 096 ReplaceReferencesRequest theReplaceReferencesRequest, IBaseResource referencingResource) { 097 Parameters params = new Parameters(); 098 099 myFhirContext.newTerser().getAllResourceReferences(referencingResource).stream() 100 .filter(refInfo -> matches( 101 refInfo, 102 theReplaceReferencesRequest.sourceId)) // We only care about references to our source resource 103 .map(refInfo -> createReplaceReferencePatchOperation( 104 referencingResource.fhirType() + "." + refInfo.getName(), 105 new Reference(theReplaceReferencesRequest.targetId.getValueAsString()))) 106 .forEach(params::addParameter); // Add each operation to parameters 107 return params; 108 } 109 110 private static boolean matches(ResourceReferenceInfo refInfo, IIdType theSourceId) { 111 return refInfo.getResourceReference() 112 .getReferenceElement() 113 .toUnqualifiedVersionless() 114 .getValueAsString() 115 .equals(theSourceId.getValueAsString()); 116 } 117 118 @Nonnull 119 private Parameters.ParametersParameterComponent createReplaceReferencePatchOperation( 120 String thePath, Type theValue) { 121 122 Parameters.ParametersParameterComponent operation = new Parameters.ParametersParameterComponent(); 123 operation.setName(PARAMETER_OPERATION); 124 operation.addPart().setName(PARAMETER_TYPE).setValue(new CodeType(OPERATION_REPLACE)); 125 operation.addPart().setName(PARAMETER_PATH).setValue(new StringType(thePath)); 126 operation.addPart().setName(PARAMETER_VALUE).setValue(theValue); 127 return operation; 128 } 129}