001package org.hl7.fhir.r5.terminologies;
002
003import java.util.Calendar;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.List;
007
008/*
009  Copyright (c) 2011+, HL7, Inc.
010  All rights reserved.
011  
012  Redistribution and use in source and binary forms, with or without modification, 
013  are permitted provided that the following conditions are met:
014    
015   * Redistributions of source code must retain the above copyright notice, this 
016     list of conditions and the following disclaimer.
017   * Redistributions in binary form must reproduce the above copyright notice, 
018     this list of conditions and the following disclaimer in the documentation 
019     and/or other materials provided with the distribution.
020   * Neither the name of HL7 nor the names of its contributors may be used to 
021     endorse or promote products derived from this software without specific 
022     prior written permission.
023  
024  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
025  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
026  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
027  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
028  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
029  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
030  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
031  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
032  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
033  POSSIBILITY OF SUCH DAMAGE.
034  
035 */
036
037
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.exceptions.FHIRFormatError;
040import org.hl7.fhir.r5.context.IWorkerContext;
041import org.hl7.fhir.r5.model.BooleanType;
042import org.hl7.fhir.r5.model.CanonicalType;
043import org.hl7.fhir.r5.model.CodeSystem;
044import org.hl7.fhir.r5.model.DateTimeType;
045import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
046import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
047import org.hl7.fhir.r5.model.Identifier;
048import org.hl7.fhir.r5.model.Meta;
049import org.hl7.fhir.r5.model.UriType;
050import org.hl7.fhir.r5.model.ValueSet;
051import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
052import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
053import org.hl7.fhir.r5.model.CodeType;
054import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
055import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
056import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
057import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
058import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
059import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
060import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus;
061import org.hl7.fhir.r5.utils.ToolingExtensions;
062import org.hl7.fhir.utilities.StandardsStatus;
063import org.hl7.fhir.utilities.Utilities;
064
065public class ValueSetUtilities {
066
067  public static ValueSet makeShareable(ValueSet vs) {
068    if (!vs.hasExperimental()) {
069      vs.setExperimental(false);
070    }
071    if (!vs.hasMeta())
072      vs.setMeta(new Meta());
073    for (UriType t : vs.getMeta().getProfile()) 
074      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
075        return vs;
076    vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset"));
077    return vs;
078  }
079
080  public static boolean makeVSShareable(ValueSet vs) {
081    if (!vs.hasMeta())
082      vs.setMeta(new Meta());
083    for (UriType t : vs.getMeta().getProfile()) 
084      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
085        return false;
086    vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset"));
087    return true;
088  }
089
090  public static void checkShareable(ValueSet vs) {
091    if (!vs.hasMeta())
092      throw new Error("ValueSet "+vs.getUrl()+" is not shareable");
093    for (UriType t : vs.getMeta().getProfile()) {
094      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
095        return;
096    }
097    throw new Error("ValueSet "+vs.getUrl()+" is not shareable");    
098  }
099
100  public static boolean hasOID(ValueSet vs) {
101    return getOID(vs) != null;
102  }
103
104  public static String getOID(ValueSet vs) {
105    for (Identifier id : vs.getIdentifier()) {
106      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:"))
107        return id.getValue().substring(8);
108    }
109    return null;
110  }
111
112  public static void setOID(ValueSet vs, String oid) {
113    if (!oid.startsWith("urn:oid:"))
114      oid = "urn:oid:" + oid;
115    for (Identifier id : vs.getIdentifier()) {
116      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) {
117        id.setValue(oid);
118        return;
119      }
120    }
121    vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid);
122  }
123
124  public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException {
125    if (vs.hasUserData("external.url"))
126      return;
127    
128    if (wg != null) {
129      if (!ToolingExtensions.hasExtension(vs, ToolingExtensions.EXT_WORKGROUP) || 
130          (!Utilities.existsInList(ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) {
131        ToolingExtensions.setCodeExtension(vs, ToolingExtensions.EXT_WORKGROUP, wg);
132      }
133    }
134    if (status != null) {
135      StandardsStatus ss = ToolingExtensions.getStandardsStatus(vs);
136      if (ss == null || ss.isLowerThan(status)) 
137        ToolingExtensions.setStandardsStatus(vs, status, normativeVersion);
138      if (pckage != null) {
139        if (!vs.hasUserData("ballot.package"))        
140          vs.setUserData("ballot.package", pckage);
141        else if (!pckage.equals(vs.getUserString("ballot.package")))
142          if (!"infrastructure".equals(vs.getUserString("ballot.package")))
143          System.out.println("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString("ballot.package"));
144      }
145      if (status == StandardsStatus.NORMATIVE) {
146        vs.setExperimental(false);
147        vs.setStatus(PublicationStatus.ACTIVE);
148      }
149    }
150    if (fmm != null) {
151      String sfmm = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_FMM_LEVEL);
152      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm))  {
153        ToolingExtensions.setIntegerExtension(vs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
154      }
155      if (Integer.parseInt(fmm) <= 1) {
156        vs.setExperimental(true);
157      }
158    }
159    if (vs.hasUserData("cs"))
160      CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData("cs"), wg, status, pckage, fmm, normativeVersion);
161    else if (status == StandardsStatus.NORMATIVE && context != null) {
162      for (ConceptSetComponent csc : vs.getCompose().getInclude()) {
163        if (csc.hasSystem()) {
164          CodeSystem cs = context.fetchCodeSystem(csc.getSystem());
165          if (cs != null) {
166            CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion);
167          }
168        }
169      }
170    }
171  }
172
173  private static int ssval(String status) {
174    if ("Draft".equals("status")) 
175      return 1;
176    if ("Informative".equals("status")) 
177      return 2;
178    if ("External".equals("status")) 
179      return 3;
180    if ("Trial Use".equals("status")) 
181      return 3;
182    if ("Normative".equals("status")) 
183      return 4;
184    return -1;
185  }
186
187  public static ValueSet generateImplicitValueSet(String uri) {
188    if (uri.startsWith("http://snomed.info/sct"))
189      return generateImplicitSnomedValueSet(uri);
190    if (uri.startsWith("http://loinc.org/vs"))
191      return generateImplicitLoincValueSet(uri);
192    if (uri.equals("http://hl7.org/fhir/ValueSet/mimetypes")) {
193      return generateImplicitMimetypesValueSet(uri);
194    }
195    return null;
196  }
197
198  private static ValueSet generateImplicitMimetypesValueSet(String theUri) {
199    ValueSet valueSet = new ValueSet();
200    valueSet.setStatus(PublicationStatus.ACTIVE);
201    valueSet.setUrl(theUri);
202    valueSet.setDescription("This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)");
203    valueSet.getCompose()
204      .addInclude().setSystem("urn:ietf:bcp:13");
205    return valueSet;
206  }
207
208  private static ValueSet generateImplicitLoincValueSet(String uri) {
209    if ("http://loinc.org/vs".equals(uri))
210      return makeLoincValueSet();
211    if (uri.startsWith("http://loinc.org/vs/LL"))
212      return makeAnswerList(makeLoincValueSet(), uri);
213    return null;
214  }
215
216  private static ValueSet makeAnswerList(ValueSet vs, String uri) {
217    vs.setUrl(uri);
218    String c = uri.substring(20);
219    vs.setName("LOINCAnswers"+c);
220    vs.setTitle("LOINC Answer Codes for "+c);
221    vs.getCompose().getIncludeFirstRep().addFilter().setProperty("LIST").setOp(FilterOperator.EQUAL).setValue(c);
222    return vs;
223  }
224
225  private static ValueSet makeLoincValueSet() {
226    ValueSet vs = new ValueSet();
227    vs.setUrl("http://loinc.org/vs");
228    vs.setName("LOINCCodes");
229    vs.setTitle("All LOINC codes");
230    vs.setCopyright("This content LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at http://loinc.org/terms-of-use");
231    vs.setStatus(PublicationStatus.ACTIVE);
232    vs.getCompose().addInclude().setSystem("http://loinc.org");
233    return vs;
234  }
235
236  private static ValueSet generateImplicitSnomedValueSet(String uri) {
237    if ("http://snomed.info/sct?fhir_vs".equals(uri))
238      return makeImplicitSnomedValueSet(uri);
239    return null;
240  }
241
242  private static ValueSet makeImplicitSnomedValueSet(String uri) {
243    ValueSet vs = new ValueSet();
244    vs.setUrl(uri);
245    vs.setName("SCTValueSet");
246    vs.setTitle("SCT ValueSet");
247    vs.setDescription("All SNOMED CT Concepts");
248    vs.setCopyright("This value set includes content from SNOMED CT, which is copyright © 2002+ International Health Terminology Standards Development Organisation (SNOMED International), and distributed by agreement between SNOMED International and HL7. Implementer use of SNOMED CT is not covered by this agreement");
249    vs.setStatus(PublicationStatus.ACTIVE);
250    vs.getCompose().addInclude().setSystem("http://snomed.info/sct");
251    return vs;
252  }
253
254  public static void setDeprecated(List<ValueSetExpansionPropertyComponent> vsProp,  ValueSetExpansionContainsComponent n) {
255    n.addProperty().setCode("status").setValue(new CodeType("deprecated"));
256    for (ValueSetExpansionPropertyComponent o : vsProp) {
257      if ("status".equals(o.getCode())) {
258        return;
259      }
260    }
261    vsProp.add(new ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status"));
262  }
263
264
265  public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> {
266
267    @Override
268    public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) {
269      return o1.getCode().compareToIgnoreCase(o2.getCode());
270    }
271  }
272
273
274  public static void sortInclude(ConceptSetComponent inc) {
275    Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter());
276  }
277
278  public static String getAllCodesSystem(ValueSet vs) {
279    if (vs.hasCompose()) {
280      ValueSetComposeComponent c = vs.getCompose();
281      if (c.getExclude().isEmpty() && c.getInclude().size() == 1) {
282        ConceptSetComponent i = c.getIncludeFirstRep();
283        if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) {
284          return i.getSystem();
285        }
286      }
287    }
288    return null;
289  }
290
291  public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) {
292    try {
293      for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) {
294        if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) {
295          return true;
296        }      
297        // this, though status should also be set
298        if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 
299          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
300        // legacy  
301        if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
302          return ((BooleanType) p.getValue()).getValue();
303      }
304      StandardsStatus ss = ToolingExtensions.getStandardsStatus(c);
305      if (ss == StandardsStatus.DEPRECATED) {
306        return true;
307      }
308      return false;
309    } catch (FHIRException e) {
310      return false;
311    }  
312  }
313
314
315}