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