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