
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.model.api.IProvenanceAgent; 026import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 027import ca.uhn.fhir.rest.api.server.RequestDetails; 028import ca.uhn.fhir.util.FhirTerser; 029import jakarta.annotation.Nullable; 030import org.hl7.fhir.instance.model.api.IBaseReference; 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.CodeableConcept; 035import org.hl7.fhir.r4.model.Period; 036import org.hl7.fhir.r4.model.Provenance; 037import org.hl7.fhir.r4.model.Reference; 038 039import java.util.ArrayList; 040import java.util.Date; 041import java.util.List; 042 043/** 044 * This service is used to create a Provenance resource for the $replace-references operation 045 * and also used as a base class for {@link ca.uhn.fhir.merge.MergeProvenanceSvc} used in $merge operations. 046 * The two operations use different activity codes. 047 */ 048public class ReplaceReferencesProvenanceSvc { 049 050 private static final String ACT_REASON_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/v3-ActReason"; 051 private static final String ACT_REASON_PATIENT_ADMINISTRATION_CODE = "PATADMIN"; 052 protected static final String ACTIVITY_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/iso-21089-lifecycle"; 053 private static final String ACTIVITY_CODE_LINK = "link"; 054 private final IFhirResourceDao<IBaseResource> myProvenanceDao; 055 private final FhirContext myFhirContext; 056 057 public ReplaceReferencesProvenanceSvc(DaoRegistry theDaoRegistry) { 058 myProvenanceDao = theDaoRegistry.getResourceDao("Provenance"); 059 myFhirContext = theDaoRegistry.getFhirContext(); 060 } 061 062 @Nullable 063 protected CodeableConcept getActivityCodeableConcept() { 064 CodeableConcept retVal = new CodeableConcept(); 065 retVal.addCoding().setSystem(ACTIVITY_CODE_SYSTEM).setCode(ACTIVITY_CODE_LINK); 066 return retVal; 067 } 068 069 protected Provenance createProvenanceObject( 070 Reference theTargetReference, 071 @Nullable Reference theSourceReference, 072 List<Reference> theUpdatedReferencingResources, 073 Date theStartTime, 074 List<IProvenanceAgent> theProvenanceAgents) { 075 Provenance provenance = new Provenance(); 076 077 Date now = new Date(); 078 provenance.setOccurred(new Period() 079 .setStart(theStartTime, TemporalPrecisionEnum.MILLI) 080 .setEnd(now, TemporalPrecisionEnum.MILLI)); 081 provenance.setRecorded(now); 082 083 addAgents(theProvenanceAgents, provenance); 084 085 CodeableConcept activityCodeableConcept = getActivityCodeableConcept(); 086 if (activityCodeableConcept != null) { 087 provenance.setActivity(activityCodeableConcept); 088 } 089 CodeableConcept activityReasonCodeableConcept = new CodeableConcept(); 090 activityReasonCodeableConcept 091 .addCoding() 092 .setSystem(ACT_REASON_CODE_SYSTEM) 093 .setCode(ACT_REASON_PATIENT_ADMINISTRATION_CODE); 094 095 provenance.addReason(activityReasonCodeableConcept); 096 097 provenance.addTarget(theTargetReference); 098 if (theSourceReference != null) { 099 provenance.addTarget(theSourceReference); 100 } 101 102 theUpdatedReferencingResources.forEach(provenance::addTarget); 103 return provenance; 104 } 105 106 /** 107 * Creates a Provenance resource for the $replace-references and $merge operations. 108 * 109 * @param theTargetId the versioned id of the target resource of the operation. 110 * @param theSourceId the versioned id of the source resource of the operation. Can be null if the operation is $merge and the source resource is deleted. 111 * @param thePatchResultBundles the list of patch result bundles that contain the updated resources. 112 * @param theStartTime the start time of the operation. 113 * @param theRequestDetails the request details 114 * @param theProvenanceAgents the list of agents to be included in the Provenance resource. 115 */ 116 public void createProvenance( 117 IIdType theTargetId, 118 @Nullable IIdType theSourceId, 119 List<Bundle> thePatchResultBundles, 120 Date theStartTime, 121 RequestDetails theRequestDetails, 122 List<IProvenanceAgent> theProvenanceAgents) { 123 Reference targetReference = new Reference(theTargetId); 124 Reference sourceReference = null; 125 if (theSourceId != null) { 126 sourceReference = new Reference(theSourceId); 127 } 128 List<Reference> references = extractUpdatedResourceReferences(thePatchResultBundles); 129 Provenance provenance = 130 createProvenanceObject(targetReference, sourceReference, references, theStartTime, theProvenanceAgents); 131 myProvenanceDao.create(provenance, theRequestDetails); 132 } 133 134 protected List<Reference> extractUpdatedResourceReferences(List<Bundle> thePatchBundles) { 135 List<Reference> patchedResourceReferences = new ArrayList<>(); 136 thePatchBundles.forEach(outputBundle -> { 137 outputBundle.getEntry().forEach(entry -> { 138 if (entry.getResponse() != null && entry.getResponse().hasLocation()) { 139 Reference reference = new Reference(entry.getResponse().getLocation()); 140 patchedResourceReferences.add(reference); 141 } 142 }); 143 }); 144 return patchedResourceReferences; 145 } 146 147 private Provenance.ProvenanceAgentComponent createR4ProvenanceAgent(IProvenanceAgent theProvenanceAgent) { 148 Provenance.ProvenanceAgentComponent agent = new Provenance.ProvenanceAgentComponent(); 149 Reference whoRef = convertToR4Reference(theProvenanceAgent.getWho()); 150 agent.setWho(whoRef); 151 Reference onBehalfOfRef = convertToR4Reference(theProvenanceAgent.getOnBehalfOf()); 152 agent.setOnBehalfOf(onBehalfOfRef); 153 return agent; 154 } 155 156 private void addAgents(List<IProvenanceAgent> theProvenanceAgents, Provenance theProvenance) { 157 if (theProvenanceAgents != null) { 158 for (IProvenanceAgent agent : theProvenanceAgents) { 159 Provenance.ProvenanceAgentComponent r4Agent = createR4ProvenanceAgent(agent); 160 theProvenance.addAgent(r4Agent); 161 } 162 } 163 } 164 165 private Reference convertToR4Reference(IBaseReference sourceRef) { 166 if (sourceRef == null) { 167 return null; 168 } 169 FhirTerser terser = myFhirContext.newTerser(); 170 Reference targetRef = new Reference(); 171 terser.cloneInto(sourceRef, targetRef, false); 172 return targetRef; 173 } 174}