001/*-
002 * #%L
003 * HAPI FHIR JPA Server - Master Data Management
004 * %%
005 * Copyright (C) 2014 - 2023 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.mdm.svc;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
026import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
027import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
028import ca.uhn.fhir.mdm.api.IMdmLink;
029import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
030import ca.uhn.fhir.mdm.api.IMdmSettings;
031import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
032import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
033import ca.uhn.fhir.mdm.log.Logs;
034import ca.uhn.fhir.mdm.model.MdmTransactionContext;
035import ca.uhn.fhir.mdm.util.MdmPartitionHelper;
036import ca.uhn.fhir.mdm.util.MdmResourceUtil;
037import ca.uhn.fhir.mdm.util.MessageHelper;
038import ca.uhn.fhir.rest.api.Constants;
039import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
040import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
041import org.hl7.fhir.instance.model.api.IAnyResource;
042import org.slf4j.Logger;
043import org.springframework.beans.factory.annotation.Autowired;
044import org.springframework.transaction.annotation.Transactional;
045
046import java.util.List;
047import java.util.Objects;
048import java.util.Optional;
049
050public class MdmLinkCreateSvcImpl implements IMdmLinkCreateSvc {
051        private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
052
053        @Autowired
054        FhirContext myFhirContext;
055
056        @Autowired
057        IIdHelperService myIdHelperService;
058
059        @Autowired
060        MdmLinkDaoSvc myMdmLinkDaoSvc;
061
062        @Autowired
063        IMdmSettings myMdmSettings;
064
065        @Autowired
066        MessageHelper myMessageHelper;
067
068        @Autowired
069        MdmPartitionHelper myMdmPartitionHelper;
070
071        @Transactional
072        @Override
073        public IAnyResource createLink(
074                        IAnyResource theGoldenResource,
075                        IAnyResource theSourceResource,
076                        MdmMatchResultEnum theMatchResult,
077                        MdmTransactionContext theMdmContext) {
078                String sourceType = myFhirContext.getResourceType(theSourceResource);
079
080                validateCreateLinkRequest(theGoldenResource, theSourceResource, sourceType);
081
082                IResourcePersistentId goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
083                IResourcePersistentId targetId = myIdHelperService.getPidOrThrowException(theSourceResource);
084
085                // check if the golden resource and the source resource are in the same partition, throw error if not
086                myMdmPartitionHelper.validateMdmResourcesPartitionMatches(theGoldenResource, theSourceResource);
087
088                Optional<? extends IMdmLink> optionalMdmLink =
089                                myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId);
090                if (optionalMdmLink.isPresent()) {
091                        throw new InvalidRequestException(
092                                        Msg.code(753) + myMessageHelper.getMessageForPresentLink(theGoldenResource, theSourceResource));
093                }
094
095                List<? extends IMdmLink> mdmLinks =
096                                myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(targetId, MdmMatchResultEnum.MATCH);
097                if (mdmLinks.size() > 0 && theMatchResult == MdmMatchResultEnum.MATCH) {
098                        throw new InvalidRequestException(
099                                        Msg.code(754) + myMessageHelper.getMessageForMultipleGoldenRecords(theSourceResource));
100                }
101
102                IMdmLink mdmLink = myMdmLinkDaoSvc.getOrCreateMdmLinkByGoldenResourceAndSourceResource(
103                                theGoldenResource, theSourceResource);
104                mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
105                mdmLink.setMdmSourceType(sourceType);
106                if (theMatchResult == null) {
107                        mdmLink.setMatchResult(MdmMatchResultEnum.MATCH);
108                } else {
109                        mdmLink.setMatchResult(theMatchResult);
110                }
111                // Add partition for the mdm link if it doesn't exist
112                RequestPartitionId goldenResourcePartitionId =
113                                (RequestPartitionId) theGoldenResource.getUserData(Constants.RESOURCE_PARTITION_ID);
114                if (goldenResourcePartitionId != null
115                                && goldenResourcePartitionId.hasPartitionIds()
116                                && goldenResourcePartitionId.getFirstPartitionIdOrNull() != null
117                                && (mdmLink.getPartitionId() == null || mdmLink.getPartitionId().getPartitionId() == null)) {
118                        mdmLink.setPartitionId(new PartitionablePartitionId(
119                                        goldenResourcePartitionId.getFirstPartitionIdOrNull(),
120                                        goldenResourcePartitionId.getPartitionDate()));
121                }
122                ourLog.info("Manually creating a " + theGoldenResource.getIdElement().toVersionless() + " to "
123                                + theSourceResource.getIdElement().toVersionless() + " mdm link.");
124                myMdmLinkDaoSvc.save(mdmLink);
125
126                return theGoldenResource;
127        }
128
129        private void validateCreateLinkRequest(
130                        IAnyResource theGoldenRecord, IAnyResource theSourceResource, String theSourceType) {
131                String goldenRecordType = myFhirContext.getResourceType(theGoldenRecord);
132
133                if (!myMdmSettings.isSupportedMdmType(goldenRecordType)) {
134                        throw new InvalidRequestException(Msg.code(755)
135                                        + myMessageHelper.getMessageForUnsupportedFirstArgumentTypeInUpdate(goldenRecordType));
136                }
137
138                if (!myMdmSettings.isSupportedMdmType(theSourceType)) {
139                        throw new InvalidRequestException(
140                                        Msg.code(756) + myMessageHelper.getMessageForUnsupportedSecondArgumentTypeInUpdate(theSourceType));
141                }
142
143                if (!Objects.equals(goldenRecordType, theSourceType)) {
144                        throw new InvalidRequestException(Msg.code(757)
145                                        + myMessageHelper.getMessageForArgumentTypeMismatchInUpdate(goldenRecordType, theSourceType));
146                }
147
148                if (!MdmResourceUtil.isMdmManaged(theGoldenRecord)) {
149                        throw new InvalidRequestException(Msg.code(758) + myMessageHelper.getMessageForUnmanagedResource());
150                }
151
152                if (!MdmResourceUtil.isMdmAllowed(theSourceResource)) {
153                        throw new InvalidRequestException(Msg.code(759) + myMessageHelper.getMessageForUnsupportedSourceResource());
154                }
155        }
156}