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 090public class ValueSetExpanderSimple implements ValueSetExpander { 091 092 private IWorkerContext context; 093 private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); 094 private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>(); 095 private ValueSet focus; 096 097 private ValueSetExpanderFactory factory; 098 099 public ValueSetExpanderSimple(IWorkerContext context, ValueSetExpanderFactory factory) { 100 super(); 101 this.context = context; 102 this.factory = factory; 103 } 104 105 @Override 106 public ValueSetExpansionOutcome expand(ValueSet source) { 107 108 try { 109 focus = source.copy(); 110 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 111 focus.getExpansion().setTimestampElement(DateTimeType.now()); 112 focus.getExpansion().setIdentifier(Factory.createUUID()); 113 114 handleDefine(source, focus.getExpansion().getParameter()); 115 if (source.hasCompose()) 116 handleCompose(source.getCompose(), focus.getExpansion().getParameter()); 117 118 for (ValueSetExpansionContainsComponent c : codes) { 119 if (map.containsKey(key(c))) { 120 focus.getExpansion().getContains().add(c); 121 } 122 } 123 return new ValueSetExpansionOutcome(focus, null); 124 } catch (Exception e) { 125 // well, we couldn't expand, so we'll return an interface to a checker that can 126 // check membership of the set 127 // that might fail too, but it might not, later. 128 return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage()); 129 } 130 } 131 132 private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params) 133 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) 144 throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException { 145 if (value == null) 146 throw new TerminologyServiceException("unable to find value set with no identity"); 147 ValueSet vs = context.fetchResource(ValueSet.class, value); 148 if (vs == null) 149 throw new TerminologyServiceException("Unable to find imported value set " + value); 150 ValueSetExpansionOutcome vso = factory.getExpander().expand(vs); 151 if (vso.getService() != null) 152 throw new TerminologyServiceException("Unable to expand imported value set " + value); 153 if (vs.hasVersion()) 154 if (!existsInParams(params, "version", new UriType(vs.getUrl() + "?version=" + vs.getVersion()))) 155 params.add(new ValueSetExpansionParameterComponent().setName("version") 156 .setValue(new UriType(vs.getUrl() + "?version=" + vs.getVersion()))); 157 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 158 if (!existsInParams(params, p.getName(), p.getValue())) 159 params.add(p); 160 } 161 162 for (ValueSetExpansionContainsComponent c : vso.getValueset().getExpansion().getContains()) { 163 addCode(c.getSystem(), c.getCode(), c.getDisplay()); 164 } 165 } 166 167 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) { 168 for (ValueSetExpansionParameterComponent p : params) { 169 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) 170 return true; 171 } 172 return false; 173 } 174 175 private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) 176 throws TerminologyServiceException, ETooCostly { 177 if (context.supportsSystem(inc.getSystem())) { 178 addCodes(context.expandVS(inc), params); 179 return; 180 } 181 182 ValueSet cs = context.fetchCodeSystem(inc.getSystem()); 183 if (cs == null) 184 throw new TerminologyServiceException("unable to find code system " + inc.getSystem().toString()); 185 if (cs.hasVersion()) 186 if (!existsInParams(params, "version", new UriType(cs.getUrl() + "?version=" + cs.getVersion()))) 187 params.add(new ValueSetExpansionParameterComponent().setName("version") 188 .setValue(new UriType(cs.getUrl() + "?version=" + cs.getVersion()))); 189 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 190 // special case - add all the code system 191 for (ConceptDefinitionComponent def : cs.getCodeSystem().getConcept()) { 192 addCodeAndDescendents(inc.getSystem(), def); 193 } 194 } 195 196 for (ConceptReferenceComponent c : inc.getConcept()) { 197 addCode(inc.getSystem(), c.getCode(), 198 Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay()); 199 } 200 if (inc.getFilter().size() > 1) 201 throw new TerminologyServiceException("Multiple filters not handled yet"); // need to and them, and this isn't 202 // done yet. But this shouldn't arise 203 // in non loinc and snomed value sets 204 if (inc.getFilter().size() == 1) { 205 ConceptSetFilterComponent fc = inc.getFilter().get(0); 206 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 207 // special: all non-abstract codes in the target code system under the value 208 ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), fc.getValue()); 209 if (def == null) 210 throw new TerminologyServiceException( 211 "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 212 addCodeAndDescendents(inc.getSystem(), def); 213 } else 214 throw new NotImplementedException("not done yet"); 215 } 216 } 217 218 private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) 219 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(String system, ConceptDefinitionComponent def) { 233 if (!ToolingExtensions.hasDeprecated(def)) { 234 if (!def.hasAbstractElement() || !def.getAbstract()) 235 addCode(system, def.getCode(), def.getDisplay()); 236 for (ConceptDefinitionComponent c : def.getConcept()) 237 addCodeAndDescendents(system, c); 238 } 239 } 240 241 private void excludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) 242 throws TerminologyServiceException { 243 ValueSet cs = context.fetchCodeSystem(inc.getSystem().toString()); 244 if (cs == null) 245 throw new TerminologyServiceException("unable to find value set " + inc.getSystem().toString()); 246 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 247 // special case - add all the code system 248// for (ConceptDefinitionComponent def : cs.getDefine().getConcept()) { 249//!!!! addCodeAndDescendents(inc.getSystem(), def); 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 255 // gotten into this list if they aren't valid 256 map.remove(key(inc.getSystem(), c.getCode())); 257 } 258 if (inc.getFilter().size() > 0) 259 throw new NotImplementedException("not done yet"); 260 } 261 262 private String getCodeDisplay(ValueSet cs, String code) throws TerminologyServiceException { 263 ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), code); 264 if (def == null) 265 throw new TerminologyServiceException( 266 "Unable to find code '" + code + "' in code system " + cs.getCodeSystem().getSystem()); 267 return def.getDisplay(); 268 } 269 270 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 271 for (ConceptDefinitionComponent c : clist) { 272 if (code.equals(c.getCode())) 273 return c; 274 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 275 if (v != null) 276 return v; 277 } 278 return null; 279 } 280 281 private void handleDefine(ValueSet vs, List<ValueSetExpansionParameterComponent> list) { 282 if (vs.hasVersion()) 283 list.add(new ValueSetExpansionParameterComponent().setName("version") 284 .setValue(new UriType(vs.getUrl() + "?version=" + vs.getVersion()))); 285 if (vs.hasCodeSystem()) { 286 // simple case: just generate the return 287 for (ConceptDefinitionComponent c : vs.getCodeSystem().getConcept()) 288 addDefinedCode(vs, vs.getCodeSystem().getSystem(), c); 289 } 290 } 291 292 private String key(ValueSetExpansionContainsComponent c) { 293 return key(c.getSystem(), c.getCode()); 294 } 295 296 private String key(String uri, String code) { 297 return "{" + uri + "}" + code; 298 } 299 300 private void addDefinedCode(ValueSet vs, String system, ConceptDefinitionComponent c) { 301 if (!ToolingExtensions.hasDeprecated(c)) { 302 303 if (!c.hasAbstractElement() || !c.getAbstract()) { 304 addCode(system, c.getCode(), c.getDisplay()); 305 } 306 for (ConceptDefinitionComponent g : c.getConcept()) 307 addDefinedCode(vs, vs.getCodeSystem().getSystem(), g); 308 } 309 } 310 311 private void addCode(String system, String code, String display) { 312 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 313 n.setSystem(system); 314 n.setCode(code); 315 n.setDisplay(display); 316 String s = key(n); 317 if (!map.containsKey(s)) { 318 codes.add(n); 319 map.put(s, n); 320 } 321 } 322 323}