001package org.hl7.fhir.r5.context;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.HashMap;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Map;
009import java.util.Set;
010
011import org.hl7.fhir.exceptions.DefinitionException;
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.r5.conformance.profile.BindingResolution;
014import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
015import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
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.StructureDefinition.StructureDefinitionKind;
025import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
026import org.hl7.fhir.r5.utils.ToolingExtensions;
027import org.hl7.fhir.r5.utils.XVerExtensionManager;
028import org.hl7.fhir.r5.model.Identifier;
029import org.hl7.fhir.r5.model.NamingSystem;
030import org.hl7.fhir.r5.model.StructureDefinition;
031import org.hl7.fhir.utilities.OIDUtils;
032import org.hl7.fhir.utilities.Utilities;
033import org.hl7.fhir.utilities.i18n.I18nConstants;
034import org.hl7.fhir.utilities.validation.ValidationMessage;
035import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
036import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
037
038public class ContextUtilities implements ProfileKnowledgeProvider {
039
040  private IWorkerContext context;
041  private boolean suppressDebugMessages;
042  private boolean ignoreProfileErrors;
043  private XVerExtensionManager xverManager;
044  private Map<String, String> oidCache = new HashMap<>();
045  
046  public ContextUtilities(IWorkerContext context) {
047    super();
048    this.context = context;
049  }
050
051  public boolean isSuppressDebugMessages() {
052    return suppressDebugMessages;
053  }
054
055  public void setSuppressDebugMessages(boolean suppressDebugMessages) {
056    this.suppressDebugMessages = suppressDebugMessages;
057  }
058  public boolean isIgnoreProfileErrors() {
059    return ignoreProfileErrors;
060  }
061
062  public void setIgnoreProfileErrors(boolean ignoreProfileErrors) {
063    this.ignoreProfileErrors = ignoreProfileErrors;
064  }
065
066  public String oid2Uri(String oid) {
067    if (oid != null && oid.startsWith("urn:oid:")) {
068      oid = oid.substring(8);
069    }
070    if (oidCache.containsKey(oid)) {
071      return oidCache.get(oid);
072    }
073
074    String uri = OIDUtils.getUriForOid(oid);
075    if (uri != null) {
076      oidCache.put(oid, uri);
077      return uri;
078    }
079    CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables");
080    if (cs != null) {
081      for (ConceptDefinitionComponent cc : cs.getConcept()) {
082        for (ConceptPropertyComponent cp : cc.getProperty()) {
083          if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) {
084            for (ConceptPropertyComponent cp2 : cc.getProperty()) {
085              if ("v2-cs-uri".equals(cp2.getCode())) {
086                oidCache.put(oid, cp2.getValue().primitiveValue());
087                return cp2.getValue().primitiveValue();                  
088              }
089            }              
090          }
091        }
092      }
093    }
094    for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) {
095      if (("urn:oid:"+oid).equals(css.getUrl())) {
096        oidCache.put(oid, css.getUrl());
097        return css.getUrl();
098      }
099      for (Identifier id : css.getIdentifier()) {
100        if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) {
101          oidCache.put(oid, css.getUrl());
102          return css.getUrl();
103        }
104      }
105    }
106    for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) {
107      if (hasOid(ns, oid)) {
108        uri = getUri(ns);
109        if (uri != null) {
110          oidCache.put(oid, null);
111          return null;
112        }
113      }
114    }
115    oidCache.put(oid, null);
116    return null;
117  }    
118
119  private String getUri(NamingSystem ns) {
120    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
121      if (id.getType() == NamingSystemIdentifierType.URI)
122        return id.getValue();
123    }
124    return null;
125  }
126
127  private boolean hasOid(NamingSystem ns, String oid) {
128    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
129      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid))
130        return true;
131    }
132    return false;
133  }
134
135  /**
136   * @return a list of the resource and type names defined for this version
137   */
138  public List<String> getTypeNames() {
139    Set<String> result = new HashSet<String>();
140    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
141      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
142        result.add(sd.getName());
143    }
144    return Utilities.sorted(result);
145  }
146
147
148  /**
149   * @return a set of the resource and type names defined for this version
150   */
151  public Set<String> getTypeNameSet() {
152    Set<String> result = new HashSet<String>();
153    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
154      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
155        result.add(sd.getName());
156    }
157    return result;
158  }
159
160  public String getLinkForUrl(String corePath, String url) {
161    if (url == null) {
162      return null;
163    }
164    
165    if (context.hasResource(CanonicalResource.class, url)) {
166      CanonicalResource  cr = context.fetchResource(CanonicalResource.class, url);
167      return cr.getUserString("path");
168    }
169    return null;
170  }
171  
172
173  protected String tail(String url) {
174    if (Utilities.noString(url)) {
175      return "noname";
176    }
177    if (url.contains("/")) {
178      return url.substring(url.lastIndexOf("/")+1);
179    }
180    return url;
181  }
182  
183  private boolean hasUrlProperty(StructureDefinition sd) {
184    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
185      if (ed.getPath().equals(sd.getType()+".url")) {
186        return true;
187      }
188    }
189    return false;
190  }
191  
192  // -- profile services ---------------------------------------------------------
193  
194
195  /**
196   * @return a list of the resource names that are canonical resources defined for this version
197   */
198  public List<String> getCanonicalResourceNames() {
199    List<String> names = new ArrayList<>();
200    for (StructureDefinition sd : allStructures()) {
201      if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) {
202        names.add(sd.getType());
203      }
204    }
205    return names;
206  }
207    
208  /**
209   * @return a list of all structure definitions, with snapshots generated (if possible)
210   */
211  public List<StructureDefinition> allStructures(){
212    List<StructureDefinition> result = new ArrayList<StructureDefinition>();
213    Set<StructureDefinition> set = new HashSet<StructureDefinition>();
214    for (StructureDefinition sd : getStructures()) {
215      if (!set.contains(sd)) {
216        try {
217          generateSnapshot(sd);
218          // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
219        } catch (Exception e) {
220          if (!isSuppressDebugMessages()) {
221            System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
222            if (context.getLogger().isDebugLogging()) {
223              e.printStackTrace();
224            }
225          }
226        }
227        result.add(sd);
228        set.add(sd);
229      }
230    }
231    return result;
232  }
233  
234  /**
235   * @return a list of all structure definitions, without trying to generate snapshots
236   */
237  public List<StructureDefinition> getStructures() {
238    return context.fetchResourcesByType(StructureDefinition.class);
239  }
240    
241  /**
242   * Given a structure definition, generate a snapshot (or regenerate it)
243   * @param p
244   * @throws DefinitionException
245   * @throws FHIRException
246   */
247  public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
248    generateSnapshot(p, false);
249  }
250  
251  public void generateSnapshot(StructureDefinition p, boolean ifLogical) {
252    if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p) ) && (ifLogical || p.getKind() != StructureDefinitionKind.LOGICAL)) {
253      if (!p.hasBaseDefinition())
254        throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
255      StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p);
256      if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
257        sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
258      }
259      if (sd == null) {
260        throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
261      }
262      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
263      List<String> errors = new ArrayList<String>();
264      ProfileUtilities pu = new ProfileUtilities(context, msgs, this);
265      pu.setAutoFixSliceNames(true);
266      pu.setThrowException(false);
267      if (xverManager == null) {
268        xverManager = new XVerExtensionManager(context);
269      }
270      pu.setXver(xverManager);
271      if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
272        pu.sortDifferential(sd, p, p.getUrl(), errors, true);
273      }
274      pu.setDebug(false);
275      for (String err : errors)
276        msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
277      pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName());
278      for (ValidationMessage msg : msgs) {
279        if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)
280          throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
281      }
282      if (!p.hasSnapshot())
283        throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
284      pu = null;
285    }
286    p.setGeneratedSnapshot(true);
287  }
288  
289
290  // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind.
291  private boolean isProfileNeedsRegenerate(StructureDefinition p) {
292    boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse");
293    if (needs) {
294      p.setUserData("hack.regnerated", "yes");
295    }
296    return needs;
297  }
298
299  @Override
300  public boolean isPrimitiveType(String type) {
301    StructureDefinition sd = context.fetchTypeDefinition(type);
302    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
303  }
304
305  @Override
306  public boolean isDatatype(String type) {
307    StructureDefinition sd = context.fetchTypeDefinition(type);
308    return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
309  }
310
311  @Override
312  public boolean isResource(String t) {
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 boolean isPrimitiveDatatype(String type) {
356    StructureDefinition sd = context.fetchTypeDefinition(type);
357    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
358  }
359
360  public StructureDefinition fetchByJsonName(String key) {
361    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
362      ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
363      if (sd.getKind() == StructureDefinitionKind.LOGICAL && ed != null && ed.hasExtension(ToolingExtensions.EXT_JSON_NAME) && 
364          key.equals(ToolingExtensions.readStringExtension(ed, ToolingExtensions.EXT_JSON_NAME))) {
365        return sd;
366      }
367    }
368    return null;
369  }
370
371}
372