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}