
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.interceptor.api.IInterceptorBroadcaster; 026import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 027import ca.uhn.fhir.jpa.interceptor.ProvenanceAgentsPointcutUtil; 028import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; 029import ca.uhn.fhir.model.api.IProvenanceAgent; 030import ca.uhn.fhir.rest.annotation.Operation; 031import ca.uhn.fhir.rest.annotation.OperationParam; 032import ca.uhn.fhir.rest.server.provider.ProviderConstants; 033import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 034import ca.uhn.fhir.util.CanonicalIdentifier; 035import ca.uhn.fhir.util.ParametersUtil; 036import jakarta.servlet.http.HttpServletRequest; 037import jakarta.servlet.http.HttpServletResponse; 038import org.hl7.fhir.instance.model.api.IBaseParameters; 039import org.hl7.fhir.instance.model.api.IBaseReference; 040import org.hl7.fhir.instance.model.api.IBaseResource; 041import org.hl7.fhir.instance.model.api.IPrimitiveType; 042import org.hl7.fhir.r4.model.Identifier; 043import org.hl7.fhir.r4.model.Patient; 044 045import java.util.List; 046import java.util.stream.Collectors; 047 048public class PatientMergeProvider extends BaseJpaResourceProvider<Patient> { 049 050 private final FhirContext myFhirContext; 051 private final ResourceMergeService myResourceMergeService; 052 private final ResourceUndoMergeService myResourceUndoMergeService; 053 private final IInterceptorBroadcaster myInterceptorBroadcaster; 054 055 public PatientMergeProvider( 056 FhirContext theFhirContext, 057 DaoRegistry theDaoRegistry, 058 ResourceMergeService theResourceMergeService, 059 ResourceUndoMergeService theResourceUndoMergeService, 060 IInterceptorBroadcaster theInterceptorBroadcaster) { 061 super(theDaoRegistry.getResourceDao("Patient")); 062 myFhirContext = theFhirContext; 063 assert myFhirContext.getVersion().getVersion() == FhirVersionEnum.R4; 064 myResourceMergeService = theResourceMergeService; 065 myResourceUndoMergeService = theResourceUndoMergeService; 066 myInterceptorBroadcaster = theInterceptorBroadcaster; 067 } 068 069 @Override 070 public Class<Patient> getResourceType() { 071 return Patient.class; 072 } 073 074 /** 075 * /Patient/$merge 076 */ 077 @Operation( 078 name = ProviderConstants.OPERATION_MERGE, 079 canonicalUrl = "http://hl7.org/fhir/OperationDefinition/Patient-merge") 080 public IBaseParameters patientMerge( 081 HttpServletRequest theServletRequest, 082 HttpServletResponse theServletResponse, 083 ServletRequestDetails theRequestDetails, 084 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_SOURCE_PATIENT_IDENTIFIER) 085 List<Identifier> theSourcePatientIdentifier, 086 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_TARGET_PATIENT_IDENTIFIER) 087 List<Identifier> theTargetPatientIdentifier, 088 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_SOURCE_PATIENT, max = 1) 089 IBaseReference theSourcePatient, 090 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_TARGET_PATIENT, max = 1) 091 IBaseReference theTargetPatient, 092 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_PREVIEW, typeName = "boolean", max = 1) 093 IPrimitiveType<Boolean> thePreview, 094 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_DELETE_SOURCE, typeName = "boolean", max = 1) 095 IPrimitiveType<Boolean> theDeleteSource, 096 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_RESULT_PATIENT, max = 1) 097 IBaseResource theResultPatient, 098 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_BATCH_SIZE, typeName = "unsignedInt") 099 IPrimitiveType<Integer> theResourceLimit) { 100 101 startRequest(theServletRequest); 102 103 try { 104 int resourceLimit = MergeResourceHelper.setResourceLimitFromParameter(myStorageSettings, theResourceLimit); 105 106 List<IProvenanceAgent> provenanceAgents = 107 ProvenanceAgentsPointcutUtil.ifHasCallHooks(theRequestDetails, myInterceptorBroadcaster); 108 109 // Use the builder to construct MergeOperationInputParameters 110 MergeOperationInputParameters mergeOperationParameters = 111 MergeOperationParametersUtil.inputParamsFromOperationParams( 112 theSourcePatientIdentifier, 113 theTargetPatientIdentifier, 114 theSourcePatient, 115 theTargetPatient, 116 thePreview, 117 theDeleteSource, 118 theResultPatient, 119 provenanceAgents, 120 theRequestDetails.getResource(), 121 resourceLimit); 122 123 MergeOperationOutcome mergeOutcome = 124 myResourceMergeService.merge(mergeOperationParameters, theRequestDetails); 125 126 theServletResponse.setStatus(mergeOutcome.getHttpStatusCode()); 127 return MergeOperationParametersUtil.buildMergeOperationOutputParameters( 128 myFhirContext, mergeOutcome, theRequestDetails.getResource()); 129 } finally { 130 endRequest(theServletRequest); 131 } 132 } 133 134 /** 135 * /Patient/$hapi.fhir.undo-merge 136 */ 137 @Operation(name = ProviderConstants.OPERATION_UNDO_MERGE) 138 public IBaseParameters patientUndoMerge( 139 HttpServletRequest theServletRequest, 140 HttpServletResponse theServletResponse, 141 ServletRequestDetails theRequestDetails, 142 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_SOURCE_PATIENT_IDENTIFIER) 143 List<Identifier> theSourcePatientIdentifier, 144 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_TARGET_PATIENT_IDENTIFIER) 145 List<Identifier> theTargetPatientIdentifier, 146 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_SOURCE_PATIENT, max = 1) 147 IBaseReference theSourcePatient, 148 @OperationParam(name = ProviderConstants.OPERATION_MERGE_PARAM_TARGET_PATIENT, max = 1) 149 IBaseReference theTargetPatient) { 150 151 startRequest(theServletRequest); 152 153 try { 154 // create input parameters 155 UndoMergeOperationInputParameters inputParameters = buildUndoMergeOperationInputParameters( 156 theSourcePatientIdentifier, theTargetPatientIdentifier, theSourcePatient, theTargetPatient); 157 158 // now call the undo service with parameters 159 OperationOutcomeWithStatusCode undomergeOutcome = 160 myResourceUndoMergeService.undoMerge(inputParameters, theRequestDetails); 161 theServletResponse.setStatus(undomergeOutcome.getHttpStatusCode()); 162 IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext); 163 164 ParametersUtil.addParameterToParameters( 165 myFhirContext, 166 retVal, 167 ProviderConstants.OPERATION_UNDO_MERGE_OUTCOME, 168 undomergeOutcome.getOperationOutcome()); 169 return retVal; 170 } finally { 171 endRequest(theServletRequest); 172 } 173 } 174 175 private UndoMergeOperationInputParameters buildUndoMergeOperationInputParameters( 176 List<Identifier> theSourcePatientIdentifier, 177 List<Identifier> theTargetPatientIdentifier, 178 IBaseReference theSourcePatient, 179 IBaseReference theTargetPatient) { 180 181 int resourceLimit = myStorageSettings.getInternalSynchronousSearchSize(); 182 183 UndoMergeOperationInputParameters undoMergeOperationParameters = 184 new UndoMergeOperationInputParameters(resourceLimit); 185 186 setCommonMergeOperationInputParameters( 187 undoMergeOperationParameters, 188 theSourcePatientIdentifier, 189 theTargetPatientIdentifier, 190 theSourcePatient, 191 theTargetPatient); 192 193 return undoMergeOperationParameters; 194 } 195 196 private void setCommonMergeOperationInputParameters( 197 MergeOperationsCommonInputParameters theMergeOperationParameters, 198 List<Identifier> theSourcePatientIdentifier, 199 List<Identifier> theTargetPatientIdentifier, 200 IBaseReference theSourcePatient, 201 IBaseReference theTargetPatient) { 202 if (theSourcePatientIdentifier != null) { 203 List<CanonicalIdentifier> sourceResourceIdentifiers = theSourcePatientIdentifier.stream() 204 .map(CanonicalIdentifier::fromIdentifier) 205 .collect(Collectors.toList()); 206 theMergeOperationParameters.setSourceResourceIdentifiers(sourceResourceIdentifiers); 207 } 208 if (theTargetPatientIdentifier != null) { 209 List<CanonicalIdentifier> targetResourceIdentifiers = theTargetPatientIdentifier.stream() 210 .map(CanonicalIdentifier::fromIdentifier) 211 .collect(Collectors.toList()); 212 theMergeOperationParameters.setTargetResourceIdentifiers(targetResourceIdentifiers); 213 } 214 theMergeOperationParameters.setSourceResource(theSourcePatient); 215 theMergeOperationParameters.setTargetResource(theTargetPatient); 216 } 217}