001package org.hl7.fhir.dstu2.terminologies; 002 003/*- 004 * #%L 005 * org.hl7.fhir.dstu2 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import java.io.FileNotFoundException; 025import java.io.IOException; 026 027/* 028Copyright (c) 2011+, HL7, Inc 029All rights reserved. 030 031Redistribution and use in source and binary forms, with or without modification, 032are permitted provided that the following conditions are met: 033 034 * Redistributions of source code must retain the above copyright notice, this 035 list of conditions and the following disclaimer. 036 * Redistributions in binary form must reproduce the above copyright notice, 037 this list of conditions and the following disclaimer in the documentation 038 and/or other materials provided with the distribution. 039 * Neither the name of HL7 nor the names of its contributors may be used to 040 endorse or promote products derived from this software without specific 041 prior written permission. 042 043THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 044ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 045WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 046IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 047INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 048NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 049PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 050WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 051ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 052POSSIBILITY OF SUCH DAMAGE. 053 054*/ 055 056import java.util.ArrayList; 057import java.util.HashMap; 058import java.util.List; 059import java.util.Map; 060 061import org.apache.commons.lang3.NotImplementedException; 062import org.hl7.fhir.dstu2.model.DateTimeType; 063import org.hl7.fhir.dstu2.model.Factory; 064import org.hl7.fhir.dstu2.model.PrimitiveType; 065import org.hl7.fhir.dstu2.model.Type; 066import org.hl7.fhir.dstu2.model.UriType; 067import org.hl7.fhir.dstu2.model.ValueSet; 068import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionComponent; 069import org.hl7.fhir.dstu2.model.ValueSet.ConceptReferenceComponent; 070import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; 071import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetFilterComponent; 072import org.hl7.fhir.dstu2.model.ValueSet.FilterOperator; 073import org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent; 074import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; 075import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent; 076import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionParameterComponent; 077import org.hl7.fhir.dstu2.utils.IWorkerContext; 078import org.hl7.fhir.dstu2.utils.ToolingExtensions; 079import org.hl7.fhir.exceptions.TerminologyServiceException; 080import org.hl7.fhir.utilities.Utilities; 081 082public class ValueSetExpanderSimple implements ValueSetExpander { 083 084 private IWorkerContext context; 085 private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); 086 private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>(); 087 private ValueSet focus; 088 089 private ValueSetExpanderFactory factory; 090 091 public ValueSetExpanderSimple(IWorkerContext context, ValueSetExpanderFactory factory) { 092 super(); 093 this.context = context; 094 this.factory = factory; 095 } 096 097 @Override 098 public ValueSetExpansionOutcome expand(ValueSet source) { 099 100 try { 101 focus = source.copy(); 102 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 103 focus.getExpansion().setTimestampElement(DateTimeType.now()); 104 focus.getExpansion().setIdentifier(Factory.createUUID()); 105 106 handleDefine(source, focus.getExpansion().getParameter()); 107 if (source.hasCompose()) 108 handleCompose(source.getCompose(), focus.getExpansion().getParameter()); 109 110 for (ValueSetExpansionContainsComponent c : codes) { 111 if (map.containsKey(key(c))) { 112 focus.getExpansion().getContains().add(c); 113 } 114 } 115 return new ValueSetExpansionOutcome(focus, null); 116 } catch (Exception e) { 117 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 118 // that might fail too, but it might not, later. 119 return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage()); 120 } 121 } 122 123 private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException, ETooCostly, FileNotFoundException, IOException { 124 for (UriType imp : compose.getImport()) 125 importValueSet(imp.getValue(), params); 126 for (ConceptSetComponent inc : compose.getInclude()) 127 includeCodes(inc, params); 128 for (ConceptSetComponent inc : compose.getExclude()) 129 excludeCodes(inc, params); 130 131 } 132 133 private void importValueSet(String value, List<ValueSetExpansionParameterComponent> params) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException { 134 if (value == null) 135 throw new TerminologyServiceException("unable to find value set with no identity"); 136 ValueSet vs = context.fetchResource(ValueSet.class, value); 137 if (vs == null) 138 throw new TerminologyServiceException("Unable to find imported value set "+value); 139 ValueSetExpansionOutcome vso = factory.getExpander().expand(vs); 140 if (vso.getService() != null) 141 throw new TerminologyServiceException("Unable to expand imported value set "+value); 142 if (vs.hasVersion()) 143 if (!existsInParams(params, "version", new UriType(vs.getUrl()+"?version="+vs.getVersion()))) 144 params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion()))); 145 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 146 if (!existsInParams(params, p.getName(), p.getValue())) 147 params.add(p); 148 } 149 150 for (ValueSetExpansionContainsComponent c : vso.getValueset().getExpansion().getContains()) { 151 addCode(c.getSystem(), c.getCode(), c.getDisplay()); 152 } 153 } 154 155 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) { 156 for (ValueSetExpansionParameterComponent p : params) { 157 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) 158 return true; 159 } 160 return false; 161 } 162 163 private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException, ETooCostly { 164 if (context.supportsSystem(inc.getSystem())) { 165 addCodes(context.expandVS(inc), params); 166 return; 167 } 168 169 ValueSet cs = context.fetchCodeSystem(inc.getSystem()); 170 if (cs == null) 171 throw new TerminologyServiceException("unable to find code system "+inc.getSystem().toString()); 172 if (cs.hasVersion()) 173 if (!existsInParams(params, "version", new UriType(cs.getUrl()+"?version="+cs.getVersion()))) 174 params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl()+"?version="+cs.getVersion()))); 175 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 176 // special case - add all the code system 177 for (ConceptDefinitionComponent def : cs.getCodeSystem().getConcept()) { 178 addCodeAndDescendents(inc.getSystem(), def); 179 } 180 } 181 182 for (ConceptReferenceComponent c : inc.getConcept()) { 183 addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay()); 184 } 185 if (inc.getFilter().size() > 1) 186 throw new TerminologyServiceException("Multiple filters not handled yet"); // need to and them, and this isn't done yet. But this shouldn't arise in non loinc and snomed value sets 187 if (inc.getFilter().size() == 1) { 188 ConceptSetFilterComponent fc = inc.getFilter().get(0); 189 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 190 // special: all non-abstract codes in the target code system under the value 191 ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), fc.getValue()); 192 if (def == null) 193 throw new TerminologyServiceException("Code '"+fc.getValue()+"' not found in system '"+inc.getSystem()+"'"); 194 addCodeAndDescendents(inc.getSystem(), def); 195 } else 196 throw new NotImplementedException("not done yet"); 197 } 198 } 199 200 private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) throws ETooCostly { 201 if (expand.getContains().size() > 500) 202 throw new ETooCostly("Too many codes to display (>"+Integer.toString(expand.getContains().size())+")"); 203 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 204 if (!existsInParams(params, p.getName(), p.getValue())) 205 params.add(p); 206 } 207 208 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 209 addCode(c.getSystem(), c.getCode(), c.getDisplay()); 210 } 211 } 212 213 private void addCodeAndDescendents(String system, ConceptDefinitionComponent def) { 214 if (!ToolingExtensions.hasDeprecated(def)) { 215 if (!def.hasAbstractElement() || !def.getAbstract()) 216 addCode(system, def.getCode(), def.getDisplay()); 217 for (ConceptDefinitionComponent c : def.getConcept()) 218 addCodeAndDescendents(system, c); 219 } 220 } 221 222 private void excludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException { 223 ValueSet cs = context.fetchCodeSystem(inc.getSystem().toString()); 224 if (cs == null) 225 throw new TerminologyServiceException("unable to find value set "+inc.getSystem().toString()); 226 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 227 // special case - add all the code system 228// for (ConceptDefinitionComponent def : cs.getDefine().getConcept()) { 229//!!!! addCodeAndDescendents(inc.getSystem(), def); 230// } 231 } 232 233 234 for (ConceptReferenceComponent c : inc.getConcept()) { 235 // we don't need to check whether the codes are valid here- they can't have gotten into this list if they aren't valid 236 map.remove(key(inc.getSystem(), c.getCode())); 237 } 238 if (inc.getFilter().size() > 0) 239 throw new NotImplementedException("not done yet"); 240 } 241 242 243 private String getCodeDisplay(ValueSet cs, String code) throws TerminologyServiceException { 244 ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), code); 245 if (def == null) 246 throw new TerminologyServiceException("Unable to find code '"+code+"' in code system "+cs.getCodeSystem().getSystem()); 247 return def.getDisplay(); 248 } 249 250 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 251 for (ConceptDefinitionComponent c : clist) { 252 if (code.equals(c.getCode())) 253 return c; 254 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 255 if (v != null) 256 return v; 257 } 258 return null; 259 } 260 261 private void handleDefine(ValueSet vs, List<ValueSetExpansionParameterComponent> list) { 262 if (vs.hasVersion()) 263 list.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion()))); 264 if (vs.hasCodeSystem()) { 265 // simple case: just generate the return 266 for (ConceptDefinitionComponent c : vs.getCodeSystem().getConcept()) 267 addDefinedCode(vs, vs.getCodeSystem().getSystem(), c); 268 } 269 } 270 271 private String key(ValueSetExpansionContainsComponent c) { 272 return key(c.getSystem(), c.getCode()); 273 } 274 275 private String key(String uri, String code) { 276 return "{"+uri+"}"+code; 277 } 278 279 private void addDefinedCode(ValueSet vs, String system, ConceptDefinitionComponent c) { 280 if (!ToolingExtensions.hasDeprecated(c)) { 281 282 if (!c.hasAbstractElement() || !c.getAbstract()) { 283 addCode(system, c.getCode(), c.getDisplay()); 284 } 285 for (ConceptDefinitionComponent g : c.getConcept()) 286 addDefinedCode(vs, vs.getCodeSystem().getSystem(), g); 287 } 288 } 289 290 private void addCode(String system, String code, String display) { 291 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 292 n.setSystem(system); 293 n.setCode(code); 294 n.setDisplay(display); 295 String s = key(n); 296 if (!map.containsKey(s)) { 297 codes.add(n); 298 map.put(s, n); 299 } 300 } 301 302 303}