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