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