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.rules.json; 021 022import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; 023import ca.uhn.fhir.model.api.IModelJson; 024import com.fasterxml.jackson.annotation.JsonProperty; 025import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 026import com.fasterxml.jackson.databind.util.StdConverter; 027import com.google.common.annotations.VisibleForTesting; 028import org.apache.commons.lang3.StringUtils; 029import org.apache.commons.lang3.Validate; 030 031import java.util.ArrayList; 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037import java.util.stream.Collectors; 038 039import static ca.uhn.fhir.mdm.api.MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE; 040 041@JsonDeserialize(converter = MdmRulesJson.MdmRulesJsonConverter.class) 042public class MdmRulesJson implements IModelJson { 043 044 @JsonProperty(value = "version", required = true) 045 String myVersion; 046 047 @JsonProperty(value = "candidateSearchParams", required = true) 048 List<MdmResourceSearchParamJson> myCandidateSearchParams = new ArrayList<>(); 049 050 @JsonProperty(value = "candidateFilterSearchParams", required = true) 051 List<MdmFilterSearchParamJson> myCandidateFilterSearchParams = new ArrayList<>(); 052 053 @JsonProperty(value = "matchFields", required = true) 054 List<MdmFieldMatchJson> myMatchFieldJsonList = new ArrayList<>(); 055 056 @JsonProperty(value = "matchResultMap", required = true) 057 Map<String, MdmMatchResultEnum> myMatchResultMap = new HashMap<>(); 058 059 /** 060 * This field is deprecated, use eidSystems instead. 061 */ 062 @Deprecated 063 @JsonProperty(value = "eidSystem") 064 String myEnterpriseEIDSystem; 065 066 @JsonProperty(value = "eidSystems") 067 Map<String, String> myEnterpriseEidSystems = new HashMap<>(); 068 069 @JsonProperty(value = "mdmTypes") 070 List<String> myMdmTypes; 071 072 transient VectorMatchResultMap myVectorMatchResultMap; 073 074 public void addMatchField(MdmFieldMatchJson theMatchRuleName) { 075 myMatchFieldJsonList.add(theMatchRuleName); 076 } 077 078 public void addResourceSearchParam(MdmResourceSearchParamJson theSearchParam) { 079 myCandidateSearchParams.add(theSearchParam); 080 } 081 082 public void addFilterSearchParam(MdmFilterSearchParamJson theSearchParam) { 083 myCandidateFilterSearchParams.add(theSearchParam); 084 } 085 086 int size() { 087 return myMatchFieldJsonList.size(); 088 } 089 090 MdmFieldMatchJson get(int theIndex) { 091 return myMatchFieldJsonList.get(theIndex); 092 } 093 094 MdmMatchResultEnum getMatchResult(String theFieldMatchNames) { 095 return myMatchResultMap.get(theFieldMatchNames); 096 } 097 098 public MdmMatchResultEnum getMatchResult(Long theMatchVector) { 099 return myVectorMatchResultMap.get(theMatchVector); 100 } 101 102 public void putMatchResult(String theFieldMatchNames, MdmMatchResultEnum theMatchResult) { 103 myMatchResultMap.put(theFieldMatchNames, theMatchResult); 104 initialize(); 105 } 106 107 Map<String, MdmMatchResultEnum> getMatchResultMap() { 108 return Collections.unmodifiableMap(myMatchResultMap); 109 } 110 111 /** 112 * Must call initialize() before calling getMatchResult(Long) 113 */ 114 public void initialize() { 115 myVectorMatchResultMap = new VectorMatchResultMap(this); 116 } 117 118 public List<MdmFieldMatchJson> getMatchFields() { 119 return Collections.unmodifiableList(myMatchFieldJsonList); 120 } 121 122 public List<MdmResourceSearchParamJson> getCandidateSearchParams() { 123 return Collections.unmodifiableList(myCandidateSearchParams); 124 } 125 126 public List<MdmFilterSearchParamJson> getCandidateFilterSearchParams() { 127 return Collections.unmodifiableList(myCandidateFilterSearchParams); 128 } 129 130 /** 131 * Use {@link this#getEnterpriseEIDSystemForResourceType(String)} instead. 132 */ 133 @Deprecated 134 public String getEnterpriseEIDSystem() { 135 return myEnterpriseEIDSystem; 136 } 137 138 /** 139 * Use {@link this#setEnterpriseEIDSystems(Map)} (String)} or {@link this#addEnterpriseEIDSystem(String, String)} instead. 140 */ 141 @Deprecated 142 public void setEnterpriseEIDSystem(String theEnterpriseEIDSystem) { 143 myEnterpriseEIDSystem = theEnterpriseEIDSystem; 144 } 145 146 public void setEnterpriseEIDSystems(Map<String, String> theEnterpriseEIDSystems) { 147 myEnterpriseEidSystems = theEnterpriseEIDSystems; 148 } 149 150 public void addEnterpriseEIDSystem(String theResourceType, String theEidSystem) { 151 if (myEnterpriseEidSystems == null) { 152 myEnterpriseEidSystems = new HashMap<>(); 153 } 154 myEnterpriseEidSystems.put(theResourceType, theEidSystem); 155 } 156 157 public Map<String, String> getEnterpriseEIDSystems() { 158 // First try the new property. 159 if (myEnterpriseEidSystems != null && !myEnterpriseEidSystems.isEmpty()) { 160 return myEnterpriseEidSystems; 161 // If that fails, fall back to our deprecated property. 162 } else if (!StringUtils.isBlank(myEnterpriseEIDSystem)) { 163 HashMap<String, String> retVal = new HashMap<>(); 164 retVal.put(ALL_RESOURCE_SEARCH_PARAM_TYPE, myEnterpriseEIDSystem); 165 return retVal; 166 // Otherwise, return an empty map. 167 } else { 168 return Collections.emptyMap(); 169 } 170 } 171 172 public String getEnterpriseEIDSystemForResourceType(String theResourceType) { 173 Map<String, String> enterpriseEIDSystems = getEnterpriseEIDSystems(); 174 if (enterpriseEIDSystems.containsKey(ALL_RESOURCE_SEARCH_PARAM_TYPE)) { 175 return enterpriseEIDSystems.get(ALL_RESOURCE_SEARCH_PARAM_TYPE); 176 } else { 177 return enterpriseEIDSystems.get(theResourceType); 178 } 179 } 180 181 public String getVersion() { 182 return myVersion; 183 } 184 185 public MdmRulesJson setVersion(String theVersion) { 186 myVersion = theVersion; 187 return this; 188 } 189 190 private void validate() { 191 Validate.notBlank(myVersion, "version may not be blank"); 192 193 Map<String, String> enterpriseEIDSystems = getEnterpriseEIDSystems(); 194 195 // If we have a * eid system, there should only be one. 196 if (enterpriseEIDSystems.containsKey(ALL_RESOURCE_SEARCH_PARAM_TYPE)) { 197 Validate.isTrue(enterpriseEIDSystems.size() == 1); 198 } 199 } 200 201 public String getSummary() { 202 return myCandidateSearchParams.size() + " Candidate Search Params, " + myCandidateFilterSearchParams.size() 203 + " Filter Search Params, " + myMatchFieldJsonList.size() 204 + " Match Fields, " + myMatchResultMap.size() 205 + " Match Result Entries"; 206 } 207 208 public String getFieldMatchNamesForVector(long theVector) { 209 return myVectorMatchResultMap.getFieldMatchNames(theVector); 210 } 211 212 public Set<Map.Entry<String, MdmMatchResultEnum>> getMatchedRulesFromVectorMap(Long theLong) { 213 Set<String> matchedRules = myVectorMatchResultMap.getMatchedRules(theLong); 214 return myMatchResultMap.entrySet().stream() 215 .filter(e -> matchedRules.contains(e.getKey())) 216 .collect(Collectors.toSet()); 217 } 218 219 public String getDetailedFieldMatchResultWithSuccessInformation(long theVector) { 220 List<String> fieldMatchResult = new ArrayList<>(); 221 for (int i = 0; i < myMatchFieldJsonList.size(); ++i) { 222 if ((theVector & (1 << i)) == 0) { 223 fieldMatchResult.add(myMatchFieldJsonList.get(i).getName() + ": NO"); 224 } else { 225 fieldMatchResult.add(myMatchFieldJsonList.get(i).getName() + ": YES"); 226 } 227 } 228 return String.join("\n", fieldMatchResult); 229 } 230 231 @VisibleForTesting 232 VectorMatchResultMap getVectorMatchResultMapForUnitTest() { 233 return myVectorMatchResultMap; 234 } 235 236 /** 237 * Ensure the vector map is initialized after we deserialize 238 */ 239 static class MdmRulesJsonConverter extends StdConverter<MdmRulesJson, MdmRulesJson> { 240 241 /** 242 * This empty constructor is required by Jackson 243 */ 244 public MdmRulesJsonConverter() {} 245 246 @Override 247 public MdmRulesJson convert(MdmRulesJson theMdmRulesJson) { 248 theMdmRulesJson.validate(); 249 theMdmRulesJson.initialize(); 250 return theMdmRulesJson; 251 } 252 } 253 254 public List<String> getMdmTypes() { 255 return myMdmTypes; 256 } 257 258 public void setMdmTypes(List<String> theMdmTypes) { 259 myMdmTypes = theMdmTypes; 260 } 261}