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