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 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.dstu2.model.DateTimeType; 073import org.hl7.fhir.dstu2.model.Factory; 074import org.hl7.fhir.dstu2.model.PrimitiveType; 075import org.hl7.fhir.dstu2.model.Type; 076import org.hl7.fhir.dstu2.model.UriType; 077import org.hl7.fhir.dstu2.model.ValueSet; 078import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionComponent; 079import org.hl7.fhir.dstu2.model.ValueSet.ConceptReferenceComponent; 080import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; 081import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetFilterComponent; 082import org.hl7.fhir.dstu2.model.ValueSet.FilterOperator; 083import org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent; 084import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; 085import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent; 086import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionParameterComponent; 087import org.hl7.fhir.dstu2.utils.IWorkerContext; 088import org.hl7.fhir.dstu2.utils.ToolingExtensions; 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 handleDefine(source, focus.getExpansion().getParameter()); 117 if (source.hasCompose()) 118 handleCompose(source.getCompose(), focus.getExpansion().getParameter()); 119 120 for (ValueSetExpansionContainsComponent c : codes) { 121 if (map.containsKey(key(c))) { 122 focus.getExpansion().getContains().add(c); 123 } 124 } 125 return new ValueSetExpansionOutcome(focus, null); 126 } catch (Exception e) { 127 // well, we couldn't expand, so we'll return an interface to a checker that can 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) throws TerminologyServiceException, ETooCostly, FileNotFoundException, IOException { 134 for (UriType imp : compose.getImport()) 135 importValueSet(imp.getValue(), params); 136 for (ConceptSetComponent inc : compose.getInclude()) 137 includeCodes(inc, params); 138 for (ConceptSetComponent inc : compose.getExclude()) 139 excludeCodes(inc, params); 140 141 } 142 143 private void importValueSet(String value, List<ValueSetExpansionParameterComponent> params) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException { 144 if (value == null) 145 throw new TerminologyServiceException("unable to find value set with no identity"); 146 ValueSet vs = context.fetchResource(ValueSet.class, value); 147 if (vs == null) 148 throw new TerminologyServiceException("Unable to find imported value set "+value); 149 ValueSetExpansionOutcome vso = factory.getExpander().expand(vs); 150 if (vso.getService() != null) 151 throw new TerminologyServiceException("Unable to expand imported value set "+value); 152 if (vs.hasVersion()) 153 if (!existsInParams(params, "version", new UriType(vs.getUrl()+"?version="+vs.getVersion()))) 154 params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion()))); 155 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 156 if (!existsInParams(params, p.getName(), p.getValue())) 157 params.add(p); 158 } 159 160 for (ValueSetExpansionContainsComponent c : vso.getValueset().getExpansion().getContains()) { 161 addCode(c.getSystem(), c.getCode(), c.getDisplay()); 162 } 163 } 164 165 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) { 166 for (ValueSetExpansionParameterComponent p : params) { 167 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) 168 return true; 169 } 170 return false; 171 } 172 173 private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException, ETooCostly { 174 if (context.supportsSystem(inc.getSystem())) { 175 addCodes(context.expandVS(inc), params); 176 return; 177 } 178 179 ValueSet cs = context.fetchCodeSystem(inc.getSystem()); 180 if (cs == null) 181 throw new TerminologyServiceException("unable to find code system "+inc.getSystem().toString()); 182 if (cs.hasVersion()) 183 if (!existsInParams(params, "version", new UriType(cs.getUrl()+"?version="+cs.getVersion()))) 184 params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl()+"?version="+cs.getVersion()))); 185 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 186 // special case - add all the code system 187 for (ConceptDefinitionComponent def : cs.getCodeSystem().getConcept()) { 188 addCodeAndDescendents(inc.getSystem(), def); 189 } 190 } 191 192 for (ConceptReferenceComponent c : inc.getConcept()) { 193 addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay()); 194 } 195 if (inc.getFilter().size() > 1) 196 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 197 if (inc.getFilter().size() == 1) { 198 ConceptSetFilterComponent fc = inc.getFilter().get(0); 199 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 200 // special: all non-abstract codes in the target code system under the value 201 ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), fc.getValue()); 202 if (def == null) 203 throw new TerminologyServiceException("Code '"+fc.getValue()+"' not found in system '"+inc.getSystem()+"'"); 204 addCodeAndDescendents(inc.getSystem(), def); 205 } else 206 throw new NotImplementedException("not done yet"); 207 } 208 } 209 210 private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) throws ETooCostly { 211 if (expand.getContains().size() > 500) 212 throw new ETooCostly("Too many codes to display (>"+Integer.toString(expand.getContains().size())+")"); 213 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 214 if (!existsInParams(params, p.getName(), p.getValue())) 215 params.add(p); 216 } 217 218 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 219 addCode(c.getSystem(), c.getCode(), c.getDisplay()); 220 } 221 } 222 223 private void addCodeAndDescendents(String system, ConceptDefinitionComponent def) { 224 if (!ToolingExtensions.hasDeprecated(def)) { 225 if (!def.hasAbstractElement() || !def.getAbstract()) 226 addCode(system, def.getCode(), def.getDisplay()); 227 for (ConceptDefinitionComponent c : def.getConcept()) 228 addCodeAndDescendents(system, c); 229 } 230 } 231 232 private void excludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException { 233 ValueSet cs = context.fetchCodeSystem(inc.getSystem().toString()); 234 if (cs == null) 235 throw new TerminologyServiceException("unable to find value set "+inc.getSystem().toString()); 236 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 237 // special case - add all the code system 238// for (ConceptDefinitionComponent def : cs.getDefine().getConcept()) { 239//!!!! addCodeAndDescendents(inc.getSystem(), def); 240// } 241 } 242 243 244 for (ConceptReferenceComponent c : inc.getConcept()) { 245 // 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 246 map.remove(key(inc.getSystem(), c.getCode())); 247 } 248 if (inc.getFilter().size() > 0) 249 throw new NotImplementedException("not done yet"); 250 } 251 252 253 private String getCodeDisplay(ValueSet cs, String code) throws TerminologyServiceException { 254 ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), code); 255 if (def == null) 256 throw new TerminologyServiceException("Unable to find code '"+code+"' in code system "+cs.getCodeSystem().getSystem()); 257 return def.getDisplay(); 258 } 259 260 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 261 for (ConceptDefinitionComponent c : clist) { 262 if (code.equals(c.getCode())) 263 return c; 264 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 265 if (v != null) 266 return v; 267 } 268 return null; 269 } 270 271 private void handleDefine(ValueSet vs, List<ValueSetExpansionParameterComponent> list) { 272 if (vs.hasVersion()) 273 list.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion()))); 274 if (vs.hasCodeSystem()) { 275 // simple case: just generate the return 276 for (ConceptDefinitionComponent c : vs.getCodeSystem().getConcept()) 277 addDefinedCode(vs, vs.getCodeSystem().getSystem(), c); 278 } 279 } 280 281 private String key(ValueSetExpansionContainsComponent c) { 282 return key(c.getSystem(), c.getCode()); 283 } 284 285 private String key(String uri, String code) { 286 return "{"+uri+"}"+code; 287 } 288 289 private void addDefinedCode(ValueSet vs, String system, ConceptDefinitionComponent c) { 290 if (!ToolingExtensions.hasDeprecated(c)) { 291 292 if (!c.hasAbstractElement() || !c.getAbstract()) { 293 addCode(system, c.getCode(), c.getDisplay()); 294 } 295 for (ConceptDefinitionComponent g : c.getConcept()) 296 addDefinedCode(vs, vs.getCodeSystem().getSystem(), g); 297 } 298 } 299 300 private void addCode(String system, String code, String display) { 301 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 302 n.setSystem(system); 303 n.setCode(code); 304 n.setDisplay(display); 305 String s = key(n); 306 if (!map.containsKey(s)) { 307 codes.add(n); 308 map.put(s, n); 309 } 310 } 311 312 313}