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