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
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.dstu2.model.DateTimeType;
073import org.hl7.fhir.dstu2.model.Factory;
074import org.hl7.fhir.dstu2.model.PrimitiveType;
075import org.hl7.fhir.dstu2.model.Type;
076import org.hl7.fhir.dstu2.model.UriType;
077import org.hl7.fhir.dstu2.model.ValueSet;
078import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionComponent;
079import org.hl7.fhir.dstu2.model.ValueSet.ConceptReferenceComponent;
080import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent;
081import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetFilterComponent;
082import org.hl7.fhir.dstu2.model.ValueSet.FilterOperator;
083import org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent;
084import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent;
085import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent;
086import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionParameterComponent;
087import org.hl7.fhir.dstu2.utils.IWorkerContext;
088import org.hl7.fhir.dstu2.utils.ToolingExtensions;
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      handleDefine(source, focus.getExpansion().getParameter());
117      if (source.hasCompose()) 
118        handleCompose(source.getCompose(), focus.getExpansion().getParameter());
119
120      for (ValueSetExpansionContainsComponent c : codes) {
121        if (map.containsKey(key(c))) {
122          focus.getExpansion().getContains().add(c);
123        }
124      }
125      return new ValueSetExpansionOutcome(focus, null);
126    } catch (Exception e) {
127      // well, we couldn't expand, so we'll return an interface to a checker that can 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) throws TerminologyServiceException, ETooCostly, FileNotFoundException, IOException {
134        for (UriType imp : compose.getImport()) 
135                importValueSet(imp.getValue(), params);
136        for (ConceptSetComponent inc : compose.getInclude()) 
137                includeCodes(inc, params);
138        for (ConceptSetComponent inc : compose.getExclude()) 
139                excludeCodes(inc, params);
140
141  }
142
143        private void importValueSet(String value, List<ValueSetExpansionParameterComponent> params) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException {
144          if (value == null)
145                throw new TerminologyServiceException("unable to find value set with no identity");
146          ValueSet vs = context.fetchResource(ValueSet.class, value);
147          if (vs == null)
148                        throw new TerminologyServiceException("Unable to find imported value set "+value);
149          ValueSetExpansionOutcome vso = factory.getExpander().expand(vs);
150          if (vso.getService() != null)
151      throw new TerminologyServiceException("Unable to expand imported value set "+value);
152    if (vs.hasVersion())
153      if (!existsInParams(params, "version", new UriType(vs.getUrl()+"?version="+vs.getVersion())))
154        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion())));
155    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
156      if (!existsInParams(params, p.getName(), p.getValue()))
157          params.add(p);
158    }
159    
160          for (ValueSetExpansionContainsComponent c : vso.getValueset().getExpansion().getContains()) {
161                addCode(c.getSystem(), c.getCode(), c.getDisplay());
162          }       
163  }
164
165        private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) {
166    for (ValueSetExpansionParameterComponent p : params) {
167      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false))
168        return true;
169    }
170    return false;
171  }
172
173  private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException, ETooCostly {
174          if (context.supportsSystem(inc.getSystem())) {
175        addCodes(context.expandVS(inc), params);
176      return;
177          }
178            
179          ValueSet cs = context.fetchCodeSystem(inc.getSystem());
180          if (cs == null)
181                throw new TerminologyServiceException("unable to find code system "+inc.getSystem().toString());
182          if (cs.hasVersion())
183      if (!existsInParams(params, "version", new UriType(cs.getUrl()+"?version="+cs.getVersion())))
184        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl()+"?version="+cs.getVersion())));
185          if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
186            // special case - add all the code system
187            for (ConceptDefinitionComponent def : cs.getCodeSystem().getConcept()) {
188        addCodeAndDescendents(inc.getSystem(), def);
189            }
190          }
191            
192          for (ConceptReferenceComponent c : inc.getConcept()) {
193                addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay());
194          }
195          if (inc.getFilter().size() > 1)
196            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
197    if (inc.getFilter().size() == 1) {
198            ConceptSetFilterComponent fc = inc.getFilter().get(0);
199                if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
200                        // special: all non-abstract codes in the target code system under the value
201                        ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), fc.getValue());
202                        if (def == null)
203                                throw new TerminologyServiceException("Code '"+fc.getValue()+"' not found in system '"+inc.getSystem()+"'");
204                        addCodeAndDescendents(inc.getSystem(), def);
205                } else
206                        throw new NotImplementedException("not done yet");
207          }
208  }
209
210        private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) throws ETooCostly {
211          if (expand.getContains().size() > 500) 
212            throw new ETooCostly("Too many codes to display (>"+Integer.toString(expand.getContains().size())+")");
213    for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
214      if (!existsInParams(params, p.getName(), p.getValue()))
215          params.add(p);
216    }
217          
218    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
219      addCode(c.getSystem(), c.getCode(), c.getDisplay());
220    }   
221  }
222
223        private void addCodeAndDescendents(String system, ConceptDefinitionComponent def) {
224                if (!ToolingExtensions.hasDeprecated(def)) {  
225                        if (!def.hasAbstractElement() || !def.getAbstract())
226                                addCode(system, def.getCode(), def.getDisplay());
227                        for (ConceptDefinitionComponent c : def.getConcept()) 
228                                addCodeAndDescendents(system, c);
229                }
230  }
231
232        private void excludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException {
233          ValueSet cs = context.fetchCodeSystem(inc.getSystem().toString());
234          if (cs == null)
235                throw new TerminologyServiceException("unable to find value set "+inc.getSystem().toString());
236    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
237      // special case - add all the code system
238//      for (ConceptDefinitionComponent def : cs.getDefine().getConcept()) {
239//!!!!        addCodeAndDescendents(inc.getSystem(), def);
240//      }
241    }
242      
243
244          for (ConceptReferenceComponent c : inc.getConcept()) {
245                // 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
246                map.remove(key(inc.getSystem(), c.getCode()));
247          }
248          if (inc.getFilter().size() > 0)
249                throw new NotImplementedException("not done yet");
250  }
251
252        
253        private String getCodeDisplay(ValueSet cs, String code) throws TerminologyServiceException {
254                ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), code);
255                if (def == null)
256                        throw new TerminologyServiceException("Unable to find code '"+code+"' in code system "+cs.getCodeSystem().getSystem());
257                return def.getDisplay();
258  }
259
260        private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
261                for (ConceptDefinitionComponent c : clist) {
262                        if (code.equals(c.getCode()))
263                          return c;
264                        ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);   
265                        if (v != null)
266                          return v;
267                }
268                return null;
269  }
270        
271        private void handleDefine(ValueSet vs, List<ValueSetExpansionParameterComponent> list) {
272          if (vs.hasVersion())
273            list.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion())));
274          if (vs.hasCodeSystem()) {
275      // simple case: just generate the return
276        for (ConceptDefinitionComponent c : vs.getCodeSystem().getConcept()) 
277                addDefinedCode(vs, vs.getCodeSystem().getSystem(), c);
278        }
279  }
280
281        private String key(ValueSetExpansionContainsComponent c) {
282                return key(c.getSystem(), c.getCode());
283        }
284
285        private String key(String uri, String code) {
286                return "{"+uri+"}"+code;
287        }
288
289        private void addDefinedCode(ValueSet vs, String system, ConceptDefinitionComponent c) {
290                if (!ToolingExtensions.hasDeprecated(c)) { 
291
292                        if (!c.hasAbstractElement() || !c.getAbstract()) {
293                                addCode(system, c.getCode(), c.getDisplay());
294                        }
295                        for (ConceptDefinitionComponent g : c.getConcept()) 
296                                addDefinedCode(vs, vs.getCodeSystem().getSystem(), g);
297                }
298  }
299
300        private void addCode(String system, String code, String display) {
301                ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
302                n.setSystem(system);
303          n.setCode(code);
304          n.setDisplay(display);
305          String s = key(n);
306          if (!map.containsKey(s)) { 
307                codes.add(n);
308                map.put(s, n);
309          }
310  }
311
312  
313}