001/*- 002 * #%L 003 * HAPI FHIR - Master Data Management 004 * %% 005 * Copyright (C) 2014 - 2024 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.mdm.provider; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; 025import ca.uhn.fhir.mdm.api.paging.MdmPageLinkBuilder; 026import ca.uhn.fhir.mdm.api.paging.MdmPageLinkTuple; 027import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; 028import ca.uhn.fhir.mdm.model.MdmTransactionContext; 029import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson; 030import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson; 031import ca.uhn.fhir.rest.api.server.RequestDetails; 032import ca.uhn.fhir.rest.server.TransactionLogMessages; 033import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 034import ca.uhn.fhir.rest.server.provider.ProviderConstants; 035import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 036import ca.uhn.fhir.util.ParametersUtil; 037import jakarta.annotation.Nonnull; 038import jakarta.annotation.Nullable; 039import org.hl7.fhir.instance.model.api.IBase; 040import org.hl7.fhir.instance.model.api.IBaseParameters; 041import org.hl7.fhir.instance.model.api.IPrimitiveType; 042import org.springframework.data.domain.Page; 043 044import java.util.Arrays; 045import java.util.Collection; 046import java.util.Collections; 047import java.util.List; 048import java.util.Objects; 049import java.util.stream.Collectors; 050 051public abstract class BaseMdmProvider { 052 053 protected final FhirContext myFhirContext; 054 055 public BaseMdmProvider(FhirContext theFhirContext) { 056 myFhirContext = theFhirContext; 057 } 058 059 protected void validateMergeParameters( 060 IPrimitiveType<String> theFromGoldenResourceId, IPrimitiveType<String> theToGoldenResourceId) { 061 validateNotNull(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromGoldenResourceId); 062 validateNotNull(ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToGoldenResourceId); 063 if (theFromGoldenResourceId.getValue().equals(theToGoldenResourceId.getValue())) { 064 throw new InvalidRequestException( 065 Msg.code(1493) + "fromGoldenResourceId must be different from toGoldenResourceId"); 066 } 067 } 068 069 private void validateNotNull(String theName, IPrimitiveType<String> theString) { 070 if (theString == null || theString.getValue() == null) { 071 throw new InvalidRequestException(Msg.code(1494) + theName + " cannot be null"); 072 } 073 } 074 075 protected void validateMdmLinkHistoryParameters( 076 List<IPrimitiveType<String>> theGoldenResourceIds, List<IPrimitiveType<String>> theSourceIds) { 077 validateBothCannotBeNullOrEmpty( 078 ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, 079 theGoldenResourceIds, 080 ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, 081 theSourceIds); 082 } 083 084 private void validateBothCannotBeNullOrEmpty( 085 String theFirstName, 086 List<IPrimitiveType<String>> theFirstList, 087 String theSecondName, 088 List<IPrimitiveType<String>> theSecondList) { 089 if ((theFirstList == null || theFirstList.isEmpty()) && (theSecondList == null || theSecondList.isEmpty())) { 090 throw new InvalidRequestException(Msg.code(2292) + "Please include either [" + theFirstName + "]s, [" 091 + theSecondName + "]s, or both in your search inputs."); 092 } 093 } 094 095 protected void validateUpdateLinkParameters( 096 IPrimitiveType<String> theGoldenResourceId, 097 IPrimitiveType<String> theResourceId, 098 IPrimitiveType<String> theMatchResult) { 099 validateNotNull(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId); 100 validateNotNull(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theResourceId); 101 validateNotNull(ProviderConstants.MDM_UPDATE_LINK_MATCH_RESULT, theMatchResult); 102 MdmMatchResultEnum matchResult = MdmMatchResultEnum.valueOf(theMatchResult.getValue()); 103 switch (matchResult) { 104 case NO_MATCH: 105 case MATCH: 106 break; 107 default: 108 throw new InvalidRequestException(Msg.code(1495) + ProviderConstants.MDM_UPDATE_LINK + " illegal " 109 + ProviderConstants.MDM_UPDATE_LINK_MATCH_RESULT + " value '" + matchResult + "'. Must be " 110 + MdmMatchResultEnum.NO_MATCH + " or " + MdmMatchResultEnum.MATCH); 111 } 112 } 113 114 protected void validateNotDuplicateParameters( 115 IPrimitiveType<String> theGoldenResourceId, IPrimitiveType<String> theResourceId) { 116 validateNotNull(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId); 117 validateNotNull(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theResourceId); 118 } 119 120 protected void validateCreateLinkParameters( 121 IPrimitiveType<String> theGoldenResourceId, 122 IPrimitiveType<String> theResourceId, 123 @Nullable IPrimitiveType<String> theMatchResult) { 124 validateNotNull(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId); 125 validateNotNull(ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, theResourceId); 126 if (theMatchResult != null) { 127 MdmMatchResultEnum matchResult = MdmMatchResultEnum.valueOf(theMatchResult.getValue()); 128 switch (matchResult) { 129 case NO_MATCH: 130 case POSSIBLE_MATCH: 131 case MATCH: 132 break; 133 default: 134 throw new InvalidRequestException(Msg.code(1496) + ProviderConstants.MDM_CREATE_LINK + " illegal " 135 + ProviderConstants.MDM_CREATE_LINK_MATCH_RESULT + " value '" + matchResult + "'. Must be " 136 + MdmMatchResultEnum.NO_MATCH + ", " + MdmMatchResultEnum.MATCH + " or " 137 + MdmMatchResultEnum.POSSIBLE_MATCH); 138 } 139 } 140 } 141 142 protected MdmTransactionContext createMdmContext( 143 RequestDetails theRequestDetails, 144 MdmTransactionContext.OperationType theOperationType, 145 String theResourceType) { 146 TransactionLogMessages transactionLogMessages = 147 TransactionLogMessages.createFromTransactionGuid(theRequestDetails.getTransactionGuid()); 148 MdmTransactionContext mdmTransactionContext = 149 new MdmTransactionContext(transactionLogMessages, theOperationType); 150 mdmTransactionContext.setResourceType(theResourceType); 151 return mdmTransactionContext; 152 } 153 154 @Nonnull 155 protected List<String> convertToStringsIncludingCommaDelimitedIfNotNull( 156 List<IPrimitiveType<String>> thePrimitiveTypeStrings) { 157 if (thePrimitiveTypeStrings == null) { 158 return Collections.emptyList(); 159 } 160 161 return thePrimitiveTypeStrings.stream() 162 .map(this::extractStringOrNull) 163 .filter(Objects::nonNull) 164 .map(input -> Arrays.asList(input.split(","))) 165 .flatMap(Collection::stream) 166 .collect(Collectors.toUnmodifiableList()); 167 } 168 169 protected String extractStringOrNull(IPrimitiveType<String> theString) { 170 if (theString == null) { 171 return null; 172 } 173 return theString.getValue(); 174 } 175 176 protected IBaseParameters parametersFromMdmLinks( 177 Page<MdmLinkJson> theMdmLinkStream, 178 boolean theIncludeResultAndSource, 179 ServletRequestDetails theServletRequestDetails, 180 MdmPageRequest thePageRequest) { 181 IBaseParameters retval = ParametersUtil.newInstance(myFhirContext); 182 addPagingParameters(retval, theMdmLinkStream, theServletRequestDetails, thePageRequest); 183 184 long numDuplicates = theMdmLinkStream.getTotalElements(); 185 ParametersUtil.addParameterToParametersLong(myFhirContext, retval, "total", numDuplicates); 186 187 theMdmLinkStream.getContent().forEach(mdmLink -> { 188 IBase resultPart = ParametersUtil.addParameterToParameters(myFhirContext, retval, "link"); 189 ParametersUtil.addPartString(myFhirContext, resultPart, "goldenResourceId", mdmLink.getGoldenResourceId()); 190 ParametersUtil.addPartString(myFhirContext, resultPart, "sourceResourceId", mdmLink.getSourceId()); 191 192 if (theIncludeResultAndSource) { 193 ParametersUtil.addPartString( 194 myFhirContext, 195 resultPart, 196 "matchResult", 197 mdmLink.getMatchResult().name()); 198 ParametersUtil.addPartString( 199 myFhirContext, 200 resultPart, 201 "linkSource", 202 mdmLink.getLinkSource().name()); 203 ParametersUtil.addPartBoolean(myFhirContext, resultPart, "eidMatch", mdmLink.getEidMatch()); 204 ParametersUtil.addPartBoolean( 205 myFhirContext, resultPart, "hadToCreateNewResource", mdmLink.getLinkCreatedNewResource()); 206 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "score", mdmLink.getScore()); 207 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "linkCreated", (double) 208 mdmLink.getCreated().getTime()); 209 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "linkUpdated", (double) 210 mdmLink.getUpdated().getTime()); 211 } 212 }); 213 return retval; 214 } 215 216 protected void parametersFromMdmLinkRevisions( 217 IBaseParameters theRetVal, List<MdmLinkWithRevisionJson> theMdmLinkRevisions) { 218 if (theMdmLinkRevisions.isEmpty()) { 219 final IBase resultPart = ParametersUtil.addParameterToParameters( 220 myFhirContext, theRetVal, "historical links not found for query parameters"); 221 222 ParametersUtil.addPartString( 223 myFhirContext, resultPart, "theResults", "historical links not found for query parameters"); 224 } 225 226 theMdmLinkRevisions.forEach(mdmLinkRevision -> parametersFromMdmLinkRevision(theRetVal, mdmLinkRevision)); 227 } 228 229 private void parametersFromMdmLinkRevision(IBaseParameters retVal, MdmLinkWithRevisionJson mdmLinkRevision) { 230 final IBase resultPart = ParametersUtil.addParameterToParameters(myFhirContext, retVal, "historical link"); 231 final MdmLinkJson mdmLink = mdmLinkRevision.getMdmLink(); 232 233 ParametersUtil.addPartString(myFhirContext, resultPart, "goldenResourceId", mdmLink.getGoldenResourceId()); 234 ParametersUtil.addPartString( 235 myFhirContext, 236 resultPart, 237 "revisionTimestamp", 238 mdmLinkRevision.getRevisionTimestamp().toString()); 239 ParametersUtil.addPartString(myFhirContext, resultPart, "sourceResourceId", mdmLink.getSourceId()); 240 ParametersUtil.addPartString( 241 myFhirContext, 242 resultPart, 243 "matchResult", 244 mdmLink.getMatchResult().name()); 245 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "score", mdmLink.getScore()); 246 ParametersUtil.addPartString( 247 myFhirContext, resultPart, "linkSource", mdmLink.getLinkSource().name()); 248 ParametersUtil.addPartBoolean(myFhirContext, resultPart, "eidMatch", mdmLink.getEidMatch()); 249 ParametersUtil.addPartBoolean( 250 myFhirContext, resultPart, "hadToCreateNewResource", mdmLink.getLinkCreatedNewResource()); 251 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "score", mdmLink.getScore()); 252 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "linkCreated", (double) 253 mdmLink.getCreated().getTime()); 254 ParametersUtil.addPartDecimal(myFhirContext, resultPart, "linkUpdated", (double) 255 mdmLink.getUpdated().getTime()); 256 } 257 258 protected void addPagingParameters( 259 IBaseParameters theParameters, 260 Page<MdmLinkJson> theCurrentPage, 261 ServletRequestDetails theServletRequestDetails, 262 MdmPageRequest thePageRequest) { 263 MdmPageLinkTuple mdmPageLinkTuple = 264 MdmPageLinkBuilder.buildMdmPageLinks(theServletRequestDetails, theCurrentPage, thePageRequest); 265 266 if (mdmPageLinkTuple.getPreviousLink().isPresent()) { 267 ParametersUtil.addParameterToParametersUri( 268 myFhirContext, 269 theParameters, 270 "prev", 271 mdmPageLinkTuple.getPreviousLink().get()); 272 } 273 274 ParametersUtil.addParameterToParametersUri( 275 myFhirContext, theParameters, "self", mdmPageLinkTuple.getSelfLink()); 276 277 if (mdmPageLinkTuple.getNextLink().isPresent()) { 278 ParametersUtil.addParameterToParametersUri( 279 myFhirContext, 280 theParameters, 281 "next", 282 mdmPageLinkTuple.getNextLink().get()); 283 } 284 } 285}