
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.merge; 021 022import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 023import ca.uhn.fhir.model.api.IProvenanceAgent; 024import ca.uhn.fhir.replacereferences.ReplaceReferencesProvenanceSvc; 025import ca.uhn.fhir.rest.api.server.RequestDetails; 026import ca.uhn.fhir.util.CanonicalIdentifier; 027import jakarta.annotation.Nullable; 028import org.hl7.fhir.instance.model.api.IBaseResource; 029import org.hl7.fhir.instance.model.api.IIdType; 030import org.hl7.fhir.r4.model.Bundle; 031import org.hl7.fhir.r4.model.CodeableConcept; 032import org.hl7.fhir.r4.model.Identifier; 033import org.hl7.fhir.r4.model.Parameters; 034import org.hl7.fhir.r4.model.Provenance; 035import org.hl7.fhir.r4.model.Type; 036 037import java.util.Date; 038import java.util.List; 039 040/** 041 * Handles Provenance resources for the $merge operation. 042 */ 043public class MergeProvenanceSvc extends ReplaceReferencesProvenanceSvc { 044 045 private static final String ACTIVITY_CODE_MERGE = "merge"; 046 private final MergeOperationInputParameterNames myInputParamNames; 047 048 public MergeProvenanceSvc(DaoRegistry theDaoRegistry) { 049 super(theDaoRegistry); 050 myInputParamNames = new MergeOperationInputParameterNames(); 051 } 052 053 @Override 054 protected CodeableConcept getActivityCodeableConcept() { 055 CodeableConcept retVal = new CodeableConcept(); 056 retVal.addCoding().setSystem(ACTIVITY_CODE_SYSTEM).setCode(ACTIVITY_CODE_MERGE); 057 return retVal; 058 } 059 060 @Override 061 public void createProvenance( 062 IIdType theTargetId, 063 IIdType theSourceId, 064 List<Bundle> thePatchResultBundles, 065 Date theStartTime, 066 RequestDetails theRequestDetails, 067 List<IProvenanceAgent> theProvenanceAgents, 068 List<IBaseResource> theContainedResources) { 069 070 super.createProvenance( 071 theTargetId, 072 theSourceId, 073 thePatchResultBundles, 074 theStartTime, 075 theRequestDetails, 076 theProvenanceAgents, 077 theContainedResources, 078 // we need to create a Provenance resource even when there were no referencing resources, 079 // because src and target resources are always updated in $merge operation 080 true); 081 } 082 083 /** 084 * Finds a Provenance with the activity code "merge" that has the given target id and source identifiers. 085 * @return the Provenance resource if matching one is found, or null if not found. 086 */ 087 @Nullable 088 public Provenance findProvenanceByTargetIdAndSourceIdentifiers( 089 IIdType theTargetId, List<CanonicalIdentifier> theSourceIdentifiers, RequestDetails theRequestDetails) { 090 String sourceIdentifierParameterName = myInputParamNames.getSourceIdentifiersParameterName(); 091 List<Provenance> provenances = 092 getProvenancesOfTargetsFilteredByActivity(List.of(theTargetId), theRequestDetails); 093 // the input parameters must be the first contained resource in Provenance's contained resources, 094 // find the one that matches the source identifiers 095 for (Provenance provenance : provenances) { 096 if (provenance.hasContained() 097 && provenance.getContained().get(0) instanceof Parameters parameters 098 && parameters.hasParameter(sourceIdentifierParameterName)) { 099 List<Type> originalInputSrcIdentifiers = parameters.getParameterValues(sourceIdentifierParameterName); 100 if (hasIdentifiers(originalInputSrcIdentifiers, theSourceIdentifiers)) { 101 return provenance; 102 } 103 } 104 } 105 return null; 106 } 107 108 private boolean hasIdentifiers(List<Type> theIdentifiers, List<CanonicalIdentifier> theIdentifiersToLookFor) { 109 for (CanonicalIdentifier identifier : theIdentifiersToLookFor) { 110 boolean identifierFound = theIdentifiers.stream() 111 .map(i -> (Identifier) i) 112 .anyMatch(i -> i.getSystem() 113 .equals(identifier.getSystemElement().getValueAsString()) 114 && i.getValue().equals(identifier.getValueElement().getValueAsString())); 115 116 if (!identifierFound) { 117 return false; 118 } 119 } 120 return true; 121 } 122}