001package org.hl7.fhir.validation;
002
003import java.io.ByteArrayInputStream;
004import java.io.ByteArrayOutputStream;
005import java.io.FileNotFoundException;
006import java.io.FileOutputStream;
007import java.io.IOException;
008import java.io.InputStream;
009import java.io.OutputStream;
010import java.io.PrintWriter;
011import java.net.URISyntaxException;
012import java.util.ArrayList;
013import java.util.Arrays;
014import java.util.Date;
015import java.util.HashMap;
016import java.util.List;
017import java.util.Locale;
018import java.util.Map;
019import java.util.UUID;
020
021import org.fhir.ucum.UcumEssenceService;
022import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
023import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
024import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
025import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
026import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
027import org.hl7.fhir.exceptions.DefinitionException;
028import org.hl7.fhir.exceptions.FHIRException;
029import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
030import org.hl7.fhir.r5.context.ContextUtilities;
031import org.hl7.fhir.r5.context.IWorkerContext;
032import org.hl7.fhir.r5.context.IWorkerContextManager;
033import org.hl7.fhir.r5.context.SimpleWorkerContext;
034import org.hl7.fhir.r5.context.SystemOutLoggingService;
035import org.hl7.fhir.r5.elementmodel.Element;
036import org.hl7.fhir.r5.elementmodel.Manager;
037import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
038import org.hl7.fhir.r5.elementmodel.ObjectConverter;
039import org.hl7.fhir.r5.elementmodel.ParserBase;
040import org.hl7.fhir.r5.elementmodel.SHCParser;
041import org.hl7.fhir.r5.formats.FormatUtilities;
042import org.hl7.fhir.r5.formats.IParser.OutputStyle;
043import org.hl7.fhir.r5.formats.JsonParser;
044import org.hl7.fhir.r5.formats.XmlParser;
045import org.hl7.fhir.r5.model.Base;
046import org.hl7.fhir.r5.model.Bundle;
047import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
048import org.hl7.fhir.r5.model.CanonicalResource;
049import org.hl7.fhir.r5.model.Coding;
050import org.hl7.fhir.r5.model.DomainResource;
051import org.hl7.fhir.r5.model.ElementDefinition;
052import org.hl7.fhir.r5.model.ExpressionNode;
053import org.hl7.fhir.r5.model.ImplementationGuide;
054import org.hl7.fhir.r5.model.OperationOutcome;
055import org.hl7.fhir.r5.model.PackageInformation;
056import org.hl7.fhir.r5.model.Parameters;
057import org.hl7.fhir.r5.model.Resource;
058import org.hl7.fhir.r5.model.StructureDefinition;
059import org.hl7.fhir.r5.model.StructureMap;
060import org.hl7.fhir.r5.model.ValueSet;
061import org.hl7.fhir.r5.renderers.RendererFactory;
062import org.hl7.fhir.r5.renderers.utils.RenderingContext;
063import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
064import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
065import org.hl7.fhir.r5.utils.EOperationOutcome;
066import org.hl7.fhir.r5.utils.FHIRPathEngine;
067import org.hl7.fhir.r5.utils.ToolingExtensions;
068import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
069import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
070import org.hl7.fhir.r5.utils.validation.IResourceValidator;
071import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
072import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
073import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
074import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
075import org.hl7.fhir.r5.utils.validation.constants.CheckDisplayOption;
076import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
077import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
078import org.hl7.fhir.r5.utils.validation.constants.IdStatus;
079import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
080import org.hl7.fhir.utilities.FhirPublication;
081import org.hl7.fhir.utilities.IniFile;
082import org.hl7.fhir.utilities.SIDUtilities;
083import org.hl7.fhir.utilities.SimpleHTTPClient;
084import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult;
085import org.hl7.fhir.utilities.TextFile;
086import org.hl7.fhir.utilities.TimeTracker;
087import org.hl7.fhir.utilities.Utilities;
088import org.hl7.fhir.utilities.VersionUtilities;
089import org.hl7.fhir.utilities.npm.CommonPackages;
090import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
091import org.hl7.fhir.utilities.npm.NpmPackage;
092import org.hl7.fhir.utilities.npm.ToolsVersion;
093import org.hl7.fhir.utilities.validation.ValidationMessage;
094import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
095import org.hl7.fhir.validation.BaseValidator.ValidationControl;
096import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
097import org.hl7.fhir.validation.cli.services.IPackageInstaller;
098import org.hl7.fhir.validation.cli.utils.ProfileLoader;
099import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
100import org.hl7.fhir.validation.cli.utils.SchemaValidator;
101import org.hl7.fhir.validation.cli.utils.ValidationLevel;
102import org.hl7.fhir.validation.instance.InstanceValidator;
103import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
104import org.xml.sax.SAXException;
105
106import lombok.Getter;
107import lombok.Setter;
108import lombok.With;
109import lombok.experimental.Accessors;
110
111/*
112Copyright (c) 2011+, HL7, Inc
113All rights reserved.
114
115Redistribution and use in source and binary forms, with or without modification,
116are permitted provided that the following conditions are met:
117
118 * Redistributions of source code must retain the above copyright notice, this
119   list of conditions and the following disclaimer.
120 * Redistributions in binary form must reproduce the above copyright notice,
121   this list of conditions and the following disclaimer in the documentation
122   and/or other materials provided with the distribution.
123 * Neither the name of HL7 nor the names of its contributors may be used to
124   endorse or promote products derived from this software without specific
125   prior written permission.
126
127THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
128ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
129WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
130IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
131INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
132NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
133PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
134WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
135ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
136POSSIBILITY OF SUCH DAMAGE.
137
138*/
139
140/**
141 * This is just a wrapper around the InstanceValidator class for convenient use
142 * <p>
143 * The following resource formats are supported: XML, JSON, Turtle
144 * The following versions are supported: 1.0.2, 1.4.0, 3.0.2, 4.0.1, and current
145 * <p>
146 * Note: the validation engine is intended to be threadsafe
147 * To Use:
148 * <p>
149 * 1/ Initialize
150 * ValidationEngine validator = new ValidationEngine(src);
151 * - this must be the packageId of the relevant core specification
152 * for the version you want to validate against (e.g. hl7.fhir.r4.core)
153 * <p>
154 * validator.connectToTSServer(txServer);
155 * - this is optional; in the absence of a terminology service, snomed, loinc etc will not be validated
156 * <p>
157 * validator.loadIg(src);
158 * - call this any number of times for the Implementation Guide(s) of interest.
159 * - See https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator for documentation about the src parameter (-ig parameter)
160 * <p>
161 * validator.loadQuestionnaire(src)
162 * - url or filename of a questionnaire to load. Any loaded questionnaires will be used while validating
163 * <p>
164 * validator.setNative(doNative);
165 * - whether to do xml/json/rdf schema validation as well
166 * <p>
167 * You only need to do this initialization once. You can validate as many times as you like
168 * <p>
169 * 2. validate
170 * validator.validate(src, profiles);
171 * - source (as stream, byte[]), or url or filename of a resource to validate.
172 * Also validate against any profiles (as canonical URLS, equivalent to listing them in Resource.meta.profile)
173 * <p>
174 * if the source is provided as byte[] or stream, you need to provide a format too, though you can
175 * leave that as null, and the validator will guess
176 * <p>
177 * 3. Or, instead of validating, transform (see documentation and use in Validator.java)
178 *
179 * @author Grahame Grieve
180 */
181@Accessors(chain = true)
182public class ValidationEngine implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IPackageInstaller, IWorkerContextManager.IPackageLoadingTracker {
183
184  @Getter @Setter private SimpleWorkerContext context;
185  @Getter @Setter private Map<String, byte[]> binaries = new HashMap<>();
186  @Getter @Setter private boolean doNative;
187  @Getter @Setter private boolean noInvariantChecks;
188  @Getter @Setter private boolean wantInvariantInMessage;
189  @Getter @Setter private boolean hintAboutNonMustSupport;
190  @Getter @Setter private boolean anyExtensionsAllowed = false;
191  @Getter @Setter private String version;
192  @Getter @Setter private String language;
193  @Setter private FilesystemPackageCacheManager pcm;
194  @Getter @Setter private PrintWriter mapLog;
195  @Getter @Setter private boolean debug = false;
196  @Getter @Setter private IValidatorResourceFetcher fetcher;
197  @Getter @Setter private IValidationPolicyAdvisor policyAdvisor;
198  @Getter @Setter private IWorkerContextManager.ICanonicalResourceLocator locator;
199  @Getter @Setter private boolean assumeValidRestReferences;
200  @Getter @Setter private boolean noExtensibleBindingMessages;
201  @Getter @Setter private boolean noUnicodeBiDiControlChars;
202  @Getter @Setter private boolean securityChecks;
203  @Getter @Setter private boolean crumbTrails;
204  @Getter @Setter private boolean forPublication;
205  @Getter @Setter private boolean allowExampleUrls;
206  @Getter @Setter private boolean showMessagesFromReferences;
207  @Getter @Setter private boolean doImplicitFHIRPathStringConversion;
208  @Getter @Setter private HtmlInMarkdownCheck htmlInMarkdownCheck;
209  @Getter @Setter private Locale locale;
210  @Getter @Setter private List<ImplementationGuide> igs = new ArrayList<>();
211  @Getter @Setter private List<String> extensionDomains = new ArrayList<>();
212
213  @Getter @Setter private boolean showTimes;
214  @Getter @Setter private List<BundleValidationRule> bundleValidationRules = new ArrayList<>();
215  @Getter @Setter private QuestionnaireMode questionnaireMode;
216  @Getter @Setter private ValidationLevel level = ValidationLevel.HINTS;
217  @Getter @Setter private FHIRPathEngine fhirPathEngine;
218  @Getter @Setter private IgLoader igLoader;
219  @Getter @Setter private Coding jurisdiction;
220
221
222  /**
223   * Creating a validation engine is an expensive operation - takes seconds. 
224   * Once you have a validation engine created, you can quickly clone it to 
225   * get one that can load packages without affecting other uses
226   * 
227   * @param other
228   * @throws FHIRException
229   * @throws IOException
230   */
231  public ValidationEngine(ValidationEngine other) throws FHIRException, IOException {
232    super();
233    context = new SimpleWorkerContext(other.context);
234    binaries.putAll(other.binaries);
235    doNative = other.doNative;
236    noInvariantChecks = other.noInvariantChecks;
237    wantInvariantInMessage = other.wantInvariantInMessage;
238    hintAboutNonMustSupport = other.hintAboutNonMustSupport;
239    anyExtensionsAllowed = other.anyExtensionsAllowed;
240    version = other.version;
241    language = other.language;
242    pcm = other.pcm;
243    mapLog = other.mapLog;
244    debug = other.debug;
245    fetcher = other.fetcher;
246    policyAdvisor = other.policyAdvisor;
247    locator = other.locator;
248    assumeValidRestReferences = other.assumeValidRestReferences;
249    noExtensibleBindingMessages = other.noExtensibleBindingMessages;
250    noUnicodeBiDiControlChars = other.noUnicodeBiDiControlChars;
251    securityChecks = other.securityChecks;
252    crumbTrails = other.crumbTrails;
253    forPublication = other.forPublication;
254    allowExampleUrls = other.allowExampleUrls;
255    showMessagesFromReferences = other.showMessagesFromReferences;
256    doImplicitFHIRPathStringConversion = other.doImplicitFHIRPathStringConversion;
257    htmlInMarkdownCheck = other.htmlInMarkdownCheck;
258    locale = other.locale;
259    igs.addAll(other.igs);
260    extensionDomains.addAll(other.extensionDomains);
261    showTimes = other.showTimes;
262    bundleValidationRules.addAll(other.bundleValidationRules);
263    questionnaireMode = other.questionnaireMode;
264    level = other.level;
265    fhirPathEngine = other.fhirPathEngine;
266    igLoader = other.igLoader;
267    jurisdiction = other.jurisdiction;
268  }
269  
270  /**
271   * Systems that host the ValidationEngine can use this to control what validation the validator performs.
272   * <p>
273   * Using this, you can turn particular kinds of validation on and off. In addition, you can override
274   * the error | warning | hint level and make it a different level.
275   * <p>
276   * Each entry has
277   * * 'allowed': a boolean flag. if this is false, the Validator will not report the error.
278   * * 'level' : set to error, warning, information
279   * <p>
280   * Entries are registered by ID, using the IDs in /org.hl7.fhir.utilities/src/main/resources/Messages.properties
281   * <p>
282   * This feature is not supported by the validator CLI - and won't be. It's for systems hosting
283   * the validation framework in their own implementation context
284   */
285  @Getter @Setter private Map<String, ValidationControl> validationControl = new HashMap<>();
286  private Map<String, Boolean> resolvedUrls = new HashMap<>();
287
288  private ValidationEngine()  {
289
290  }
291
292  public static class ValidationEngineBuilder {
293
294    @With
295    private final String terminologyCachePath;
296
297    @With
298    private final String userAgent;
299
300    @With
301    private final String version;
302
303    //All three of these may be required to instantiate a txServer
304    private final String txServer;
305    private final String txLog;
306    private final FhirPublication txVersion;
307
308    @With
309    private final TimeTracker timeTracker;
310
311    @With
312    private final boolean canRunWithoutTerminologyServer;
313
314    @With
315    private final IWorkerContext.ILoggingService loggingService;
316
317    @With
318    private boolean THO = true;
319
320
321    public ValidationEngineBuilder() {
322      terminologyCachePath = null;
323      userAgent = null;
324      version = null;
325      txServer = null;
326      txLog = null;
327      txVersion = null;
328      timeTracker = null;
329      canRunWithoutTerminologyServer = false;
330      loggingService = new SystemOutLoggingService();
331    }
332
333    public ValidationEngineBuilder(String terminologyCachePath, String userAgent, String version, String txServer, String txLog, FhirPublication txVersion, TimeTracker timeTracker, boolean canRunWithoutTerminologyServer, IWorkerContext.ILoggingService loggingService, boolean THO) {
334      this.terminologyCachePath = terminologyCachePath;
335      this.userAgent = userAgent;
336      this.version = version;
337      this.txServer = txServer;
338      this.txLog = txLog;
339      this.txVersion = txVersion;
340      this.timeTracker = timeTracker;
341      this.canRunWithoutTerminologyServer = canRunWithoutTerminologyServer;
342      this.loggingService = loggingService;
343      this.THO = THO;
344    }
345
346    public ValidationEngineBuilder withTxServer(String txServer, String txLog, FhirPublication txVersion) {
347      return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, txServer, txLog, txVersion, timeTracker, canRunWithoutTerminologyServer, loggingService, THO);
348    }
349
350    public ValidationEngine fromNothing() throws IOException {
351      ValidationEngine engine = new ValidationEngine();
352      SimpleWorkerContext.SimpleWorkerContextBuilder contextBuilder = new SimpleWorkerContext.SimpleWorkerContextBuilder().withLoggingService(loggingService);
353      if (terminologyCachePath != null)
354        contextBuilder = contextBuilder.withTerminologyCachePath(terminologyCachePath);
355      engine.setContext(contextBuilder.build());
356      engine.initContext(timeTracker);
357      engine.setIgLoader(new IgLoader(engine.getPcm(), engine.getContext(), engine.getVersion(), engine.isDebug()));
358      loadTx(engine);
359      return engine;
360    }
361
362    public ValidationEngine fromSource(String src) throws IOException, URISyntaxException {
363      ValidationEngine engine = new ValidationEngine();
364      engine.loadCoreDefinitions(src, false, terminologyCachePath, userAgent, timeTracker, loggingService);
365      engine.getContext().setCanRunWithoutTerminology(canRunWithoutTerminologyServer);
366      engine.getContext().setPackageTracker(engine);    
367      if (txServer != null) {
368        engine.setTerminologyServer(txServer, txLog, txVersion);
369      }
370      engine.setVersion(version);
371      engine.setIgLoader(new IgLoader(engine.getPcm(), engine.getContext(), engine.getVersion(), engine.isDebug()));
372      if (THO) {
373        loadTx(engine);
374      }
375      return engine;
376    }
377
378    private void loadTx(ValidationEngine engine) throws FHIRException, IOException {
379      String pid = null;
380      if (VersionUtilities.isR3Ver(version)) {
381        pid =  "hl7.terminology.r3";
382      }
383      if (VersionUtilities.isR4Ver(version)) {
384        pid =  "hl7.terminology.r4";
385      }
386      if (VersionUtilities.isR4BVer(version)) {
387        pid =  "hl7.terminology.r4";
388      }
389      if (VersionUtilities.isR5Ver(version)) {
390        pid =  "hl7.terminology.r5";
391      }
392      if (pid != null) {
393        engine.loadPackage(pid, null);
394      }
395    }
396  }
397
398  /**
399   *
400   * @param src
401   * @param recursive
402   * @param terminologyCachePath
403   * @param userAgent
404   * @param tt
405   * @param loggingService
406   * @throws FHIRException
407   * @throws IOException
408   *
409   * @see IgLoader#loadIgSource(String, boolean, boolean) loadIgSource for detailed description of the src parameter
410   */
411  private void loadCoreDefinitions(String src, boolean recursive, String terminologyCachePath, String userAgent, TimeTracker tt, IWorkerContext.ILoggingService loggingService) throws FHIRException, IOException {
412    NpmPackage npm = getPcm().loadPackage(src, null);
413    if (npm != null) {
414      version = npm.fhirVersion();
415      SimpleWorkerContext.SimpleWorkerContextBuilder contextBuilder = new SimpleWorkerContext.SimpleWorkerContextBuilder().withLoggingService(loggingService);
416      if (terminologyCachePath != null)
417        contextBuilder = contextBuilder.withTerminologyCachePath(terminologyCachePath);
418      if (userAgent != null) {
419        contextBuilder.withUserAgent(userAgent);
420      }
421      context = contextBuilder.fromPackage(npm, ValidatorUtils.loaderForVersion(version));
422    } else {
423      Map<String, byte[]> source = igLoader.loadIgSource(src, recursive, true);
424      if (version == null) {
425        version = getVersionFromPack(source);
426      }
427      SimpleWorkerContext.SimpleWorkerContextBuilder contextBuilder = new SimpleWorkerContext.SimpleWorkerContextBuilder();
428      if (terminologyCachePath != null)
429        contextBuilder = contextBuilder.withTerminologyCachePath(terminologyCachePath);
430      if (userAgent != null) {
431        contextBuilder.withUserAgent(userAgent);
432      }
433      context = contextBuilder.fromDefinitions(source, ValidatorUtils.loaderForVersion(version), new PackageInformation(src, new Date()));
434      ValidatorUtils.grabNatives(getBinaries(), source, "http://hl7.org/fhir");
435    }
436    // ucum-essence.xml should be in the class path. if it's not, ask about how to sort this out 
437    // on https://chat.fhir.org/#narrow/stream/179167-hapi
438    try {
439      ClassLoader classLoader = ValidationEngine.class.getClassLoader();
440      InputStream ue = classLoader.getResourceAsStream("ucum-essence.xml");
441      context.setUcumService(new UcumEssenceService(ue));
442    } catch (Exception e) {
443      throw new FHIRException("Error loading UCUM from embedded ucum-essence.xml: "+e.getMessage(), e);
444    }
445    initContext(tt);
446  }
447
448  protected void initContext(TimeTracker tt) throws IOException {
449    context.setCanNoTS(true);
450    context.setCacheId(UUID.randomUUID().toString());
451    context.setAllowLoadingDuplicates(true); // because of Forge
452    context.setExpansionProfile(makeExpProfile());
453    if (tt != null) {
454      context.setClock(tt);
455    }
456    NpmPackage npmX = getPcm().loadPackage(CommonPackages.ID_XVER, CommonPackages.VER_XVER);
457    context.loadFromPackage(npmX, null);
458
459    this.fhirPathEngine = new FHIRPathEngine(context);
460  }
461
462  private String getVersionFromPack(Map<String, byte[]> source) {
463    if (source.containsKey("version.info")) {
464      IniFile vi = new IniFile(new ByteArrayInputStream(removeBom(source.get("version.info"))));
465      return vi.getStringProperty("FHIR", "version");
466    } else {
467      throw new Error("Missing version.info?");
468    }
469  }
470
471  private byte[] removeBom(byte[] bs) {
472    if (bs.length > 3 && bs[0] == -17 && bs[1] == -69 && bs[2] == -65)
473      return Arrays.copyOfRange(bs, 3, bs.length);
474    else
475      return bs;
476  }
477
478  private Parameters makeExpProfile() {
479    Parameters ep = new Parameters();
480    ep.addParameter("profile-url", "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"); // change this to blow the cache
481    // all defaults....
482    return ep;
483  }
484
485  public String connectToTSServer(String url, String log, FhirPublication version) throws URISyntaxException, IOException, FHIRException {
486    return connectToTSServer(url, log, null, version);
487  }
488
489  public String connectToTSServer(String url, String log, String txCachePath, FhirPublication version) throws URISyntaxException, IOException, FHIRException {
490    context.setTlogging(false);
491    if (url == null) {
492      context.setCanRunWithoutTerminology(true);
493      context.setNoTerminologyServer(true);
494      return "n/a: No Terminology Server";
495    } else {
496      try {
497        return context.connectToTSServer(TerminologyClientFactory.makeClient(url, context.getUserAgent(), version), log);
498      } catch (Exception e) {
499        if (context.isCanRunWithoutTerminology()) {
500          return "n/a: Running without Terminology Server (error: " + e.getMessage() + ")";
501        } else
502          throw e;
503      }
504    }
505  }
506
507  public void loadProfile(String src) throws FHIRException, IOException {
508    if (context.hasResource(StructureDefinition.class, src))
509      return;
510    if (context.hasResource(ImplementationGuide.class, src))
511      return;
512
513    byte[] source = ProfileLoader.loadProfileSource(src);
514    FhirFormat fmt = FormatUtilities.determineFormat(source);
515    Resource r = FormatUtilities.makeParser(fmt).parse(source);
516    context.cacheResource(r);
517  }
518
519  // testing entry point
520  public OperationOutcome validate(FhirFormat format, InputStream stream, List<String> profiles) throws FHIRException, IOException, EOperationOutcome {
521    List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
522    InstanceValidator validator = getValidator(format);
523    validator.validate(null, messages, stream, format, asSdList(profiles));
524    return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine);
525  }
526
527  public List<StructureDefinition> asSdList(List<String> profiles) throws Error {
528    List<StructureDefinition> list = new ArrayList<>();
529    if (profiles != null) {
530      for (String p : profiles) {
531        StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
532        if (sd == null) {
533          throw new Error("Unable to resolve profile " + p);
534        }
535        list.add(sd);
536      }
537    }
538    return list;
539  }
540
541  public OperationOutcome validate(String source, List<String> profiles) throws FHIRException, IOException {
542    List<String> l = new ArrayList<String>();
543    l.add(source);
544    return (OperationOutcome) validate(l, profiles, null);
545  }
546
547  public Resource validate(List<String> sources, List<String> profiles, List<ValidationRecord> record) throws FHIRException, IOException {
548    if (profiles.size() > 0) {
549      System.out.println("  Profiles: " + profiles);
550    }
551    List<String> refs = new ArrayList<String>();
552    boolean asBundle = ValidatorUtils.parseSources(sources, refs, context);
553    Bundle results = new Bundle();
554    results.setType(Bundle.BundleType.COLLECTION);
555    for (String ref : refs) {
556      TimeTracker.Session tts = context.clock().start("validation");
557      context.clock().milestone();
558      System.out.println("  Validate " + ref);
559      Content cnt = igLoader.loadContent(ref, "validate", false);
560      try {
561        OperationOutcome outcome = validate(ref, cnt.focus, cnt.cntType, profiles, record);
562        ToolingExtensions.addStringExtension(outcome, ToolingExtensions.EXT_OO_FILE, ref);
563        System.out.println(" " + context.clock().milestone());
564        results.addEntry().setResource(outcome);
565        tts.end();
566      } catch (Exception e) {
567        System.out.println("Validation Infrastructure fail validating " + ref + ": " + e.getMessage());
568        tts.end();
569        throw new FHIRException(e);
570      }
571    }
572    if (asBundle)
573      return results;
574    else
575      return results.getEntryFirstRep().getResource();
576  }
577
578  public OperationOutcome validate(byte[] source, FhirFormat cntType, List<String> profiles, List<ValidationMessage> messages) throws FHIRException, IOException, EOperationOutcome {
579    InstanceValidator validator = getValidator(cntType);
580
581    validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles));
582    return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine);
583  }
584
585  public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles, List<ValidationRecord> record) throws FHIRException, IOException, EOperationOutcome, SAXException {
586    List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
587    if (doNative) {
588      SchemaValidator.validateSchema(location, cntType, messages);
589    }
590    InstanceValidator validator = getValidator(cntType);
591    validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles));
592    if (showTimes) {
593      System.out.println(location + ": " + validator.reportTimes());
594    }
595    if (record != null) {
596      record.add(new ValidationRecord(location, messages));
597    }
598    return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine);
599  }
600
601  public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles, IdStatus resourceIdRule, boolean anyExtensionsAllowed, BestPracticeWarningLevel bpWarnings, CheckDisplayOption displayOption) throws FHIRException, IOException, EOperationOutcome, SAXException {
602    List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
603    if (doNative) {
604      SchemaValidator.validateSchema(location, cntType, messages);
605    }
606    InstanceValidator validator = getValidator(cntType);
607    validator.setResourceIdRule(resourceIdRule);
608    validator.setBestPracticeWarningLevel(bpWarnings);
609    validator.setCheckDisplay(displayOption);
610    validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles));
611    return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine);
612  }
613
614  public org.hl7.fhir.r5.elementmodel.Element transform(String source, String map) throws FHIRException, IOException {
615    Content cnt = igLoader.loadContent(source, "validate", false);
616    return transform(cnt.focus, cnt.cntType, map);
617  }
618
619  public StructureMap compile(String mapUri) throws FHIRException, IOException {
620    StructureMap map = context.fetchResource(StructureMap.class, mapUri);
621    return map;
622  }
623
624  public org.hl7.fhir.r5.elementmodel.Element transform(byte[] source, FhirFormat cntType, String mapUri) throws FHIRException, IOException {
625    List<Base> outputs = new ArrayList<>();
626    StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs, mapLog, context));
627    StructureMap map = context.fetchResource(StructureMap.class, mapUri);
628    if (map == null) throw new Error("Unable to find map " + mapUri + " (Known Maps = " + context.listMapUrls() + ")");
629    org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map);
630    StructureDefinition sourceSD = getSourceResourceFromStructureMap(map);
631    ParserBase parser = Manager.makeParser(context, cntType);
632    if (sourceSD.getKind() == StructureDefinition.StructureDefinitionKind.LOGICAL) {
633      parser.setLogical(sourceSD);
634    }
635    org.hl7.fhir.r5.elementmodel.Element src = parser.parseSingle(new ByteArrayInputStream(source));    
636    scu.transform(null, src, map, resource);
637    resource.populatePaths(null);
638    return resource;
639  }
640
641  private org.hl7.fhir.r5.elementmodel.Element getTargetResourceFromStructureMap(StructureMap map) {
642    String targetTypeUrl = null;
643    for (StructureMap.StructureMapStructureComponent component : map.getStructure()) {
644      if (component.getMode() == StructureMap.StructureMapModelMode.TARGET) {
645        targetTypeUrl = component.getUrl();
646        break;
647      }
648    }
649
650    if (targetTypeUrl == null) throw new FHIRException("Unable to determine resource URL for target type");
651
652    StructureDefinition structureDefinition = null;
653    for (StructureDefinition sd : this.context.fetchResourcesByType(StructureDefinition.class)) {
654      if (sd.getUrl().equalsIgnoreCase(targetTypeUrl)) {
655        structureDefinition = sd;
656        break;
657      }
658    }
659
660    if (structureDefinition == null) throw new FHIRException("Unable to find StructureDefinition for target type ('" + targetTypeUrl + "')");
661
662    return Manager.build(getContext(), structureDefinition);
663  }
664  
665  private StructureDefinition getSourceResourceFromStructureMap(StructureMap map) {
666        StructureMap.StructureMapGroupComponent g = map.getGroup().get(0);
667        String type = null;
668        for (StructureMap.StructureMapGroupInputComponent inp : g.getInput()) {
669          if (inp.getMode() == StructureMap.StructureMapInputMode.SOURCE)
670            if (type != null)
671              throw new DefinitionException("This engine does not support multiple source inputs");
672            else
673              type = inp.getType();
674        }         
675          
676        String sourceTypeUrl = null;
677        for (StructureMap.StructureMapStructureComponent component : map.getStructure()) {
678          if (component.getMode() == StructureMap.StructureMapModelMode.SOURCE
679              && component.getAlias().equalsIgnoreCase(type)) {
680            sourceTypeUrl = component.getUrl();
681            break;
682          }
683        }
684            
685        StructureDefinition structureDefinition = null;
686        for (StructureDefinition sd : this.context.fetchResourcesByType(StructureDefinition.class)) {
687          if (sd.getUrl().equalsIgnoreCase(sourceTypeUrl)) {
688            structureDefinition = sd;
689                break;
690          }
691        }
692
693        if (structureDefinition == null) throw new FHIRException("Unable to find StructureDefinition for source type ('" + sourceTypeUrl + "')");
694        return structureDefinition;
695  }
696
697
698  public Resource generate(String source, String version) throws FHIRException, IOException, EOperationOutcome {
699    Content cnt = igLoader.loadContent(source, "validate", false);
700    Resource res = igLoader.loadResourceByVersion(version, cnt.focus, source);
701    RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE);
702    genResource(res, rc);
703    return (Resource) res;
704  }
705
706  public void genResource(Resource res, RenderingContext rc) throws IOException, EOperationOutcome {
707    if (res instanceof Bundle) {
708      Bundle bnd = (Bundle) res;
709      for (BundleEntryComponent be : bnd.getEntry()) {
710        if (be.hasResource()) {
711          genResource(be.getResource(), rc);
712        }
713      }
714    } else {
715      RendererFactory.factory(res, rc).render((DomainResource) res);
716    }
717  }
718
719  public void convert(String source, String output) throws FHIRException, IOException {
720    Content cnt = igLoader.loadContent(source, "validate", false);
721    Element e = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
722    Manager.compose(context, e, new FileOutputStream(output), (output.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML), OutputStyle.PRETTY, null);
723  }
724
725  public String evaluateFhirPath(String source, String expression) throws FHIRException, IOException {
726    Content cnt = igLoader.loadContent(source, "validate", false);
727    FHIRPathEngine fpe = this.getValidator(null).getFHIRPathEngine();
728    Element e = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
729    ExpressionNode exp = fpe.parse(expression);
730    return fpe.evaluateToString(new ValidatorHostContext(context, e), e, e, e, exp);
731  }
732
733  public StructureDefinition snapshot(String source, String version) throws FHIRException, IOException {
734    Content cnt = igLoader.loadContent(source, "validate", false);
735    Resource res = igLoader.loadResourceByVersion(version, cnt.focus, Utilities.getFileNameForName(source));
736
737    if (!(res instanceof StructureDefinition))
738      throw new FHIRException("Require a StructureDefinition for generating a snapshot");
739    StructureDefinition sd = (StructureDefinition) res;
740    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
741
742    new ProfileUtilities(context, null, null).setAutoFixSliceNames(true).generateSnapshot(base, sd, sd.getUrl(), null, sd.getName());
743    return sd;
744  }
745
746  public CanonicalResource loadCanonicalResource(String source, String version) throws FHIRException, IOException {
747    Content cnt = igLoader.loadContent(source, "validate", false);
748    Resource res = igLoader.loadResourceByVersion(version, cnt.focus, Utilities.getFileNameForName(source));
749
750    if (!(res instanceof CanonicalResource))
751      throw new FHIRException("Require a CanonicalResource");
752    return (CanonicalResource) res;
753  }
754
755  public void seeResource(Resource r) throws FHIRException {
756    context.cacheResource(r);
757  }
758
759  public void dropResource(String type, String id) {
760    context.dropResource(type, id);
761  }
762
763  public InstanceValidator getValidator(FhirFormat format) throws FHIRException, IOException {
764    InstanceValidator validator = new InstanceValidator(context, null, null);
765    validator.setHintAboutNonMustSupport(hintAboutNonMustSupport);
766    validator.setAnyExtensionsAllowed(anyExtensionsAllowed);
767    validator.getExtensionDomains().clear();
768    validator.getExtensionDomains().addAll(extensionDomains);
769    validator.setNoInvariantChecks(isNoInvariantChecks());
770    validator.setWantInvariantInMessage(isWantInvariantInMessage());
771    validator.setValidationLanguage(language);
772    if (language != null) {
773      validator.getContext().setValidationMessageLanguage(Locale.forLanguageTag(language));
774    }
775    validator.setAssumeValidRestReferences(assumeValidRestReferences);
776    validator.setNoExtensibleWarnings(noExtensibleBindingMessages);
777    validator.setSecurityChecks(securityChecks);
778    validator.setCrumbTrails(crumbTrails);
779    validator.setForPublication(forPublication);
780    validator.setAllowExamples(allowExampleUrls);
781    validator.setShowMessagesFromReferences(showMessagesFromReferences);
782    validator.getContext().setLocale(locale);
783    validator.setFetcher(this);
784    validator.getImplementationGuides().addAll(igs);
785    validator.getBundleValidationRules().addAll(bundleValidationRules);
786    validator.getValidationControl().putAll(validationControl);
787    validator.setQuestionnaireMode(questionnaireMode);
788    validator.setLevel(level);
789    validator.setHtmlInMarkdownCheck(htmlInMarkdownCheck);
790    validator.setNoUnicodeBiDiControlChars(noUnicodeBiDiControlChars);
791    validator.setDoImplicitFHIRPathStringConversion(doImplicitFHIRPathStringConversion);
792    if (format == FhirFormat.SHC) {
793      igLoader.loadIg(getIgs(), getBinaries(), SHCParser.CURRENT_PACKAGE, true);      
794    }
795    validator.setJurisdiction(jurisdiction);
796    validator.setLogProgress(true);
797    return validator;
798  }
799
800  public void prepare() {
801    for (StructureDefinition sd : new ContextUtilities(context).allStructures()) {
802      try {
803        makeSnapshot(sd);
804      } catch (Exception e) {
805        System.out.println("Process Note: Unable to generate snapshot for " + sd.present() + ": " + e.getMessage());
806        if (context.getLogger().isDebugLogging()) {
807          e.printStackTrace();
808        }
809      }
810    }
811  }
812
813  private void makeSnapshot(StructureDefinition sd) throws DefinitionException, FHIRException {
814    if (sd.hasSnapshot())
815      return;
816    StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
817    if (sdb != null) {
818      makeSnapshot(sdb);
819      new ProfileUtilities(context, null, null).setAutoFixSliceNames(true).generateSnapshot(sdb, sd, sd.getUrl(), null, sd.getName());
820    }
821  }
822
823  public void handleOutput(Resource r, String output, String version) throws FHIRException, IOException {
824    if (output.startsWith("http://")) {
825      ByteArrayOutputStream bs = new ByteArrayOutputStream();
826      handleOutputToStream(r, output, bs, version);
827      SimpleHTTPClient http = new SimpleHTTPClient();
828      HTTPResult res = http.post(output, "application/fhir+xml", bs.toByteArray(), "application/fhir+xml");
829      res.checkThrowException();
830    } else {
831      FileOutputStream s = new FileOutputStream(output);
832      handleOutputToStream(r, output, s, version);
833    }
834  }
835
836  private void handleOutputToStream(Resource r, String fn, OutputStream s, String version) throws FHIRException, IOException {
837    if (fn.endsWith(".html") || fn.endsWith(".htm") && r instanceof DomainResource)
838      new XhtmlComposer(XhtmlComposer.HTML, true).compose(s, ((DomainResource) r).getText().getDiv());
839    else if (VersionUtilities.isR3Ver(version)) {
840      org.hl7.fhir.dstu3.model.Resource res = VersionConvertorFactory_30_50.convertResource(r);
841      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
842        new org.hl7.fhir.dstu3.formats.XmlParser().setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(s, res);
843      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
844        new org.hl7.fhir.dstu3.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(s, res);
845      else if (fn.endsWith(".txt") || fn.endsWith(".map"))
846        TextFile.stringToStream(org.hl7.fhir.dstu3.utils.StructureMapUtilities.render((org.hl7.fhir.dstu3.model.StructureMap) res), s, false);
847      else
848        throw new FHIRException("Unsupported format for " + fn);
849    } else if (VersionUtilities.isR4Ver(version)) {
850      org.hl7.fhir.r4.model.Resource res = VersionConvertorFactory_40_50.convertResource(r);
851      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
852        new org.hl7.fhir.r4.formats.XmlParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(s, res);
853      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
854        new org.hl7.fhir.r4.formats.JsonParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(s, res);
855      else if (fn.endsWith(".txt") || fn.endsWith(".map"))
856        TextFile.stringToStream(org.hl7.fhir.r4.utils.StructureMapUtilities.render((org.hl7.fhir.r4.model.StructureMap) res), s, false);
857      else
858        throw new FHIRException("Unsupported format for " + fn);
859    } else if (VersionUtilities.isR2BVer(version)) {
860      org.hl7.fhir.dstu2016may.model.Resource res = VersionConvertorFactory_14_50.convertResource(r);
861      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
862        new org.hl7.fhir.dstu2016may.formats.XmlParser().setOutputStyle(org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle.PRETTY).compose(s, res);
863      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
864        new org.hl7.fhir.dstu2016may.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle.PRETTY).compose(s, res);
865      else
866        throw new FHIRException("Unsupported format for " + fn);
867    } else if (VersionUtilities.isR2Ver(version)) {
868      org.hl7.fhir.dstu2.model.Resource res = VersionConvertorFactory_10_50.convertResource(r, new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5());
869      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
870        new org.hl7.fhir.dstu2.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(s, res);
871      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
872        new org.hl7.fhir.dstu2.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(s, res);
873      else
874        throw new FHIRException("Unsupported format for " + fn);
875    } else if (VersionUtilities.isR5Ver(version)) {
876      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
877        new XmlParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r);
878      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
879        new JsonParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r);
880      else if (fn.endsWith(".txt") || fn.endsWith(".map"))
881        TextFile.stringToStream(StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false);
882      else
883        throw new FHIRException("Unsupported format for " + fn);
884    } else
885      throw new FHIRException("Encountered unsupported configured version " + version + " loading " + fn);
886
887    s.close();
888  }
889
890  public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception {
891    Content cnt = igLoader.loadContent(source, "validate", false);
892    org.hl7.fhir.r5.elementmodel.Element src = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
893
894    // if the src has a url, we try to use the java code 
895    if ((canDoNative == null && src.hasChild("url")) || (canDoNative != null && canDoNative)) {
896      try {
897        if (VersionUtilities.isR2Ver(version)) {
898          return VersionConvertor.convertVersionNativeR2(targetVer, cnt, format);
899        } else if (VersionUtilities.isR2BVer(version)) {
900          return VersionConvertor.convertVersionNativeR2b(targetVer, cnt, format);
901        } else if (VersionUtilities.isR3Ver(version)) {
902          return VersionConvertor.convertVersionNativeR3(targetVer, cnt, format);
903        } else if (VersionUtilities.isR4Ver(version)) {
904          return VersionConvertor.convertVersionNativeR4(targetVer, cnt, format);
905        } else {
906          throw new FHIRException("Source version not supported yet: " + version);
907        }
908      } catch (Exception e) {
909        System.out.println("Conversion failed using Java convertor: " + e.getMessage());
910      }
911    }
912    // ok, we try converting using the structure maps
913    System.out.println("Loading hl7.fhir.xver.r4");
914    igLoader.loadIg(getIgs(), getBinaries(), "hl7.fhir.xver.r4", false);
915    String type = src.fhirType();
916    String url = getMapId(type, targetVer);
917    List<Base> outputs = new ArrayList<Base>();
918    StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs, mapLog, context));
919    StructureMap map = context.fetchResource(StructureMap.class, url);
920    if (map == null)
921      throw new Error("Unable to find map " + url + " (Known Maps = " + context.listMapUrls() + ")");
922    org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map);
923    scu.transform(null, src, map, resource);
924    ByteArrayOutputStream bs = new ByteArrayOutputStream();
925    Manager.compose(context, resource, bs, format, OutputStyle.PRETTY, null);
926    return bs.toByteArray();
927  }
928
929  private String getMapId(String type, String targetVer) {
930    if (VersionUtilities.isR2Ver(version)) {
931      if (VersionUtilities.isR3Ver(targetVer)) {
932        return "http://hl7.org/fhir/StructureMap/" + type + "2to3";
933      }
934    } else if (VersionUtilities.isR3Ver(version)) {
935      if (VersionUtilities.isR2Ver(targetVer)) {
936        return "http://hl7.org/fhir/StructureMap/" + type + "3to2";
937      } else if (VersionUtilities.isR4Ver(targetVer)) {
938        return "http://hl7.org/fhir/StructureMap/" + type + "3to4";
939      }
940    } else if (VersionUtilities.isR4Ver(version)) {
941      if (VersionUtilities.isR3Ver(targetVer)) {
942        return "http://hl7.org/fhir/StructureMap/" + type + "4to3";
943      }
944    }
945    throw new FHIRException("Source/Target version not supported: " + version + " -> " + targetVer);
946  }
947
948  public String setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException, IOException {
949    return connectToTSServer(src, log, version);
950  }
951
952  public ValidationEngine setMapLog(String mapLog) throws FileNotFoundException {
953    if (mapLog != null) {
954      this.mapLog = new PrintWriter(mapLog);
955    }
956    return this;
957  }
958
959  public ValidationEngine setSnomedExtension(String sct) {
960    getContext().getExpansionParameters().addParameter("system-version", "http://snomed.info/sct|http://snomed.info/sct/" + sct);
961    return this;
962  }
963
964  public FilesystemPackageCacheManager getPcm() throws IOException {
965    if (pcm == null) {
966      //System.out.println("Creating Package manager?");
967      pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
968    }
969    return pcm;
970  }
971
972  @Override
973  public byte[] fetchRaw(IResourceValidator validator, String source) throws IOException {
974    SimpleHTTPClient http = new SimpleHTTPClient();
975    HTTPResult res = http.get(source);
976    res.checkThrowException();
977    return res.getContent();
978  }
979
980  @Override
981  public boolean packageExists(String id, String ver) throws IOException, FHIRException {
982    return getPcm().packageExists(id, ver);
983  }
984
985  @Override
986  public void loadPackage(String id, String ver) throws IOException, FHIRException {
987    igLoader.loadIg(getIgs(), getBinaries(),id + (ver == null ? "" : "#" + ver), true);
988  }
989
990  @Override
991  public Element fetch(IResourceValidator validator, Object appContext, String url) throws FHIRException, IOException {
992    Resource resource = context.fetchResource(Resource.class, url);
993    if (resource != null) {
994      return new ObjectConverter(context).convert(resource);
995    }
996    if (fetcher != null) {
997      return fetcher.fetch(validator, appContext, url);
998    }
999    return null;
1000  }
1001
1002  @Override
1003  public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path, String url) {
1004    Resource resource = context.fetchResource(StructureDefinition.class, url);
1005    if (resource != null) {
1006      return ReferenceValidationPolicy.CHECK_VALID;
1007    }
1008    if (!(url.contains("hl7.org") || url.contains("fhir.org"))) {
1009      return ReferenceValidationPolicy.IGNORE;
1010    } else if (policyAdvisor != null) {
1011      return policyAdvisor.policyForReference(validator, appContext, path, url);
1012    } else {
1013      return ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE;
1014    }
1015  }
1016
1017  @Override
1018  public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator,
1019                                                      Object appContext,
1020                                                      String containerType,
1021                                                      String containerId,
1022                                                      Element.SpecialElement containingResourceType,
1023                                                      String path,
1024                                                      String url) {
1025    return ContainedReferenceValidationPolicy.CHECK_VALID;
1026  }
1027
1028  @Override
1029  public CodedContentValidationPolicy policyForCodedContent(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind, ValueSet valueSet, List<String> systems) {
1030    return CodedContentValidationPolicy.VALUESET;
1031  }
1032
1033  @Override
1034  public boolean resolveURL(IResourceValidator validator, Object appContext, String path, String url, String type) throws FHIRException {
1035    // some of this logic might take a while, and it's not going to change once loaded
1036    if (resolvedUrls .containsKey(type+"|"+url)) {
1037      return resolvedUrls.get(type+"|"+url);
1038    }
1039    if (!url.startsWith("http://") && !url.startsWith("https://")) { // ignore these
1040      resolvedUrls.put(type+"|"+url, true);
1041      return true;
1042    }
1043    if (context.fetchResource(Resource.class, url) != null) {
1044      resolvedUrls.put(type+"|"+url, true);
1045      return true;
1046    }
1047    if (SIDUtilities.isKnownSID(url) || 
1048        Utilities.existsInList(url, "http://hl7.org/fhir/w5", "http://hl7.org/fhir/fivews", "http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) {
1049      resolvedUrls.put(type+"|"+url, true);
1050      return true;
1051    }
1052    if (Utilities.existsInList(url, "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct")) {
1053      resolvedUrls.put(type+"|"+url, true);
1054      return true;
1055    }
1056    if (context.getNSUrlMap().containsKey(url)) {
1057      resolvedUrls.put(type+"|"+url, true);
1058      return true;
1059    }
1060    if (url.contains("example.org") || url.contains("acme.com")) {
1061      resolvedUrls.put(type+"|"+url, false);
1062      return false; // todo... how to access settings from here?
1063    }
1064    if (fetcher != null) {
1065      try {
1066        boolean ok = fetcher.resolveURL(validator, appContext, path, url, type);
1067        resolvedUrls.put(type+"|"+url, ok);
1068        return ok;
1069      } catch (Exception e) {
1070        e.printStackTrace();
1071        resolvedUrls.put(type+"|"+url, false);
1072        return false;
1073      }
1074    }
1075    resolvedUrls.put(type+"|"+url, false);
1076    return false;
1077  }
1078
1079
1080  @Override
1081  public CanonicalResource fetchCanonicalResource(IResourceValidator validator, String url) throws URISyntaxException {
1082    Resource res = context.fetchResource(Resource.class, url);
1083    if (res != null) {
1084      if (res instanceof CanonicalResource) {
1085        return (CanonicalResource) res;
1086      } else {
1087        return null;
1088      }
1089    }
1090    return fetcher != null ? fetcher.fetchCanonicalResource(validator, url) : null;
1091  }
1092
1093  @Override
1094  public boolean fetchesCanonicalResource(IResourceValidator validator, String url) {
1095    return fetcher != null && fetcher.fetchesCanonicalResource(validator, url);
1096  }
1097
1098  @Override
1099  public void packageLoaded(String pid, String version) {
1100    resolvedUrls.clear();
1101    
1102  }
1103
1104}