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}