001/*- 002 * #%L 003 * HAPI FHIR JPA Server 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.provider.merge; 021 022import ca.uhn.fhir.batch2.jobs.merge.MergeResourceHelper; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.context.FhirVersionEnum; 025import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 026import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; 027import ca.uhn.fhir.rest.annotation.Operation; 028import ca.uhn.fhir.rest.annotation.OperationParam; 029import ca.uhn.fhir.rest.server.provider.ProviderConstants; 030import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 031import ca.uhn.fhir.util.CanonicalIdentifier; 032import ca.uhn.fhir.util.ParametersUtil; 033import jakarta.servlet.http.HttpServletRequest; 034import jakarta.servlet.http.HttpServletResponse; 035import org.hl7.fhir.instance.model.api.IBaseParameters; 036import org.hl7.fhir.instance.model.api.IBaseReference; 037import org.hl7.fhir.instance.model.api.IBaseResource; 038import org.hl7.fhir.instance.model.api.IPrimitiveType; 039import org.hl7.fhir.r4.model.Identifier; 040import org.hl7.fhir.r4.model.Patient; 041 042import java.util.List; 043import java.util.stream.Collectors; 044 045import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_MERGE_OUTPUT_PARAM_RESULT; 046 047public class PatientMergeProvider extends BaseJpaResourceProvider<Patient> { 048 049 private final FhirContext myFhirContext; 050 private final ResourceMergeService myResourceMergeService; 051 052 public PatientMergeProvider( 053 FhirContext theFhirContext, DaoRegistry theDaoRegistry, ResourceMergeService theResourceMergeService) { 054 super(theDaoRegistry.getResourceDao("Patient")); 055 myFhirContext = theFhirContext; 056 assert myFhirContext.getVersion().getVersion() == FhirVersionEnum.R4; 057 myResourceMergeService = theResourceMergeService; 058 } 059 060 @Override 061 public Class<Patient> getResourceType() { 062 return Patient.class; 063 } 064 065 /** 066 * /Patient/$merge 067 */ 068 @Operation( 069 name = ProviderConstants.OPERATION_MERGE, 070 canonicalUrl = "http://hl7.org/fhir/OperationDefinition/Patient-merge") 071 public IBaseParameters patientMerge( 072 HttpServletRequest theServletRequest, 073 HttpServletResponse theServletResponse, 074 ServletRequestDetails theRequestDetails, 075 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_SOURCE_PATIENT_IDENTIFIER) 076 List<Identifier> theSourcePatientIdentifier, 077 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_TARGET_PATIENT_IDENTIFIER) 078 List<Identifier> theTargetPatientIdentifier, 079 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_SOURCE_PATIENT, max = 1) 080 IBaseReference theSourcePatient, 081 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_TARGET_PATIENT, max = 1) 082 IBaseReference theTargetPatient, 083 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_PREVIEW, typeName = "boolean", max = 1) 084 IPrimitiveType<Boolean> thePreview, 085 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_DELETE_SOURCE, typeName = "boolean", max = 1) 086 IPrimitiveType<Boolean> theDeleteSource, 087 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_RESULT_PATIENT, max = 1) 088 IBaseResource theResultPatient, 089 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_BATCH_SIZE, typeName = "unsignedInt") 090 IPrimitiveType<Integer> theResourceLimit) { 091 092 startRequest(theServletRequest); 093 094 try { 095 int resourceLimit = MergeResourceHelper.setResourceLimitFromParameter(myStorageSettings, theResourceLimit); 096 097 BaseMergeOperationInputParameters mergeOperationParameters = buildMergeOperationInputParameters( 098 theSourcePatientIdentifier, 099 theTargetPatientIdentifier, 100 theSourcePatient, 101 theTargetPatient, 102 thePreview, 103 theDeleteSource, 104 theResultPatient, 105 resourceLimit); 106 107 MergeOperationOutcome mergeOutcome = 108 myResourceMergeService.merge(mergeOperationParameters, theRequestDetails); 109 110 theServletResponse.setStatus(mergeOutcome.getHttpStatusCode()); 111 return buildMergeOperationOutputParameters(myFhirContext, mergeOutcome, theRequestDetails.getResource()); 112 } finally { 113 endRequest(theServletRequest); 114 } 115 } 116 117 private IBaseParameters buildMergeOperationOutputParameters( 118 FhirContext theFhirContext, MergeOperationOutcome theMergeOutcome, IBaseResource theInputParameters) { 119 120 IBaseParameters retVal = ParametersUtil.newInstance(theFhirContext); 121 ParametersUtil.addParameterToParameters( 122 theFhirContext, retVal, ProviderConstants.OPERATION_MERGE_OUTPUT_PARAM_INPUT, theInputParameters); 123 124 ParametersUtil.addParameterToParameters( 125 theFhirContext, 126 retVal, 127 ProviderConstants.OPERATION_MERGE_OUTPUT_PARAM_OUTCOME, 128 theMergeOutcome.getOperationOutcome()); 129 130 if (theMergeOutcome.getUpdatedTargetResource() != null) { 131 ParametersUtil.addParameterToParameters( 132 theFhirContext, 133 retVal, 134 OPERATION_MERGE_OUTPUT_PARAM_RESULT, 135 theMergeOutcome.getUpdatedTargetResource()); 136 } 137 138 if (theMergeOutcome.getTask() != null) { 139 ParametersUtil.addParameterToParameters( 140 theFhirContext, 141 retVal, 142 ProviderConstants.OPERATION_MERGE_OUTPUT_PARAM_TASK, 143 theMergeOutcome.getTask()); 144 } 145 return retVal; 146 } 147 148 private BaseMergeOperationInputParameters buildMergeOperationInputParameters( 149 List<Identifier> theSourcePatientIdentifier, 150 List<Identifier> theTargetPatientIdentifier, 151 IBaseReference theSourcePatient, 152 IBaseReference theTargetPatient, 153 IPrimitiveType<Boolean> thePreview, 154 IPrimitiveType<Boolean> theDeleteSource, 155 IBaseResource theResultPatient, 156 int theResourceLimit) { 157 BaseMergeOperationInputParameters mergeOperationParameters = 158 new PatientMergeOperationInputParameters(theResourceLimit); 159 if (theSourcePatientIdentifier != null) { 160 List<CanonicalIdentifier> sourceResourceIdentifiers = theSourcePatientIdentifier.stream() 161 .map(CanonicalIdentifier::fromIdentifier) 162 .collect(Collectors.toList()); 163 mergeOperationParameters.setSourceResourceIdentifiers(sourceResourceIdentifiers); 164 } 165 if (theTargetPatientIdentifier != null) { 166 List<CanonicalIdentifier> targetResourceIdentifiers = theTargetPatientIdentifier.stream() 167 .map(CanonicalIdentifier::fromIdentifier) 168 .collect(Collectors.toList()); 169 mergeOperationParameters.setTargetResourceIdentifiers(targetResourceIdentifiers); 170 } 171 mergeOperationParameters.setSourceResource(theSourcePatient); 172 mergeOperationParameters.setTargetResource(theTargetPatient); 173 mergeOperationParameters.setPreview(thePreview != null && thePreview.getValue()); 174 mergeOperationParameters.setDeleteSource(theDeleteSource != null && theDeleteSource.getValue()); 175 176 if (theResultPatient != null) { 177 // pass in a copy of the result patient as we don't want it to be modified. It will be 178 // returned back to the client as part of the response. 179 mergeOperationParameters.setResultResource(((Patient) theResultPatient).copy()); 180 } 181 182 return mergeOperationParameters; 183 } 184}