001package ca.uhn.fhir.jpa.mdm.svc.candidate;
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.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
026import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
027import ca.uhn.fhir.mdm.api.IMdmLink;
028import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
029import ca.uhn.fhir.mdm.api.MatchedTarget;
030import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
031import ca.uhn.fhir.mdm.log.Logs;
032import ca.uhn.fhir.rest.api.Constants;
033import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
034import org.hl7.fhir.instance.model.api.IAnyResource;
035import org.hl7.fhir.instance.model.api.IBaseResource;
036import org.slf4j.Logger;
037import org.springframework.beans.factory.annotation.Autowired;
038import org.springframework.stereotype.Service;
039
040import java.util.ArrayList;
041import java.util.List;
042import java.util.Optional;
043import java.util.stream.Collectors;
044
045@Service
046public class FindCandidateByExampleSvc extends BaseCandidateFinder {
047        private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
048        @Autowired
049        IIdHelperService myIdHelperService;
050        @Autowired
051        private FhirContext myFhirContext;
052        @Autowired
053        private MdmLinkDaoSvc myMdmLinkDaoSvc;
054        @Autowired
055        private IMdmMatchFinderSvc myMdmMatchFinderSvc;
056
057        /**
058         * Attempt to find matching Golden Resources by resolving them from similar Matching target resources. Runs MDM logic
059         * over the existing target resources, then finds their entries in the MdmLink table, and returns all the matches
060         * found therein.
061         *
062         * @param theTarget the {@link IBaseResource} which we want to find candidate Golden Resources for.
063         * @return an Optional list of {@link MatchedGoldenResourceCandidate} indicating matches.
064         */
065        @Override
066        protected List<MatchedGoldenResourceCandidate> findMatchGoldenResourceCandidates(IAnyResource theTarget) {
067                List<MatchedGoldenResourceCandidate> retval = new ArrayList<>();
068
069                List<ResourcePersistentId> goldenResourcePidsToExclude = getNoMatchGoldenResourcePids(theTarget);
070
071                List<MatchedTarget> matchedCandidates = myMdmMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theTarget), theTarget, (RequestPartitionId) theTarget.getUserData(Constants.RESOURCE_PARTITION_ID));
072
073                // Convert all possible match targets to their equivalent Golden Resources by looking up in the MdmLink table,
074                // while ensuring that the matches aren't in our NO_MATCH list.
075                // The data flow is as follows ->
076                // MatchedTargetCandidate -> Golden Resource -> MdmLink -> MatchedGoldenResourceCandidate
077                matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList());
078                List<String> skippedLogMessages = new ArrayList<>();
079                List<String> matchedLogMessages = new ArrayList<>();
080                for (MatchedTarget match : matchedCandidates) {
081                        Optional<? extends IMdmLink> optionalMdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(RequestPartitionId.allPartitions(), match.getTarget()));
082                        if (!optionalMdmLink.isPresent()) {
083                                if (ourLog.isDebugEnabled()) {
084                                        skippedLogMessages.add(String.format("%s does not link to a Golden Resource (it may be a Golden Resource itself).  Removing candidate.", match.getTarget().getIdElement().toUnqualifiedVersionless()));
085                                }
086                                continue;
087                        }
088
089
090                        IMdmLink matchMdmLink = optionalMdmLink.get();
091                        if (goldenResourcePidsToExclude.contains(matchMdmLink.getGoldenResourcePersistenceId())) {
092                                skippedLogMessages.add(String.format("Skipping MDM on candidate Golden Resource with PID %s due to manual NO_MATCH", matchMdmLink.getGoldenResourcePersistenceId().toString()));
093                                continue;
094                        }
095
096                        MatchedGoldenResourceCandidate candidate = new MatchedGoldenResourceCandidate(matchMdmLink.getGoldenResourcePersistenceId(), match.getMatchResult());
097
098                        if (ourLog.isDebugEnabled()) {
099                                matchedLogMessages.add(String.format("Navigating from matched resource %s to its Golden Resource %s", match.getTarget().getIdElement().toUnqualifiedVersionless(), matchMdmLink.getGoldenResourcePersistenceId().toString()));
100                        }
101
102                        retval.add(candidate);
103                }
104
105                if (ourLog.isDebugEnabled()) {
106                        for (String logMessage: skippedLogMessages) {
107                                ourLog.debug(logMessage);
108                        }
109                        for (String logMessage: matchedLogMessages) {
110                                ourLog.debug(logMessage);
111                        }
112                }
113                return retval;
114        }
115
116        private List<ResourcePersistentId> getNoMatchGoldenResourcePids(IBaseResource theBaseResource) {
117                ResourcePersistentId targetPid = myIdHelperService.getPidOrNull(RequestPartitionId.allPartitions(), theBaseResource);
118                return myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(targetPid, MdmMatchResultEnum.NO_MATCH)
119                        .stream()
120                        .map(IMdmLink::getGoldenResourcePersistenceId)
121                        .collect(Collectors.toList());
122        }
123
124        @Override
125        protected CandidateStrategyEnum getStrategy() {
126                return CandidateStrategyEnum.SCORE;
127        }
128}