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}