001package org.hl7.fhir.r4.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 static org.apache.commons.lang3.StringUtils.isNotBlank;
033
034import java.io.FileNotFoundException;
035import java.io.IOException;
036
037/*
038 * Copyright (c) 2011+, HL7, Inc
039 * All rights reserved.
040 * 
041 * Redistribution and use in source and binary forms, with or without modification,
042 * are 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 * 
053 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
054 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
055 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
056 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
057 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
058 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
059 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
060 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
061 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
062 * POSSIBILITY OF SUCH DAMAGE.
063 * 
064 */
065
066import java.util.ArrayList;
067import java.util.HashMap;
068import java.util.HashSet;
069import java.util.List;
070import java.util.Map;
071import java.util.Set;
072
073import org.apache.commons.lang3.NotImplementedException;
074import org.hl7.fhir.exceptions.FHIRException;
075import org.hl7.fhir.exceptions.FHIRFormatError;
076import org.hl7.fhir.exceptions.NoTerminologyServiceException;
077import org.hl7.fhir.exceptions.TerminologyServiceException;
078import org.hl7.fhir.r4.context.IWorkerContext;
079import org.hl7.fhir.r4.model.CodeSystem;
080import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
081import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
082import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionDesignationComponent;
083import org.hl7.fhir.r4.model.DateTimeType;
084import org.hl7.fhir.r4.model.Factory;
085import org.hl7.fhir.r4.model.Parameters;
086import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
087import org.hl7.fhir.r4.model.PrimitiveType;
088import org.hl7.fhir.r4.model.Type;
089import org.hl7.fhir.r4.model.UriType;
090import org.hl7.fhir.r4.model.ValueSet;
091import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent;
092import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceDesignationComponent;
093import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
094import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent;
095import org.hl7.fhir.r4.model.ValueSet.FilterOperator;
096import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent;
097import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
098import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
099import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionParameterComponent;
100import org.hl7.fhir.r4.utils.ToolingExtensions;
101import org.hl7.fhir.utilities.Utilities;
102
103public class ValueSetExpanderSimple implements ValueSetExpander {
104
105  private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
106  private List<ValueSetExpansionContainsComponent> roots = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
107  private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
108  private IWorkerContext context;
109  private boolean canBeHeirarchy = true;
110  private Set<String> excludeKeys = new HashSet<String>();
111  private Set<String> excludeSystems = new HashSet<String>();
112  private ValueSet focus;
113  private int maxExpansionSize = 500;
114
115  private int total;
116
117  public ValueSetExpanderSimple(IWorkerContext context) {
118    super();
119    this.context = context;
120  }
121
122  public void setMaxExpansionSize(int theMaxExpansionSize) {
123    maxExpansionSize = theMaxExpansionSize;
124  }
125
126  private ValueSetExpansionContainsComponent addCode(String system, String code, String display,
127      ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations,
128      Parameters expParams, boolean isAbstract, boolean inactive, List<ValueSet> filters) {
129
130    if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code))
131      return null;
132    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
133    n.setSystem(system);
134    n.setCode(code);
135    if (isAbstract)
136      n.setAbstract(true);
137    if (inactive)
138      n.setInactive(true);
139
140    if (expParams.getParameterBool("includeDesignations") && designations != null) {
141      for (ConceptDefinitionDesignationComponent t : designations) {
142        ToolingExtensions.addLanguageTranslation(n, t.getLanguage(), t.getValue());
143      }
144    }
145    ConceptDefinitionDesignationComponent t = expParams.hasLanguage()
146        ? getMatchingLang(designations, expParams.getLanguage())
147        : null;
148    if (t == null)
149      n.setDisplay(display);
150    else
151      n.setDisplay(t.getValue());
152
153    String s = key(n);
154    if (map.containsKey(s) || excludeKeys.contains(s)) {
155      canBeHeirarchy = false;
156    } else {
157      codes.add(n);
158      map.put(s, n);
159      total++;
160    }
161    if (canBeHeirarchy && parent != null) {
162      parent.getContains().add(n);
163    } else {
164      roots.add(n);
165    }
166    return n;
167  }
168
169  private boolean filterContainsCode(List<ValueSet> filters, String system, String code) {
170    for (ValueSet vse : filters)
171      if (expansionContainsCode(vse.getExpansion().getContains(), system, code))
172        return true;
173    return false;
174  }
175
176  private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) {
177    for (ValueSetExpansionContainsComponent cc : contains) {
178      if (system.equals(cc.getSystem()) && code.equals(cc.getCode()))
179        return true;
180      if (expansionContainsCode(cc.getContains(), system, code))
181        return true;
182    }
183    return false;
184  }
185
186  private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list,
187      String lang) {
188    for (ConceptDefinitionDesignationComponent t : list)
189      if (t.getLanguage().equals(lang))
190        return t;
191    for (ConceptDefinitionDesignationComponent t : list)
192      if (t.getLanguage().startsWith(lang))
193        return t;
194    return null;
195  }
196
197  private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus,
198      ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters) throws FHIRException {
199    focus.checkNoModifiers("Expansion.contains", "expanding");
200    ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), parent,
201        convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters);
202    for (ValueSetExpansionContainsComponent c : focus.getContains())
203      addCodeAndDescendents(focus, np, expParams, filters);
204  }
205
206  private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) {
207    List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>();
208    for (ConceptReferenceDesignationComponent d : designations) {
209      ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent();
210      n.setLanguage(d.getLanguage());
211      n.setUse(d.getUse());
212      n.setValue(d.getValue());
213      list.add(n);
214    }
215    return list;
216  }
217
218  private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def,
219      ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters,
220      ConceptDefinitionComponent exclusion) throws FHIRException {
221    def.checkNoModifiers("Code in Code System", "expanding");
222    if (exclusion != null) {
223      if (exclusion.getCode().equals(def.getCode()))
224        return; // excluded.
225    }
226    if (!CodeSystemUtilities.isDeprecated(cs, def)) {
227      ValueSetExpansionContainsComponent np = null;
228      boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
229      boolean inc = CodeSystemUtilities.isInactive(cs, def);
230      if (canBeHeirarchy || !abs)
231        np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc,
232            filters);
233      for (ConceptDefinitionComponent c : def.getConcept())
234        addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion);
235    } else {
236      for (ConceptDefinitionComponent c : def.getConcept())
237        addCodeAndDescendents(cs, system, c, null, expParams, filters, exclusion);
238    }
239
240  }
241
242  private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params,
243      Parameters expParams, List<ValueSet> filters) throws ETooCostly, FHIRException {
244    if (expand != null) {
245      if (expand.getContains().size() > maxExpansionSize)
246        throw new ETooCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")");
247      for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
248        if (!existsInParams(params, p.getName(), p.getValue()))
249          params.add(p);
250      }
251
252      copyImportContains(expand.getContains(), null, expParams, filters);
253    }
254  }
255
256  private void excludeCode(String theSystem, String theCode) {
257    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
258    n.setSystem(theSystem);
259    n.setCode(theCode);
260    String s = key(n);
261    excludeKeys.add(s);
262  }
263
264  private void excludeCodes(ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params, String ctxt)
265      throws FHIRException {
266    exc.checkNoModifiers("Compose.exclude", "expanding");
267    if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) {
268      excludeSystems.add(exc.getSystem());
269    }
270
271    if (exc.hasValueSet())
272      throw new Error("Processing Value set references in exclude is not yet done in " + ctxt);
273    // importValueSet(imp.getValue(), params, expParams);
274
275    CodeSystem cs = context.fetchCodeSystem(exc.getSystem());
276    if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) {
277      ValueSetExpansionOutcome vse = context.expandVS(exc, false);
278      ValueSet valueset = vse.getValueset();
279      if (valueset == null)
280        throw new TerminologyServiceException("Error Expanding ValueSet: " + vse.getError());
281      excludeCodes(valueset.getExpansion(), params);
282      return;
283    }
284
285    for (ConceptReferenceComponent c : exc.getConcept()) {
286      excludeCode(exc.getSystem(), c.getCode());
287    }
288
289    if (exc.getFilter().size() > 0)
290      throw new NotImplementedException("not done yet");
291  }
292
293  private void excludeCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) {
294    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
295      excludeCode(c.getSystem(), c.getCode());
296    }
297  }
298
299  private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) {
300    for (ValueSetExpansionParameterComponent p : params) {
301      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false))
302        return true;
303    }
304    return false;
305  }
306
307  @Override
308  public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) {
309    try {
310      return doExpand(source, expParams);
311    } catch (NoTerminologyServiceException e) {
312      // well, we couldn't expand, so we'll return an interface to a checker that can
313      // check membership of the set
314      // that might fail too, but it might not, later.
315      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE);
316    } catch (RuntimeException e) {
317      // TODO: we should put something more specific instead of just Exception below,
318      // since
319      // it swallows bugs.. what would be expected to be caught there?
320      throw e;
321    } catch (Exception e) {
322      // well, we couldn't expand, so we'll return an interface to a checker that can
323      // check membership of the set
324      // that might fail too, but it might not, later.
325      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
326    }
327  }
328
329  public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams)
330      throws FHIRException, ETooCostly, FileNotFoundException, IOException {
331    if (expParams == null)
332      expParams = makeDefaultExpansion();
333    source.checkNoModifiers("ValueSet", "expanding");
334    focus = source.copy();
335    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
336    focus.getExpansion().setTimestampElement(DateTimeType.now());
337    focus.getExpansion().setIdentifier(Factory.createUUID());
338    for (ParametersParameterComponent p : expParams.getParameter()) {
339      if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested"))
340        focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
341    }
342
343    if (source.hasCompose())
344      handleCompose(source.getCompose(), focus.getExpansion().getParameter(), expParams, source.getUrl());
345
346    if (canBeHeirarchy) {
347      for (ValueSetExpansionContainsComponent c : roots) {
348        focus.getExpansion().getContains().add(c);
349      }
350    } else {
351      for (ValueSetExpansionContainsComponent c : codes) {
352        if (map.containsKey(key(c)) && !c.getAbstract()) { // we may have added abstract codes earlier while we still
353                                                           // thought it might be heirarchical, but later we gave up, so
354                                                           // now ignore them
355          focus.getExpansion().getContains().add(c);
356          c.getContains().clear(); // make sure any heirarchy is wiped
357        }
358      }
359    }
360
361    if (total > 0) {
362      focus.getExpansion().setTotal(total);
363    }
364
365    return new ValueSetExpansionOutcome(focus);
366  }
367
368  private Parameters makeDefaultExpansion() {
369    Parameters res = new Parameters();
370    res.addParameter("excludeNested", true);
371    res.addParameter("includeDesignations", false);
372    return res;
373  }
374
375  private void addToHeirarchy(List<ValueSetExpansionContainsComponent> target,
376      List<ValueSetExpansionContainsComponent> source) {
377    for (ValueSetExpansionContainsComponent s : source) {
378      target.add(s);
379    }
380  }
381
382  private String getCodeDisplay(CodeSystem cs, String code) throws TerminologyServiceException {
383    ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), code);
384    if (def == null)
385      throw new TerminologyServiceException("Unable to find code '" + code + "' in code system " + cs.getUrl());
386    return def.getDisplay();
387  }
388
389  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
390    for (ConceptDefinitionComponent c : clist) {
391      if (code.equals(c.getCode()))
392        return c;
393      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
394      if (v != null)
395        return v;
396    }
397    return null;
398  }
399
400  private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params,
401      Parameters expParams, String ctxt) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
402    compose.checkNoModifiers("ValueSet.compose", "expanding");
403    // Exclude comes first because we build up a map of things to exclude
404    for (ConceptSetComponent inc : compose.getExclude())
405      excludeCodes(inc, params, ctxt);
406    canBeHeirarchy = !expParams.getParameterBool("excludeNested") && excludeKeys.isEmpty() && excludeSystems.isEmpty();
407    boolean first = true;
408    for (ConceptSetComponent inc : compose.getInclude()) {
409      if (first == true)
410        first = false;
411      else
412        canBeHeirarchy = false;
413      includeCodes(inc, params, expParams, canBeHeirarchy);
414    }
415
416  }
417
418  private ValueSet importValueSet(String value, List<ValueSetExpansionParameterComponent> params, Parameters expParams)
419      throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
420    if (value == null)
421      throw new TerminologyServiceException("unable to find value set with no identity");
422    ValueSet vs = context.fetchResource(ValueSet.class, value);
423    if (vs == null)
424      throw new TerminologyServiceException("Unable to find imported value set " + value);
425    ValueSetExpansionOutcome vso = new ValueSetExpanderSimple(context).expand(vs, expParams);
426    if (vso.getError() != null)
427      throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError());
428    if (vs.hasVersion())
429      if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion())))
430        params.add(new ValueSetExpansionParameterComponent().setName("version")
431            .setValue(new UriType(vs.getUrl() + "|" + vs.getVersion())));
432    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
433      if (!existsInParams(params, p.getName(), p.getValue()))
434        params.add(p);
435    }
436    canBeHeirarchy = false; // if we're importing a value set, we have to be combining, so we won't try for
437                            // a heirarchy
438    return vso.getValueset();
439  }
440
441  private void copyImportContains(List<ValueSetExpansionContainsComponent> list,
442      ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter) throws FHIRException {
443    for (ValueSetExpansionContainsComponent c : list) {
444      c.checkNoModifiers("Imported Expansion in Code System", "expanding");
445      ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null,
446          expParams, c.getAbstract(), c.getInactive(), filter);
447      copyImportContains(c.getContains(), np, expParams, filter);
448    }
449  }
450
451  private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params,
452      Parameters expParams, boolean heirarchical) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
453    inc.checkNoModifiers("Compose.include", "expanding");
454    List<ValueSet> imports = new ArrayList<ValueSet>();
455    for (UriType imp : inc.getValueSet()) {
456      imports.add(importValueSet(imp.getValue(), params, expParams));
457    }
458
459    if (!inc.hasSystem()) {
460      if (imports.isEmpty()) // though this is not supposed to be the case
461        return;
462      ValueSet base = imports.get(0);
463      imports.remove(0);
464      base.checkNoModifiers("Imported ValueSet", "expanding");
465      copyImportContains(base.getExpansion().getContains(), null, expParams, imports);
466    } else {
467      CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
468      if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE)) {
469        doServerIncludeCodes(inc, heirarchical, params, imports, expParams);
470      } else {
471        doInternalIncludeCodes(inc, params, expParams, imports, cs);
472      }
473    }
474  }
475
476  private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical,
477      List<ValueSetExpansionParameterComponent> params, List<ValueSet> imports, Parameters expParams)
478      throws FHIRException {
479    ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical);
480    if (vso.getError() != null)
481      throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError());
482    ValueSet vs = vso.getValueset();
483    if (vs.hasVersion())
484      if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion())))
485        params.add(new ValueSetExpansionParameterComponent().setName("version")
486            .setValue(new UriType(vs.getUrl() + "|" + vs.getVersion())));
487    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
488      if (!existsInParams(params, p.getName(), p.getValue()))
489        params.add(p);
490    }
491    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
492      addCodeAndDescendents(cc, null, expParams, imports);
493    }
494  }
495
496  public void doInternalIncludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params,
497      Parameters expParams, List<ValueSet> imports, CodeSystem cs)
498      throws NoTerminologyServiceException, TerminologyServiceException, FHIRException {
499    if (cs == null) {
500      if (context.isNoTerminologyServer())
501        throw new NoTerminologyServiceException("unable to find code system " + inc.getSystem().toString());
502      else
503        throw new TerminologyServiceException("unable to find code system " + inc.getSystem().toString());
504    }
505    cs.checkNoModifiers("Code System", "expanding");
506    if (cs.getContent() != CodeSystemContentMode.COMPLETE)
507      throw new TerminologyServiceException("Code system " + inc.getSystem().toString() + " is incomplete");
508    if (cs.hasVersion())
509      if (!existsInParams(params, "version", new UriType(cs.getUrl() + "|" + cs.getVersion())))
510        params.add(new ValueSetExpansionParameterComponent().setName("version")
511            .setValue(new UriType(cs.getUrl() + "|" + cs.getVersion())));
512
513    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
514      // special case - add all the code system
515      for (ConceptDefinitionComponent def : cs.getConcept()) {
516        addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null);
517      }
518    }
519
520    if (!inc.getConcept().isEmpty()) {
521      canBeHeirarchy = false;
522      for (ConceptReferenceComponent c : inc.getConcept()) {
523        c.checkNoModifiers("Code in Code System", "expanding");
524        addCode(inc.getSystem(), c.getCode(),
525            Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay(), null,
526            convertDesignations(c.getDesignation()), expParams, false, CodeSystemUtilities.isInactive(cs, c.getCode()),
527            imports);
528      }
529    }
530    if (inc.getFilter().size() > 1) {
531      canBeHeirarchy = false; // which will bt the case if we get around to supporting this
532      throw new TerminologyServiceException("Multiple filters not handled yet"); // need to and them, and this isn't
533                                                                                 // done yet. But this shouldn't arise
534                                                                                 // in non loinc and snomed value sets
535    }
536    if (inc.getFilter().size() == 1) {
537      ConceptSetFilterComponent fc = inc.getFilter().get(0);
538      if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
539        // special: all codes in the target code system under the value
540        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
541        if (def == null)
542          throw new TerminologyServiceException(
543              "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
544        addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null);
545      } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) {
546        // special: all codes in the target code system that are not under the value
547        ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue());
548        if (defEx == null)
549          throw new TerminologyServiceException(
550              "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
551        for (ConceptDefinitionComponent def : cs.getConcept()) {
552          addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, defEx);
553        }
554      } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
555        // special: all codes in the target code system under the value
556        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
557        if (def == null)
558          throw new TerminologyServiceException(
559              "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
560        for (ConceptDefinitionComponent c : def.getConcept())
561          addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null);
562      } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
563        // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and
564        // see if it's diplsay is 'v'?
565        canBeHeirarchy = false;
566        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
567        if (def != null) {
568          if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
569            if (def.getDisplay().contains(fc.getValue())) {
570              addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), expParams,
571                  CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), imports);
572            }
573          }
574        }
575      } else
576        throw new NotImplementedException(
577            "Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet");
578    }
579  }
580
581  private List<ConceptDefinitionDesignationComponent> convertDesignations(
582      List<ConceptReferenceDesignationComponent> list) {
583    List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>();
584    for (ConceptReferenceDesignationComponent t : list) {
585      ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent();
586      c.setLanguage(t.getLanguage());
587      c.setUse(t.getUse());
588      c.setValue(t.getValue());
589    }
590    return res;
591  }
592
593  private String key(String uri, String code) {
594    return "{" + uri + "}" + code;
595  }
596
597  private String key(ValueSetExpansionContainsComponent c) {
598    return key(c.getSystem(), c.getCode());
599  }
600
601}