001/*
002 * #%L
003 * HAPI FHIR JPA Server
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.jpa.term;
021
022import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao;
023import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao;
024import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao;
025import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
026import ca.uhn.fhir.jpa.entity.TermValueSet;
027import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
028import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
029import ca.uhn.fhir.util.ValidateUtil;
030import jakarta.annotation.Nonnull;
031
032import java.util.Collection;
033import java.util.List;
034import java.util.Optional;
035
036import static org.apache.commons.lang3.StringUtils.isAnyBlank;
037import static org.apache.commons.lang3.StringUtils.isNoneBlank;
038import static org.apache.commons.lang3.StringUtils.isNotBlank;
039
040public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator {
041        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValueSetConceptAccumulator.class);
042
043        private TermValueSet myTermValueSet;
044        private final ITermValueSetDao myValueSetDao;
045        private final ITermValueSetConceptDao myValueSetConceptDao;
046        private final ITermValueSetConceptDesignationDao myValueSetConceptDesignationDao;
047        private int myConceptsSaved;
048        private int myDesignationsSaved;
049        private int myConceptsExcluded;
050
051        private boolean mySupportLegacyLob = false;
052
053        public ValueSetConceptAccumulator(
054                        @Nonnull TermValueSet theTermValueSet,
055                        @Nonnull ITermValueSetDao theValueSetDao,
056                        @Nonnull ITermValueSetConceptDao theValueSetConceptDao,
057                        @Nonnull ITermValueSetConceptDesignationDao theValueSetConceptDesignationDao) {
058                myTermValueSet = theTermValueSet;
059                myValueSetDao = theValueSetDao;
060                myValueSetConceptDao = theValueSetConceptDao;
061                myValueSetConceptDesignationDao = theValueSetConceptDesignationDao;
062                myConceptsSaved = 0;
063                myDesignationsSaved = 0;
064                myConceptsExcluded = 0;
065        }
066
067        @Override
068        public void addMessage(String theMessage) {
069                // ignore for now
070
071        }
072
073        @Override
074        public void includeConcept(
075                        String theSystem,
076                        String theCode,
077                        String theDisplay,
078                        Long theSourceConceptPid,
079                        String theSourceConceptDirectParentPids,
080                        String theSystemVersion) {
081                saveConcept(
082                                theSystem,
083                                theCode,
084                                theDisplay,
085                                theSourceConceptPid,
086                                theSourceConceptDirectParentPids,
087                                theSystemVersion);
088        }
089
090        @Override
091        public void includeConceptWithDesignations(
092                        String theSystem,
093                        String theCode,
094                        String theDisplay,
095                        Collection<TermConceptDesignation> theDesignations,
096                        Long theSourceConceptPid,
097                        String theSourceConceptDirectParentPids,
098                        String theSystemVersion) {
099                TermValueSetConcept concept = saveConcept(
100                                theSystem,
101                                theCode,
102                                theDisplay,
103                                theSourceConceptPid,
104                                theSourceConceptDirectParentPids,
105                                theSystemVersion);
106                if (theDesignations != null) {
107                        for (TermConceptDesignation designation : theDesignations) {
108                                saveConceptDesignation(concept, designation);
109                        }
110                }
111        }
112
113        @Override
114        public boolean excludeConcept(String theSystem, String theCode) {
115                if (isAnyBlank(theSystem, theCode)) {
116                        return false;
117                }
118
119                // Get existing entity so it can be deleted.
120                Optional<TermValueSetConcept> optionalConcept;
121                int versionIdx = theSystem.indexOf("|");
122                if (versionIdx >= 0) {
123                        String systemUrl = theSystem.substring(0, versionIdx);
124                        String systemVersion = theSystem.substring(versionIdx + 1);
125                        optionalConcept = myValueSetConceptDao.findByTermValueSetIdSystemAndCodeWithVersion(
126                                        myTermValueSet.getId(), systemUrl, systemVersion, theCode);
127                } else {
128                        optionalConcept =
129                                        myValueSetConceptDao.findByTermValueSetIdSystemAndCode(myTermValueSet.getId(), theSystem, theCode);
130                }
131
132                if (optionalConcept.isPresent()) {
133                        TermValueSetConcept concept = optionalConcept.get();
134
135                        ourLog.debug(
136                                        "Excluding [{}|{}] from ValueSet[{}]",
137                                        concept.getSystem(),
138                                        concept.getCode(),
139                                        myTermValueSet.getUrl());
140                        for (TermValueSetConceptDesignation designation : concept.getDesignations()) {
141                                myValueSetConceptDesignationDao.deleteById(designation.getId());
142                                myTermValueSet.decrementTotalConceptDesignations();
143                        }
144                        myValueSetConceptDao.deleteById(concept.getId());
145                        myTermValueSet.decrementTotalConcepts();
146                        myValueSetDao.save(myTermValueSet);
147                        ourLog.debug(
148                                        "Done excluding [{}|{}] from ValueSet[{}]",
149                                        concept.getSystem(),
150                                        concept.getCode(),
151                                        myTermValueSet.getUrl());
152
153                        if (++myConceptsExcluded % 250 == 0) {
154                                ourLog.info("Have excluded {} concepts from ValueSet[{}]", myConceptsExcluded, myTermValueSet.getUrl());
155                        }
156                }
157                return false;
158        }
159
160        private TermValueSetConcept saveConcept(
161                        String theSystem,
162                        String theCode,
163                        String theDisplay,
164                        Long theSourceConceptPid,
165                        String theSourceConceptDirectParentPids,
166                        String theSystemVersion) {
167                ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "ValueSet contains a concept with no system value");
168                ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "ValueSet contains a concept with no code value");
169
170                TermValueSetConcept concept = new TermValueSetConcept();
171                concept.setValueSet(myTermValueSet);
172                concept.setOrder(myConceptsSaved);
173                int versionIndex = theSystem.indexOf("|");
174                if (versionIndex >= 0) {
175                        concept.setSystem(theSystem.substring(0, versionIndex));
176                        concept.setSystemVersion(theSystem.substring(versionIndex + 1));
177                } else {
178                        concept.setSystem(theSystem);
179                }
180                concept.setCode(theCode);
181                if (isNotBlank(theDisplay)) {
182                        concept.setDisplay(theDisplay);
183                }
184                concept.setSystemVersion(theSystemVersion);
185
186                concept.setSourceConceptPid(theSourceConceptPid);
187                concept.setSourceConceptDirectParentPids(theSourceConceptDirectParentPids);
188
189                if (!mySupportLegacyLob) {
190                        concept.clearSourceConceptDirectParentPidsLob();
191                }
192
193                myValueSetConceptDao.save(concept);
194                myValueSetDao.save(myTermValueSet.incrementTotalConcepts());
195
196                if (++myConceptsSaved % 250 == 0) {
197                        ourLog.info("Have pre-expanded {} concepts in ValueSet[{}]", myConceptsSaved, myTermValueSet.getUrl());
198                }
199
200                return concept;
201        }
202
203        private TermValueSetConceptDesignation saveConceptDesignation(
204                        TermValueSetConcept theConcept, TermConceptDesignation theDesignation) {
205                ValidateUtil.isNotBlankOrThrowInvalidRequest(
206                                theDesignation.getValue(), "ValueSet contains a concept designation with no value");
207
208                TermValueSetConceptDesignation designation = new TermValueSetConceptDesignation();
209                designation.setConcept(theConcept);
210                designation.setValueSet(myTermValueSet);
211                designation.setLanguage(theDesignation.getLanguage());
212                if (isNoneBlank(theDesignation.getUseSystem(), theDesignation.getUseCode())) {
213                        designation.setUseSystem(theDesignation.getUseSystem());
214                        designation.setUseCode(theDesignation.getUseCode());
215                        if (isNotBlank(theDesignation.getUseDisplay())) {
216                                designation.setUseDisplay(theDesignation.getUseDisplay());
217                        }
218                }
219                designation.setValue(theDesignation.getValue());
220                myValueSetConceptDesignationDao.save(designation);
221                myValueSetDao.save(myTermValueSet.incrementTotalConceptDesignations());
222
223                if (++myDesignationsSaved % 250 == 0) {
224                        ourLog.debug(
225                                        "Have pre-expanded {} designations for Concept[{}|{}] in ValueSet[{}]",
226                                        myDesignationsSaved,
227                                        theConcept.getSystem(),
228                                        theConcept.getCode(),
229                                        myTermValueSet.getUrl());
230                }
231
232                return designation;
233        }
234
235        public Boolean removeGapsFromConceptOrder() {
236                if (myConceptsExcluded <= 0) {
237                        return false;
238                }
239
240                ourLog.info("Removing gaps from concept order for ValueSet[{}]", myTermValueSet.getUrl());
241                int order = 0;
242                List<Long> conceptIds = myValueSetConceptDao.findIdsByTermValueSetId(myTermValueSet.getId());
243                for (Long conceptId : conceptIds) {
244                        myValueSetConceptDao.updateOrderById(conceptId, order++);
245                }
246                ourLog.info(
247                                "Have removed gaps from concept order for {} concepts in ValueSet[{}]",
248                                conceptIds.size(),
249                                myTermValueSet.getUrl());
250
251                return true;
252        }
253
254        public int getConceptsSaved() {
255                return myConceptsSaved;
256        }
257
258        // TODO: DM 2019-07-16 - We may need TermValueSetConceptProperty, similar to TermConceptProperty.
259        // TODO: DM 2019-07-16 - If so, we should also populate TermValueSetConceptProperty entities here.
260        // TODO: DM 2019-07-30 - Expansions don't include the properties themselves; they may be needed to facilitate
261        // filters and parameterized expansions.
262
263        public ValueSetConceptAccumulator setSupportLegacyLob(boolean theSupportLegacyLob) {
264                mySupportLegacyLob = theSupportLegacyLob;
265                return this;
266        }
267}