001package org.hl7.fhir.r5.context;
002
003import java.util.*;
004
005import lombok.Getter;
006import lombok.Setter;
007import lombok.extern.slf4j.Slf4j;
008import org.apache.commons.lang3.exception.ExceptionUtils;
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.r5.conformance.profile.BindingResolution;
012import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
013import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
014import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
015import org.hl7.fhir.r5.extensions.ExtensionUtilities;
016import org.hl7.fhir.r5.model.CanonicalResource;
017import org.hl7.fhir.r5.model.CodeSystem;
018import org.hl7.fhir.r5.model.ElementDefinition;
019import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
020import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
021import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
022import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType;
023import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent;
024import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
025import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
026import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
027import org.hl7.fhir.r5.model.StructureMap;
028
029import org.hl7.fhir.r5.utils.UserDataNames;
030import org.hl7.fhir.r5.utils.xver.XVerExtensionManager;
031import org.hl7.fhir.r5.model.Identifier;
032import org.hl7.fhir.r5.model.NamingSystem;
033import org.hl7.fhir.r5.model.Parameters;
034import org.hl7.fhir.r5.model.Resource;
035import org.hl7.fhir.r5.model.StructureDefinition;
036import org.hl7.fhir.r5.utils.xver.XVerExtensionManagerFactory;
037import org.hl7.fhir.utilities.*;
038import org.hl7.fhir.utilities.i18n.I18nConstants;
039import org.hl7.fhir.utilities.validation.ValidationMessage;
040import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
041import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
042
043@MarkedToMoveToAdjunctPackage
044@Slf4j
045public class ContextUtilities implements ProfileKnowledgeProvider {
046
047  private IWorkerContext context;
048  private XVerExtensionManager xverManager;
049  private Map<String, String> oidCache = new HashMap<>();
050  private List<StructureDefinition> allStructuresList = new ArrayList<StructureDefinition>();
051  private List<String> canonicalResourceNames;
052  private List<String> concreteResourceNames;
053  private Set<String> concreteResourceNameSet;
054  @Setter
055  private List<String> suppressedMappings;
056  @Getter
057  @Setter
058  private Set<String> localFileNames;
059  @Getter
060  @Setter
061  private Set<String> masterSourceNames;
062
063  public ContextUtilities(IWorkerContext context) {
064    super();
065    this.context = context;
066    this.suppressedMappings = new ArrayList<String>();
067  }
068
069  public ContextUtilities(IWorkerContext context, List<String> suppressedMappings) {
070    super();
071    this.context = context;
072    this.suppressedMappings = suppressedMappings;
073  }
074
075  @Deprecated
076  public boolean isSuppressDebugMessages() {
077    return false;
078  }
079
080  @Deprecated
081  public void setSuppressDebugMessages(boolean suppressDebugMessages) {
082    //DO NOTHING
083  }
084
085  public String oid2Uri(String oid) {
086    if (oid != null && oid.startsWith("urn:oid:")) {
087      oid = oid.substring(8);
088    }
089    if (oidCache.containsKey(oid)) {
090      return oidCache.get(oid);
091    }
092
093    String uri = OIDUtilities.getUriForOid(oid);
094    if (uri != null) {
095      oidCache.put(oid, uri);
096      return uri;
097    }
098    CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables");
099    if (cs != null) {
100      for (ConceptDefinitionComponent cc : cs.getConcept()) {
101        for (ConceptPropertyComponent cp : cc.getProperty()) {
102          if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) {
103            for (ConceptPropertyComponent cp2 : cc.getProperty()) {
104              if ("v2-cs-uri".equals(cp2.getCode())) {
105                oidCache.put(oid, cp2.getValue().primitiveValue());
106                return cp2.getValue().primitiveValue();
107              }
108            }
109          }
110        }
111      }
112    }
113    for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) {
114      if (("urn:oid:" + oid).equals(css.getUrl())) {
115        oidCache.put(oid, css.getUrl());
116        return css.getUrl();
117      }
118      for (Identifier id : css.getIdentifier()) {
119        if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:" + oid).equals(id.getValue())) {
120          oidCache.put(oid, css.getUrl());
121          return css.getUrl();
122        }
123      }
124    }
125    for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) {
126      if (hasOid(ns, oid)) {
127        uri = getUri(ns);
128        if (uri != null) {
129          oidCache.put(oid, null);
130          return null;
131        }
132      }
133    }
134    oidCache.put(oid, null);
135    return null;
136  }
137
138  private String getUri(NamingSystem ns) {
139    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
140      if (id.getType() == NamingSystemIdentifierType.URI)
141        return id.getValue();
142    }
143    return null;
144  }
145
146  private boolean hasOid(NamingSystem ns, String oid) {
147    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
148      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid))
149        return true;
150    }
151    return false;
152  }
153
154  /**
155   * @return a list of the resource and type names defined for this version
156   */
157  public List<String> getTypeNames() {
158    Set<String> result = new HashSet<String>();
159    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
160      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
161        result.add(sd.getName());
162    }
163    return Utilities.sorted(result);
164  }
165
166
167  /**
168   * @return a set of the resource and type names defined for this version
169   */
170  public Set<String> getTypeNameSet() {
171    Set<String> result = new HashSet<String>();
172    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
173      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION &&
174        VersionUtilities.versionMatches(context.getVersion(), sd.getFhirVersion().toCode())) {
175        result.add(sd.getName());
176      }
177    }
178    return result;
179  }
180
181  public String getLinkForUrl(String corePath, String url) {
182    if (url == null) {
183      return null;
184    }
185
186    if (context.hasResource(CanonicalResource.class, url)) {
187      CanonicalResource cr = context.fetchResource(CanonicalResource.class, url);
188      return cr.getWebPath();
189    }
190    return null;
191  }
192
193
194  protected String tail(String url) {
195    if (Utilities.noString(url)) {
196      return "noname";
197    }
198    if (url.contains("/")) {
199      return url.substring(url.lastIndexOf("/") + 1);
200    }
201    return url;
202  }
203
204  private boolean hasUrlProperty(StructureDefinition sd) {
205    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
206      if (ed.getPath().equals(sd.getType() + ".url")) {
207        return true;
208      }
209    }
210    return false;
211  }
212
213  // -- profile services ---------------------------------------------------------
214
215
216  /**
217   * @return a list of the resource names that are canonical resources defined for this version
218   */
219  public List<String> getCanonicalResourceNames() {
220    if (canonicalResourceNames == null) {
221      canonicalResourceNames = new ArrayList<>();
222      Set<String> names = new HashSet<>();
223      for (StructureDefinition sd : allStructures()) {
224        if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) {
225          names.add(sd.getType());
226        }
227      }
228      canonicalResourceNames.addAll(Utilities.sorted(names));
229    }
230    return canonicalResourceNames;
231  }
232
233  /**
234   * @return a list of all structure definitions, with snapshots generated (if possible)
235   */
236  public List<StructureDefinition> allStructures() {
237    if (allStructuresList.isEmpty()) {
238      Set<StructureDefinition> set = new HashSet<StructureDefinition>();
239      for (StructureDefinition sd : getStructures()) {
240        if (!set.contains(sd)) {
241          try {
242            generateSnapshot(sd);
243            // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
244          } catch (Exception e) {
245            log.debug("Unable to generate snapshot @2 for " + tail(sd.getUrl()) + " from " + tail(sd.getBaseDefinition()) + " because " + e.getMessage());
246            context.getLogger().logDebugMessage(ILoggingService.LogCategory.GENERATE, ExceptionUtils.getStackTrace(e));
247          }
248          allStructuresList.add(sd);
249          set.add(sd);
250        }
251      }
252    }
253    return allStructuresList;
254  }
255
256  /**
257   * @return a list of all structure definitions, without trying to generate snapshots
258   */
259  public List<StructureDefinition> getStructures() {
260    return context.fetchResourcesByType(StructureDefinition.class);
261  }
262
263  /**
264   * Given a structure definition, generate a snapshot (or regenerate it)
265   *
266   * @param p
267   * @throws DefinitionException
268   * @throws FHIRException
269   */
270  public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
271    if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p))) {
272      if (!p.hasBaseDefinition())
273        throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
274      StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), null, p);
275      if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinitionNoVersion())) {
276        sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
277      }
278      if (sd == null) {
279        throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
280      }
281      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
282      List<String> errors = new ArrayList<String>();
283      ProfileUtilities pu = new ProfileUtilities(context, msgs, this);
284      pu.setAutoFixSliceNames(true);
285      pu.setThrowException(false);
286      pu.setForPublication(context.isForPublication());
287      pu.setSuppressedMappings(suppressedMappings);
288      pu.setLocalFileNames(localFileNames);
289      pu.setMasterSourceFileNames(masterSourceNames);
290      if (xverManager == null) {
291        xverManager = XVerExtensionManagerFactory.createExtensionManager(context);
292      }
293      pu.setXver(xverManager);
294      if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
295        pu.sortDifferential(sd, p, p.getUrl(), errors, true);
296      }
297      pu.setDebug(false);
298      for (String err : errors) {
299        msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getWebPath(), "Error sorting Differential: " + err, ValidationMessage.IssueSeverity.ERROR));
300      }
301      pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString(UserDataNames.render_webroot), p.getName());
302      for (ValidationMessage msg : msgs) {
303        if ((!ProfileUtilities.isSuppressIgnorableExceptions() && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) {
304          if (!msg.isIgnorableError()) {
305            throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
306          } else {
307            log.error(msg.getMessage());
308          }
309        }
310      }
311      if (!p.hasSnapshot())
312        throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
313      pu = null;
314    }
315    p.setGeneratedSnapshot(true);
316  }
317
318
319  // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind.
320  private boolean isProfileNeedsRegenerate(StructureDefinition p) {
321    boolean needs = !p.hasUserData(UserDataNames.SNAPSHOT_regeneration_tracker) && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse");
322    if (needs) {
323      p.setUserData(UserDataNames.SNAPSHOT_regeneration_tracker, "yes");
324    }
325    return needs;
326  }
327
328  @Override
329  public boolean isPrimitiveType(String type) {
330    return context.isPrimitiveType(type);
331  }
332
333  @Override
334  public boolean isDatatype(String type) {
335    StructureDefinition sd = context.fetchTypeDefinition(type);
336    return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
337  }
338
339  @Override
340  public boolean isResource(String t) {
341    if (getConcreteResourceSet().contains(t)) {
342      return true;
343    }
344    StructureDefinition sd;
345    try {
346      sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + t);
347    } catch (Exception e) {
348      return false;
349    }
350    if (sd == null)
351      return false;
352    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
353      return false;
354    return sd.getKind() == StructureDefinitionKind.RESOURCE;
355  }
356
357  @Override
358  public boolean hasLinkFor(String typeSimple) {
359    return false;
360  }
361
362  @Override
363  public String getLinkFor(String corePath, String typeSimple) {
364    return null;
365  }
366
367  @Override
368  public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
369    return null;
370  }
371
372  @Override
373  public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
374    return null;
375  }
376
377  @Override
378  public String getLinkForProfile(StructureDefinition profile, String url) {
379    return null;
380  }
381
382  @Override
383  public boolean prependLinks() {
384    return false;
385  }
386
387  public StructureDefinition fetchByJsonName(String key) {
388    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
389      ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
390      if (/*sd.getKind() == StructureDefinitionKind.LOGICAL && */
391        // this is turned off because it's valid to use a FHIR type directly in
392        // an extension of this kind, and that can't be a logical model. Any profile on
393        // a type is acceptable as long as it has the json name on it
394        ed != null && ed.hasExtension(ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED) &&
395          key.equals(ExtensionUtilities.readStringExtension(ed, ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED))) {
396        return sd;
397      }
398    }
399    return null;
400  }
401
402  public Set<String> getConcreteResourceSet() {
403    if (concreteResourceNameSet == null) {
404      concreteResourceNameSet = new HashSet<>();
405      for (StructureDefinition sd : getStructures()) {
406        if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
407          concreteResourceNameSet.add(sd.getType());
408        }
409      }
410    }
411    return concreteResourceNameSet;
412  }
413
414  public List<String> getConcreteResources() {
415    if (concreteResourceNames == null) {
416      concreteResourceNames = new ArrayList<>();
417      concreteResourceNames.addAll(Utilities.sorted(getConcreteResourceSet()));
418    }
419    return concreteResourceNames;
420  }
421
422  public List<StructureMap> listMaps(String url) {
423    List<StructureMap> res = new ArrayList<>();
424    String start = url.substring(0, url.indexOf("*"));
425    String end = url.substring(url.indexOf("*") + 1);
426    for (StructureMap map : context.fetchResourcesByType(StructureMap.class)) {
427      String u = map.getUrl();
428      if (u.startsWith(start) && u.endsWith(end)) {
429        res.add(map);
430      }
431    }
432    return res;
433  }
434
435  public List<String> fetchCodeSystemVersions(String system) {
436    List<String> res = new ArrayList<>();
437    for (CodeSystem cs : context.fetchResourceVersions(CodeSystem.class, system)) {
438      if (cs.hasVersion()) {
439        res.add(cs.getVersion());
440      }
441    }
442    return res;
443  }
444
445  public StructureDefinition findType(String typeName) {
446    StructureDefinition t = context.fetchTypeDefinition(typeName);
447    if (t != null) {
448      return t;
449    }
450    List<StructureDefinition> candidates = new ArrayList<>();
451    for (StructureDefinition sd : getStructures()) {
452      if (sd.getType().equals(typeName)) {
453        candidates.add(sd);
454      }
455    }
456    if (candidates.size() == 1) {
457      return candidates.get(0);
458    }
459    return null;
460  }
461
462  public StructureDefinition fetchProfileByIdentifier(String tid) {
463    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
464      for (Identifier ii : sd.getIdentifier()) {
465        if (tid.equals(ii.getValue())) {
466          return sd;
467        }
468      }
469    }
470    return null;
471  }
472
473  public boolean isAbstractType(String typeName) {
474    StructureDefinition sd = context.fetchTypeDefinition(typeName);
475    if (sd != null) {
476      return sd.getAbstract();
477    }
478    return false;
479  }
480
481  public boolean isDomainResource(String typeName) {
482    StructureDefinition sd = context.fetchTypeDefinition(typeName);
483    while (sd != null) {
484      if ("DomainResource".equals(sd.getType())) {
485        return true;
486      }
487      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
488    }
489    return false;
490  }
491
492  public IWorkerContext getWorker() {
493    return context;
494  }
495
496  @Override
497  public String getCanonicalForDefaultContext() {
498    // TODO Auto-generated method stub
499    return null;
500  }
501
502  public String pinValueSet(String valueSet) {
503    return pinValueSet(valueSet, context.getExpansionParameters());
504  }
505
506  public String pinValueSet(String value, Parameters expParams) {
507    if (value.contains("|")) {
508      return value;
509    }
510    for (ParametersParameterComponent p : expParams.getParameter()) {
511      if ("default-valueset-version".equals(p.getName())) {
512        String s = p.getValue().primitiveValue();
513        if (s.startsWith(value + "|")) {
514          return s;
515        }
516      }
517    }
518    return value;
519  }
520
521  public List<StructureDefinition> allBaseStructures() {
522    List<StructureDefinition> res = new ArrayList<>();
523    for (StructureDefinition sd : allStructures()) {
524      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) {
525        res.add(sd);
526      }
527    }
528    return res;
529  }
530
531  public <T extends Resource> List<T> fetchByIdentifier(Class<T> class_, String system) {
532    List<T> list = new ArrayList<>();
533    for (T t : context.fetchResourcesByType(class_)) {
534      if (t instanceof CanonicalResource) {
535        CanonicalResource cr = (CanonicalResource) t;
536        for (Identifier id : cr.getIdentifier()) {
537          if (system.equals(id.getValue())) {
538            list.add(t);
539          }
540        }
541      }
542    }
543    return list;
544  }
545
546  public StructureDefinition fetchStructureByName(String name) {
547    StructureDefinition sd = null;
548    for (StructureDefinition t : context.fetchResourcesByType(StructureDefinition.class)) {
549      if (name.equals(t.getName())) {
550        if (sd == null) {
551          sd = t;
552        } else {
553          throw new FHIRException("Duplicate Structure name " + name + ": found both " + t.getVersionedUrl() + " and " + sd.getVersionedUrl());
554        }
555      }
556    }
557    return sd;
558  }
559
560  @Override
561  public String getDefinitionsName(Resource r) {
562    // TODO Auto-generated method stub
563    return null;
564  }
565
566  public NamingSystem fetchNamingSystem(String url) {
567    if (url == null) {
568      return null;
569    }
570    for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) {
571      for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
572        if (url.equals(id.getValue())) {
573          return ns;
574        }
575      }
576    }
577    return null;
578  }
579}
580