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}