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}