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