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