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