001package org.hl7.fhir.dstu2016may.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
032
033
034import java.io.FileNotFoundException;
035import java.io.IOException;
036
037/*
038Copyright (c) 2011+, HL7, Inc
039All rights reserved.
040
041Redistribution and use in source and binary forms, with or without modification, 
042are 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
053THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
054ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
055WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
056IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
057INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
058NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
059PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
060WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
061ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
062POSSIBILITY OF SUCH DAMAGE.
063
064*/
065
066import java.util.ArrayList;
067import java.util.HashMap;
068import java.util.List;
069import java.util.Map;
070
071import org.apache.commons.lang3.NotImplementedException;
072import org.hl7.fhir.dstu2016may.model.CodeSystem;
073import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent;
074import org.hl7.fhir.dstu2016may.model.DateTimeType;
075import org.hl7.fhir.dstu2016may.model.Factory;
076import org.hl7.fhir.dstu2016may.model.PrimitiveType;
077import org.hl7.fhir.dstu2016may.model.Type;
078import org.hl7.fhir.dstu2016may.model.UriType;
079import org.hl7.fhir.dstu2016may.model.ValueSet;
080import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent;
081import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
082import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetFilterComponent;
083import org.hl7.fhir.dstu2016may.model.ValueSet.FilterOperator;
084import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetComposeComponent;
085import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent;
086import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent;
087import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionParameterComponent;
088import org.hl7.fhir.dstu2016may.utils.IWorkerContext;
089import org.hl7.fhir.exceptions.TerminologyServiceException;
090import org.hl7.fhir.utilities.Utilities;
091
092public class ValueSetExpanderSimple implements ValueSetExpander {
093
094  private IWorkerContext context;
095  private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
096  private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
097  private ValueSet focus;
098
099        private ValueSetExpanderFactory factory;
100  
101  public ValueSetExpanderSimple(IWorkerContext context, ValueSetExpanderFactory factory) {
102    super();
103    this.context = context;
104    this.factory = factory;
105  }
106  
107  @Override
108  public ValueSetExpansionOutcome expand(ValueSet source) {
109
110    try {
111      focus = source.copy();
112      focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
113      focus.getExpansion().setTimestampElement(DateTimeType.now());
114      focus.getExpansion().setIdentifier(Factory.createUUID());
115
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 (RuntimeException e) {
126         // TODO: we should put something more specific instead of just Exception below, since
127         // it swallows bugs.. what would be expected to be caught there?
128         throw e;
129    } catch (Exception e) {
130      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
131      // that might fail too, but it might not, later.
132      return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage());
133    }
134  }
135
136        private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException, ETooCostly, FileNotFoundException, IOException {
137        for (UriType imp : compose.getImport()) 
138                importValueSet(imp.getValue(), params);
139        for (ConceptSetComponent inc : compose.getInclude()) 
140                includeCodes(inc, params);
141        for (ConceptSetComponent inc : compose.getExclude()) 
142                excludeCodes(inc, params);
143
144  }
145
146        private void importValueSet(String value, List<ValueSetExpansionParameterComponent> params) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException {
147          if (value == null)
148                throw new TerminologyServiceException("unable to find value set with no identity");
149          ValueSet vs = context.fetchResource(ValueSet.class, value);
150          if (vs == null)
151                        throw new TerminologyServiceException("Unable to find imported value set "+value);
152          ValueSetExpansionOutcome vso = factory.getExpander().expand(vs);
153          if (vso.getService() != null)
154      throw new TerminologyServiceException("Unable to expand imported value set "+value);
155    if (vs.hasVersion())
156      if (!existsInParams(params, "version", new UriType(vs.getUrl()+"?version="+vs.getVersion())))
157        params.add(new ValueSetExpansionParameterComponent().setName("version").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) throws TerminologyServiceException, ETooCostly {
177          if (context.supportsSystem(inc.getSystem())) {
178      try {
179        int i = codes.size();
180        addCodes(context.expandVS(inc), params);
181        if (codes.size() > i)
182      return;
183      } catch (Exception e) {
184        // ok, we'll try locally
185      }
186          }
187            
188          CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
189          if (cs == null)
190                throw new TerminologyServiceException("unable to find code system "+inc.getSystem().toString());
191          if (cs.hasVersion())
192      if (!existsInParams(params, "version", new UriType(cs.getUrl()+"?version="+cs.getVersion())))
193        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl()+"?version="+cs.getVersion())));
194          if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
195            // special case - add all the code system
196            for (ConceptDefinitionComponent def : cs.getConcept()) {
197        addCodeAndDescendents(cs, inc.getSystem(), def);
198            }
199          }
200            
201          for (ConceptReferenceComponent c : inc.getConcept()) {
202                addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay());
203          }
204          if (inc.getFilter().size() > 1)
205            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
206    if (inc.getFilter().size() == 1) {
207            ConceptSetFilterComponent fc = inc.getFilter().get(0);
208                if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
209                        // special: all non-abstract codes in the target code system under the value
210                        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
211                        if (def == null)
212                                throw new TerminologyServiceException("Code '"+fc.getValue()+"' not found in system '"+inc.getSystem()+"'");
213                        addCodeAndDescendents(cs, inc.getSystem(), def);
214                } else
215                        throw new NotImplementedException("not done yet");
216          }
217  }
218
219        private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) 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(CodeSystem cs, String system, ConceptDefinitionComponent def) {
233                if (!CodeSystemUtilities.isDeprecated(cs, def)) {  
234                        if (!CodeSystemUtilities.isAbstract(cs, def))
235                                addCode(system, def.getCode(), def.getDisplay());
236                        for (ConceptDefinitionComponent c : def.getConcept()) 
237                                addCodeAndDescendents(cs, system, c);
238                }
239  }
240
241        private void excludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException {
242          CodeSystem cs = context.fetchCodeSystem(inc.getSystem().toString());
243          if (cs == null)
244                throw new TerminologyServiceException("unable to find value set "+inc.getSystem().toString());
245    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
246      // special case - add all the code system
247//      for (ConceptDefinitionComponent def : cs.getDefine().getConcept()) {
248//!!!!        addCodeAndDescendents(inc.getSystem(), def);
249//      }
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 gotten into this list if they aren't valid
255                map.remove(key(inc.getSystem(), c.getCode()));
256          }
257          if (inc.getFilter().size() > 0)
258                throw new NotImplementedException("not done yet");
259  }
260
261        
262        private String getCodeDisplay(CodeSystem cs, String code) throws TerminologyServiceException {
263                ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), code);
264                if (def == null)
265                        throw new TerminologyServiceException("Unable to find code '"+code+"' in code system "+cs.getUrl());
266                return def.getDisplay();
267  }
268
269        private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
270                for (ConceptDefinitionComponent c : clist) {
271                        if (code.equals(c.getCode()))
272                          return c;
273                        ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);   
274                        if (v != null)
275                          return v;
276                }
277                return null;
278  }
279        
280        private String key(ValueSetExpansionContainsComponent c) {
281                return key(c.getSystem(), c.getCode());
282        }
283
284        private String key(String uri, String code) {
285                return "{"+uri+"}"+code;
286        }
287
288        private void addCode(String system, String code, String display) {
289                ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
290                n.setSystem(system);
291          n.setCode(code);
292          n.setDisplay(display);
293          String s = key(n);
294          if (!map.containsKey(s)) { 
295                codes.add(n);
296                map.put(s, n);
297          }
298  }
299
300  
301}