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