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