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