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