001package ca.uhn.fhir.jpa.mdm.svc;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server - Master Data Management
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.interceptor.model.RequestPartitionId;
026import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
027import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
028import ca.uhn.fhir.jpa.mdm.util.MdmPartitionHelper;
029import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
030import ca.uhn.fhir.mdm.api.IMdmLink;
031import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
032import ca.uhn.fhir.mdm.api.IMdmSettings;
033import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
034import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
035import ca.uhn.fhir.mdm.log.Logs;
036import ca.uhn.fhir.mdm.model.MdmTransactionContext;
037import ca.uhn.fhir.mdm.util.MdmResourceUtil;
038import ca.uhn.fhir.mdm.util.MessageHelper;
039import ca.uhn.fhir.rest.api.Constants;
040import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
041import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
042import org.hl7.fhir.instance.model.api.IAnyResource;
043import org.slf4j.Logger;
044import org.springframework.beans.factory.annotation.Autowired;
045import org.springframework.transaction.annotation.Transactional;
046
047import java.util.List;
048import java.util.Objects;
049import java.util.Optional;
050
051public class MdmLinkCreateSvcImpl implements IMdmLinkCreateSvc {
052        private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
053
054        @Autowired
055        FhirContext myFhirContext;
056        @Autowired
057        IIdHelperService myIdHelperService;
058        @Autowired
059        MdmLinkDaoSvc myMdmLinkDaoSvc;
060        @Autowired
061        IMdmSettings myMdmSettings;
062        @Autowired
063        MessageHelper myMessageHelper;
064        @Autowired
065        MdmPartitionHelper myMdmPartitionHelper;
066
067        @Transactional
068        @Override
069        public IAnyResource createLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext) {
070                String sourceType = myFhirContext.getResourceType(theSourceResource);
071
072                validateCreateLinkRequest(theGoldenResource, theSourceResource, sourceType);
073
074                ResourcePersistentId goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
075                ResourcePersistentId targetId = myIdHelperService.getPidOrThrowException(theSourceResource);
076
077                // check if the golden resource and the source resource are in the same partition, throw error if not
078                myMdmPartitionHelper.validateResourcesInSamePartition(theGoldenResource, theSourceResource);
079
080                Optional<? extends IMdmLink> optionalMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId);
081                if (optionalMdmLink.isPresent()) {
082                        throw new InvalidRequestException(Msg.code(753) + myMessageHelper.getMessageForPresentLink(theGoldenResource, theSourceResource));
083                }
084
085                List<? extends IMdmLink> mdmLinks = myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(targetId, MdmMatchResultEnum.MATCH);
086                if (mdmLinks.size() > 0 && theMatchResult == MdmMatchResultEnum.MATCH) {
087                        throw new InvalidRequestException(Msg.code(754) + myMessageHelper.getMessageForMultipleGoldenRecords(theSourceResource));
088                }
089
090                IMdmLink mdmLink = myMdmLinkDaoSvc.getOrCreateMdmLinkByGoldenResourceAndSourceResource(theGoldenResource, theSourceResource);
091                mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
092                mdmLink.setMdmSourceType(sourceType);
093                if (theMatchResult == null) {
094                        mdmLink.setMatchResult(MdmMatchResultEnum.MATCH);
095                } else {
096                        mdmLink.setMatchResult(theMatchResult);
097                }
098                // Add partition for the mdm link if it doesn't exist
099                RequestPartitionId goldenResourcePartitionId = (RequestPartitionId) theGoldenResource.getUserData(Constants.RESOURCE_PARTITION_ID);
100                if (goldenResourcePartitionId != null && goldenResourcePartitionId.hasPartitionIds() && goldenResourcePartitionId.getFirstPartitionIdOrNull() != null &&
101                        (mdmLink.getPartitionId() == null || mdmLink.getPartitionId().getPartitionId() == null)) {
102                        mdmLink.setPartitionId(new PartitionablePartitionId(goldenResourcePartitionId.getFirstPartitionIdOrNull(), goldenResourcePartitionId.getPartitionDate()));
103                }
104                ourLog.info("Manually creating a " + theGoldenResource.getIdElement().toVersionless() + " to " + theSourceResource.getIdElement().toVersionless() + " mdm link.");
105                myMdmLinkDaoSvc.save(mdmLink);
106
107                return theGoldenResource;
108        }
109
110        private void validateCreateLinkRequest(IAnyResource theGoldenRecord, IAnyResource theSourceResource, String theSourceType) {
111                String goldenRecordType = myFhirContext.getResourceType(theGoldenRecord);
112
113                if (!myMdmSettings.isSupportedMdmType(goldenRecordType)) {
114                        throw new InvalidRequestException(Msg.code(755) + myMessageHelper.getMessageForUnsupportedFirstArgumentTypeInUpdate(goldenRecordType));
115                }
116
117                if (!myMdmSettings.isSupportedMdmType(theSourceType)) {
118                        throw new InvalidRequestException(Msg.code(756) + myMessageHelper.getMessageForUnsupportedSecondArgumentTypeInUpdate(theSourceType));
119                }
120
121                if (!Objects.equals(goldenRecordType, theSourceType)) {
122                        throw new InvalidRequestException(Msg.code(757) + myMessageHelper.getMessageForArgumentTypeMismatchInUpdate(goldenRecordType, theSourceType));
123                }
124
125                if (!MdmResourceUtil.isMdmManaged(theGoldenRecord)) {
126                        throw new InvalidRequestException(Msg.code(758) + myMessageHelper.getMessageForUnmanagedResource());
127                }
128
129                if (!MdmResourceUtil.isMdmAllowed(theSourceResource)) {
130                        throw new InvalidRequestException(Msg.code(759) + myMessageHelper.getMessageForUnsupportedSourceResource());
131                }
132        }
133}