001package org.hl7.fhir.r4.context;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.HashSet;
006import java.util.List;
007import java.util.Map;
008import java.util.Set;
009
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.r4.conformance.ProfileUtilities;
013import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider;
014import org.hl7.fhir.r4.model.CodeSystem;
015import org.hl7.fhir.r4.model.ElementDefinition;
016import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
017import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent;
018import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
019import org.hl7.fhir.r4.model.NamingSystem.NamingSystemIdentifierType;
020import org.hl7.fhir.r4.model.NamingSystem.NamingSystemUniqueIdComponent;
021import org.hl7.fhir.r4.model.Resource;
022import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
023import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
024import org.hl7.fhir.r4.model.StructureMap;
025import org.hl7.fhir.r4.model.Identifier;
026import org.hl7.fhir.r4.model.NamingSystem;
027import org.hl7.fhir.r4.model.StructureDefinition;
028import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
029import org.hl7.fhir.utilities.OIDUtilities;
030import org.hl7.fhir.utilities.Utilities;
031import org.hl7.fhir.utilities.VersionUtilities;
032import org.hl7.fhir.utilities.i18n.I18nConstants;
033import org.hl7.fhir.utilities.validation.ValidationMessage;
034import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
035import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
036
037@MarkedToMoveToAdjunctPackage
038public class ContextUtilities implements ProfileKnowledgeProvider {
039
040  private IWorkerContext context;
041  private boolean suppressDebugMessages;
042  private Map<String, String> oidCache = new HashMap<>();
043  private List<StructureDefinition> allStructuresList = new ArrayList<StructureDefinition>();
044  private List<String> canonicalResourceNames;
045  private List<String> concreteResourceNames;
046  private Set<String> concreteResourceNameSet;
047  
048  public ContextUtilities(IWorkerContext context) {
049    super();
050    this.context = context;
051  }
052
053  public boolean isSuppressDebugMessages() {
054    return suppressDebugMessages;
055  }
056
057  public void setSuppressDebugMessages(boolean suppressDebugMessages) {
058    this.suppressDebugMessages = suppressDebugMessages;
059  }
060  
061  public String oid2Uri(String oid) {
062    if (oid != null && oid.startsWith("urn:oid:")) {
063      oid = oid.substring(8);
064    }
065    if (oidCache.containsKey(oid)) {
066      return oidCache.get(oid);
067    }
068
069    String uri = OIDUtilities.getUriForOid(oid);
070    if (uri != null) {
071      oidCache.put(oid, uri);
072      return uri;
073    }
074    CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables");
075    if (cs != null) {
076      for (ConceptDefinitionComponent cc : cs.getConcept()) {
077        for (ConceptPropertyComponent cp : cc.getProperty()) {
078          if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) {
079            for (ConceptPropertyComponent cp2 : cc.getProperty()) {
080              if ("v2-cs-uri".equals(cp2.getCode())) {
081                oidCache.put(oid, cp2.getValue().primitiveValue());
082                return cp2.getValue().primitiveValue();                  
083              }
084            }              
085          }
086        }
087      }
088    }
089    for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) {
090      if (("urn:oid:"+oid).equals(css.getUrl())) {
091        oidCache.put(oid, css.getUrl());
092        return css.getUrl();
093      }
094      for (Identifier id : css.getIdentifier()) {
095        if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) {
096          oidCache.put(oid, css.getUrl());
097          return css.getUrl();
098        }
099      }
100    }
101    for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) {
102      if (hasOid(ns, oid)) {
103        uri = getUri(ns);
104        if (uri != null) {
105          oidCache.put(oid, null);
106          return null;
107        }
108      }
109    }
110    oidCache.put(oid, null);
111    return null;
112  }    
113
114  private String getUri(NamingSystem ns) {
115    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
116      if (id.getType() == NamingSystemIdentifierType.URI)
117        return id.getValue();
118    }
119    return null;
120  }
121
122  private boolean hasOid(NamingSystem ns, String oid) {
123    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
124      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid))
125        return true;
126    }
127    return false;
128  }
129
130  /**
131   * @return a list of the resource and type names defined for this version
132   */
133  public List<String> getTypeNames() {
134    Set<String> result = new HashSet<String>();
135    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
136      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
137        result.add(sd.getName());
138    }
139    return Utilities.sorted(result);
140  }
141
142
143  /**
144   * @return a set of the resource and type names defined for this version
145   */
146  public Set<String> getTypeNameSet() {
147    Set<String> result = new HashSet<String>();
148    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
149      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && 
150          VersionUtilities.versionsCompatible(context.getVersion(), sd.getFhirVersion().toCode())) {
151        result.add(sd.getName());
152      }
153    }
154    return result;
155  }
156
157  public String getLinkForUrl(String corePath, String url) {
158    if (url == null) {
159      return null;
160    }
161    
162    if (context.hasResource(Resource.class, url)) {
163      Resource  cr = context.fetchResource(Resource.class, url);
164      return cr.getUserString("path");
165    }
166    return null;
167  }
168  
169
170  protected String tail(String url) {
171    if (Utilities.noString(url)) {
172      return "noname";
173    }
174    if (url.contains("/")) {
175      return url.substring(url.lastIndexOf("/")+1);
176    }
177    return url;
178  }
179  
180  private boolean hasUrlProperty(StructureDefinition sd) {
181    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
182      if (ed.getPath().equals(sd.getType()+".url")) {
183        return true;
184      }
185    }
186    return false;
187  }
188  
189  // -- profile services ---------------------------------------------------------
190  
191
192  /**
193   * @return a list of the resource names that are canonical resources defined for this version
194   */
195  public List<String> getCanonicalResourceNames() {
196    if (canonicalResourceNames == null) {
197      canonicalResourceNames =  new ArrayList<>();
198      Set<String> names = new HashSet<>();
199      for (StructureDefinition sd : allStructures()) {
200        if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) {
201          names.add(sd.getType());
202        }
203      }
204      canonicalResourceNames.addAll(Utilities.sorted(names));
205    }
206    return canonicalResourceNames;
207  }
208
209  /**
210   * @return a list of all structure definitions, with snapshots generated (if possible)
211   */
212  public List<StructureDefinition> allStructures(){
213    if (allStructuresList.isEmpty()) {
214      Set<StructureDefinition> set = new HashSet<StructureDefinition>();
215      for (StructureDefinition sd : getStructures()) {
216        if (!set.contains(sd)) {
217          try {
218            generateSnapshot(sd);
219            // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
220          } catch (Exception e) {
221            if (!isSuppressDebugMessages()) {
222              System.out.println("Unable to generate snapshot @2 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
223              if (context.getLogger() != null) {
224                e.printStackTrace();
225              }
226            }
227          }
228          allStructuresList.add(sd);
229          set.add(sd);
230        }
231      }
232    }
233    return allStructuresList;
234  }
235
236  /**
237   * @return a list of all structure definitions, without trying to generate snapshots
238   */
239  public List<StructureDefinition> getStructures() {
240    return context.fetchResourcesByType(StructureDefinition.class);
241  }
242    
243  /**
244   * Given a structure definition, generate a snapshot (or regenerate it)
245   * @param p
246   * @throws DefinitionException
247   * @throws FHIRException
248   */
249  public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
250    if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p))) {
251      if (!p.hasBaseDefinition())
252        throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
253      StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p);
254      if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
255        throw new Error("Not done yet"); // sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
256      }
257      if (sd == null) {
258        throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
259      }
260      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
261      List<String> errors = new ArrayList<String>();
262      ProfileUtilities pu = new ProfileUtilities(context, msgs, this);
263      pu.setThrowException(false);
264      if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
265        pu.sortDifferential(sd, p, p.getUrl(), errors);
266      }
267      pu.setDebug(false);
268      for (String err : errors) {
269        msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
270      }
271      pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName());
272      for (ValidationMessage msg : msgs) {
273        if ((msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) {
274          if (!msg.isIgnorableError()) {
275            throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
276          } else {
277            System.err.println(msg.getMessage());
278          }
279        }
280      }
281      if (!p.hasSnapshot())
282        throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
283      pu = null;
284    }
285  }
286  
287
288  // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind.
289  private boolean isProfileNeedsRegenerate(StructureDefinition p) {
290    boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse");
291    if (needs) {
292      p.setUserData("hack.regnerated", "yes");
293    }
294    return needs;
295  }
296
297  @Override
298  public boolean isPrimitiveType(String type) {
299    return context.isPrimitiveType(type);
300  }
301
302  @Override
303  public boolean isDatatype(String type) {
304    StructureDefinition sd = context.fetchTypeDefinition(type);
305    return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
306  }
307
308  @Override
309  public boolean isResource(String t) {
310    if (getConcreteResourceSet().contains(t)) {
311      return true;
312    }
313    StructureDefinition sd;
314    try {
315      sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
316    } catch (Exception e) {
317      return false;
318    }
319    if (sd == null)
320      return false;
321    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
322      return false;
323    return sd.getKind() == StructureDefinitionKind.RESOURCE;
324  }
325
326  @Override
327  public boolean hasLinkFor(String typeSimple) {
328    return false;
329  }
330
331  @Override
332  public String getLinkFor(String corePath, String typeSimple) {
333    return null;
334  }
335
336  @Override
337  public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
338    return null;
339  }
340
341  @Override
342  public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
343    return null;
344  }
345
346  @Override
347  public String getLinkForProfile(StructureDefinition profile, String url) {
348    return null;
349  }
350  @Override
351  public boolean prependLinks() {
352    return false;
353  }
354
355  public StructureDefinition fetchByJsonName(String key) {
356    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
357      ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
358      if (ed != null) {
359        return sd;
360      }
361    }
362    return null;
363  }
364
365  public Set<String> getConcreteResourceSet() {
366    if (concreteResourceNameSet == null) {
367      concreteResourceNameSet =  new HashSet<>();
368      for (StructureDefinition sd : getStructures()) {
369        if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
370          concreteResourceNameSet.add(sd.getType());
371        }
372      }
373    }
374    return concreteResourceNameSet;
375  }
376
377  public List<String> getConcreteResources() {
378    if (concreteResourceNames == null) {
379      concreteResourceNames =  new ArrayList<>();
380      concreteResourceNames.addAll(Utilities.sorted(getConcreteResourceSet()));
381    }
382    return concreteResourceNames;
383  }
384
385  public List<StructureMap> listMaps(String url) {
386    List<StructureMap> res = new ArrayList<>();
387    String start = url.substring(0, url.indexOf("*"));
388    String end = url.substring(url.indexOf("*")+1);
389    for (StructureMap map : context.fetchResourcesByType(StructureMap.class)) {
390      String u = map.getUrl();
391      if (u.startsWith(start) && u.endsWith(end)) {
392        res.add(map);
393      }
394    }
395    return res;
396  }
397
398  public List<String> fetchCodeSystemVersions(String system) {
399    List<String> res = new ArrayList<>();
400    for (CodeSystem cs : context.fetchResourcesByType(CodeSystem.class)) {
401      if (system.equals(cs.getUrl()) && cs.hasVersion()) {
402        res.add(cs.getVersion());
403      }
404    }
405    return res;
406  }
407
408  public StructureDefinition findType(String typeName) {
409    StructureDefinition t = context.fetchTypeDefinition(typeName);
410    if (t != null) {
411      return t;
412    }
413    List<StructureDefinition> candidates = new ArrayList<>();
414    for (StructureDefinition sd : getStructures()) {
415      if (sd.getType().equals(typeName)) {
416        candidates.add(sd);
417      }
418    }
419    if (candidates.size() == 1) {
420      return candidates.get(0);
421    }
422    return null;
423  }
424
425  public StructureDefinition fetchProfileByIdentifier(String tid) {
426    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
427      for (Identifier ii : sd.getIdentifier()) {
428        if (tid.equals(ii.getValue())) {
429          return sd;
430        }
431      }
432    }
433    return null;
434  }
435
436  public boolean isAbstractType(String typeName) {
437    StructureDefinition sd = context.fetchTypeDefinition(typeName);
438    if (sd != null) {
439      return sd.getAbstract();
440    }
441    return false;
442  }
443
444  public boolean isDomainResource(String typeName) {
445    StructureDefinition sd = context.fetchTypeDefinition(typeName);
446    while (sd != null) {
447      if ("DomainResource".equals(sd.getType())) {
448        return true;
449      }
450      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());  
451    }
452    return false;
453  }
454
455  public IWorkerContext getWorker() {
456    return context;     
457  }
458
459}
460