
001package org.hl7.fhir.r5.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033import org.hl7.fhir.exceptions.DefinitionException; 034import org.hl7.fhir.exceptions.FHIRException; 035import org.hl7.fhir.exceptions.FHIRFormatError; 036import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 037import org.hl7.fhir.r5.context.ContextUtilities; 038import org.hl7.fhir.r5.context.IWorkerContext; 039import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 040import org.hl7.fhir.r5.extensions.ExtensionUtilities; 041import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 042import org.hl7.fhir.r5.formats.FormatUtilities; 043import org.hl7.fhir.r5.formats.IParser.OutputStyle; 044import org.hl7.fhir.r5.model.StructureDefinition; 045import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 046import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 047import org.hl7.fhir.utilities.Utilities; 048import org.hl7.fhir.utilities.i18n.I18nConstants; 049import org.hl7.fhir.utilities.validation.IDigitalSignatureServices; 050import org.hl7.fhir.utilities.validation.ValidationMessage; 051import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 052import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 053import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 054 055import java.io.IOException; 056import java.io.InputStream; 057import java.io.OutputStream; 058import java.util.HashSet; 059import java.util.List; 060import java.util.Set; 061 062@MarkedToMoveToAdjunctPackage 063public abstract class ParserBase { 064 065 public enum IdRenderingPolicy { 066 All, None, RootOnly, NotRoot; 067 068 boolean forRoot() { 069 return this == All || this == RootOnly; 070 } 071 072 boolean forInner() { 073 return this == All || this == NotRoot; 074 } 075 } 076 077 public interface ILinkResolver { 078 String resolveType(String type); 079 String resolveProperty(Property property); 080 String resolvePage(String string); 081 String resolveReference(String referenceForElement); 082 } 083 084 public enum ValidationPolicy { NONE, QUICK, EVERYTHING } 085 086 public boolean isPrimitive(String code) { 087 return context.isPrimitiveType(code); 088 } 089 090 protected IWorkerContext context; 091 protected ValidationPolicy policy; 092 protected ILinkResolver linkResolver; 093 protected boolean showDecorations; 094 protected IdRenderingPolicy idPolicy = IdRenderingPolicy.All; 095 protected StructureDefinition logical; 096 protected IDigitalSignatureServices signatureServices; 097 private ProfileUtilities profileUtilities; 098 private ContextUtilities contextUtilities; 099 protected Set<String> canonicalFilter = new HashSet<>(); 100 101 public ParserBase(IWorkerContext context, ProfileUtilities utilities) { 102 super(); 103 this.context = context; 104 this.profileUtilities = utilities; 105 contextUtilities = new ContextUtilities(context); 106 policy = ValidationPolicy.NONE; 107 } 108 109 public ParserBase(IWorkerContext context) { 110 super(); 111 this.context = context; 112 this.profileUtilities = new ProfileUtilities(context, null, null, new FHIRPathEngine(context)); 113 contextUtilities = new ContextUtilities(context); 114 policy = ValidationPolicy.NONE; 115 } 116 117 public void setupValidation(ValidationPolicy policy) { 118 this.policy = policy; 119 } 120 121 public abstract List<ValidatedFragment> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException; 122 123 public Element parseSingle(InputStream stream, List<ValidationMessage> errors) throws IOException, FHIRFormatError, DefinitionException, FHIRException { 124 125 List<ValidatedFragment> res = parse(stream); 126 127 if (res.size() != 1) { 128 throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed"); 129 } 130 var resE = res.get(0); 131 if (resE.getElement() == null) { 132 throw new FHIRException("Parsing FHIR content failed: "+errorSummary(resE.getErrors())); 133 } else if (res.size() == 0) { 134 throw new FHIRException("Parsing FHIR content returned no elements in a context where one element is required because: "+errorSummary(resE.getErrors())); 135 } 136 if (errors != null) { 137 errors.addAll(resE.getErrors()); 138 } 139 return resE.getElement(); 140 } 141 142 private String errorSummary(List<ValidationMessage> errors) { 143 if (errors == null || errors.size() == 0) { 144 return "(no error description)"; 145 } else { 146 return errors.get(0).summary(); 147 } 148 } 149 150 public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException; 151 152 public void logError(List<ValidationMessage> errors, String ruleDate, int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError { 153 if (errors != null) { 154 if (policy == ValidationPolicy.EVERYTHING) { 155 ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level); 156 msg.setRuleDate(ruleDate); 157 errors.add(msg); 158 } else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK)) 159 throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col)); 160 } 161 } 162 163 164 protected StructureDefinition getDefinition(List<ValidationMessage> errors, int line, int col, String ns, String name) throws FHIRFormatError { 165 if (logical != null) { 166 String expectedName = ExtensionUtilities.readStringExtension(logical, ExtensionDefinitions.EXT_XML_NAME); 167 if (expectedName == null) { 168 expectedName = logical.getType(); 169 if (Utilities.isAbsoluteUrl(expectedName)) { 170 expectedName = expectedName.substring(expectedName.lastIndexOf("/")+1); 171 } 172 } 173 String expectedNamespace = ExtensionUtilities.readStringExtension(logical, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED); 174 if (matchesNamespace(expectedNamespace, ns) && matchesName(expectedName, name)) { 175 return logical; 176 } else { 177 if (expectedNamespace == null && ns == null) { 178 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.LOGICAL_MODEL_NAME_MISMATCH, name, expectedName), IssueSeverity.FATAL); 179 } else { 180 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.LOGICAL_MODEL_QNAME_MISMATCH, qn(ns, name), qn(expectedNamespace, expectedName)), IssueSeverity.FATAL); 181 } 182 return null; 183 } 184 } else { 185 if (ns == null) { 186 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS__CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAMESPACE, name), IssueSeverity.FATAL); 187 return null; 188 } 189 if (name == null) { 190 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL); 191 return null; 192 } 193 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 194 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) { 195 String type = urlTail(sd.getType()); 196 if(name.equals(type) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ExtensionUtilities.hasAnyOfExtensions(sd, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED)) 197 return sd; 198 String sns = ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED); 199 if ((name.equals(type) || name.equals(sd.getName())) && ns != null && ns.equals(sns)) 200 return sd; 201 } 202 } 203 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAMESPACENAME_, (ns == null ? "(none)" : ns), name), IssueSeverity.FATAL); 204 return null; 205 } 206 } 207 208 private Object qn(String ns, String name) { 209 return ns == null ? name : ns+"::"+name; 210 } 211 212 private boolean matchesNamespace(String expectedNamespace, String ns) { 213 if (expectedNamespace == null) { 214 return ns == null || "noNamespace".equals(ns); 215 } else { 216 return expectedNamespace.equals(ns); 217 } 218 } 219 220 private boolean matchesName(String expectedName, String name) { 221 return expectedName != null && expectedName.equals(name); 222 } 223 224 protected String urlTail(String type) { 225 return type == null || !type.contains("/") ? type : type.substring(type.lastIndexOf("/")+1); 226 } 227 228 protected StructureDefinition getDefinition(List<ValidationMessage> errors, int line, int col, String name) throws FHIRFormatError { 229 if (name == null) { 230 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL); 231 return null; 232 } 233 // first pass: only look at base definitions 234 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 235 if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) { 236 contextUtilities.generateSnapshot(sd); 237 return sd; 238 } 239 } 240 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 241 if (name.equals(sd.getTypeName()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 242 contextUtilities.generateSnapshot(sd); 243 return sd; 244 } 245 } 246 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 247 if (name.equals(sd.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 248 contextUtilities.generateSnapshot(sd); 249 return sd; 250 } 251 } 252 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL); 253 return null; 254 } 255 256 public ILinkResolver getLinkResolver() { 257 return linkResolver; 258 } 259 260 public ParserBase setLinkResolver(ILinkResolver linkResolver) { 261 this.linkResolver = linkResolver; 262 return this; 263 } 264 265 public boolean isShowDecorations() { 266 return showDecorations; 267 } 268 269 public void setShowDecorations(boolean showDecorations) { 270 this.showDecorations = showDecorations; 271 } 272 273 public String getImpliedProfile() { 274 return null; 275 } 276 277 278 public IdRenderingPolicy getIdPolicy() { 279 return idPolicy; 280 } 281 282 public void setIdPolicy(IdRenderingPolicy idPolicy) { 283 this.idPolicy = idPolicy; 284 } 285 286 protected boolean wantCompose(String path, Element e) { 287 if (!"id".equals(e.getName())) { 288 return true; 289 } 290 if (path!=null && path.contains(".")) { 291 return idPolicy.forInner(); 292 } else { 293 return idPolicy.forRoot(); 294 } 295 } 296 297 public boolean hasLogical() { 298 return logical != null; 299 } 300 301 public StructureDefinition getLogical() { 302 return logical; 303 } 304 305 public ParserBase setLogical(StructureDefinition logical) { 306 this.logical = logical; 307 return this; 308 } 309 310 public IDigitalSignatureServices getSignatureServices() { 311 return signatureServices; 312 } 313 314 public void setSignatureServices(IDigitalSignatureServices signatureServices) { 315 this.signatureServices = signatureServices; 316 } 317 318 protected String getReferenceForElement(Element element) { 319 if (element.isPrimitive()) { 320 return element.primitiveValue(); 321 } else { 322 return element.getNamedChildValue("reference"); 323 } 324 } 325 326 public IWorkerContext getContext() { 327 return context; 328 } 329 330 public ValidationPolicy getPolicy() { 331 return policy; 332 } 333 334 public ProfileUtilities getProfileUtilities() { 335 return profileUtilities; 336 } 337 338 public ContextUtilities getContextUtilities() { 339 return contextUtilities; 340 } 341 342 public void setCanonicalFilter(String... paths) { 343 canonicalFilter.clear(); 344 for (String p : paths) { 345 canonicalFilter.add(p); 346 } 347 } 348 349 350}