
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 033 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.OutputStream; 037import java.util.ArrayList; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Set; 041 042import org.hl7.fhir.exceptions.DefinitionException; 043import org.hl7.fhir.exceptions.FHIRException; 044import org.hl7.fhir.exceptions.FHIRFormatError; 045import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 046import org.hl7.fhir.r5.context.ContextUtilities; 047import org.hl7.fhir.r5.context.IWorkerContext; 048import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 049import org.hl7.fhir.r5.extensions.ExtensionUtilities; 050import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 051import org.hl7.fhir.r5.formats.FormatUtilities; 052import org.hl7.fhir.r5.formats.IParser.OutputStyle; 053import org.hl7.fhir.r5.model.StructureDefinition; 054import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 055import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 056 057import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 058import org.hl7.fhir.utilities.Utilities; 059import org.hl7.fhir.utilities.i18n.I18nConstants; 060import org.hl7.fhir.utilities.validation.IDigitalSignatureServices; 061import org.hl7.fhir.utilities.validation.ValidationMessage; 062import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 063import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 064import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 065 066@MarkedToMoveToAdjunctPackage 067public abstract class ParserBase { 068 069 public enum IdRenderingPolicy { 070 All, None, RootOnly, NotRoot; 071 072 boolean forRoot() { 073 return this == All || this == RootOnly; 074 } 075 076 boolean forInner() { 077 return this == All || this == NotRoot; 078 } 079 } 080 081 public interface ILinkResolver { 082 String resolveType(String type); 083 String resolveProperty(Property property); 084 String resolvePage(String string); 085 String resolveReference(String referenceForElement); 086 } 087 088 public enum ValidationPolicy { NONE, QUICK, EVERYTHING } 089 090 public boolean isPrimitive(String code) { 091 return context.isPrimitiveType(code); 092 } 093 094 protected IWorkerContext context; 095 protected ValidationPolicy policy; 096 protected ILinkResolver linkResolver; 097 protected boolean showDecorations; 098 protected IdRenderingPolicy idPolicy = IdRenderingPolicy.All; 099 protected StructureDefinition logical; 100 protected IDigitalSignatureServices signatureServices; 101 private ProfileUtilities profileUtilities; 102 private ContextUtilities contextUtilities; 103 protected Set<String> canonicalFilter = new HashSet<>(); 104 105 public ParserBase(IWorkerContext context, ProfileUtilities utilities) { 106 super(); 107 this.context = context; 108 this.profileUtilities = utilities; 109 contextUtilities = new ContextUtilities(context); 110 policy = ValidationPolicy.NONE; 111 } 112 113 public ParserBase(IWorkerContext context) { 114 super(); 115 this.context = context; 116 this.profileUtilities = new ProfileUtilities(context, null, null, new FHIRPathEngine(context)); 117 contextUtilities = new ContextUtilities(context); 118 policy = ValidationPolicy.NONE; 119 } 120 121 public void setupValidation(ValidationPolicy policy) { 122 this.policy = policy; 123 } 124 125 public abstract List<ValidatedFragment> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException; 126 127 public Element parseSingle(InputStream stream, List<ValidationMessage> errors) throws IOException, FHIRFormatError, DefinitionException, FHIRException { 128 129 List<ValidatedFragment> res = parse(stream); 130 131 if (res.size() != 1) { 132 throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed"); 133 } 134 var resE = res.get(0); 135 if (resE.getElement() == null) { 136 throw new FHIRException("Parsing FHIR content failed: "+errorSummary(resE.getErrors())); 137 } else if (res.size() == 0) { 138 throw new FHIRException("Parsing FHIR content returned no elements in a context where one element is required because: "+errorSummary(resE.getErrors())); 139 } 140 if (errors != null) { 141 errors.addAll(resE.getErrors()); 142 } 143 return resE.getElement(); 144 } 145 146 private String errorSummary(List<ValidationMessage> errors) { 147 if (errors == null || errors.size() == 0) { 148 return "(no error description)"; 149 } else { 150 return errors.get(0).summary(); 151 } 152 } 153 154 public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException; 155 156 public void logError(List<ValidationMessage> errors, String ruleDate, int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError { 157 if (errors != null) { 158 if (policy == ValidationPolicy.EVERYTHING) { 159 ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level); 160 msg.setRuleDate(ruleDate); 161 errors.add(msg); 162 } else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK)) 163 throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col)); 164 } 165 } 166 167 168 protected StructureDefinition getDefinition(List<ValidationMessage> errors, int line, int col, String ns, String name) throws FHIRFormatError { 169 if (logical != null) { 170 String expectedName = ExtensionUtilities.readStringExtension(logical, ExtensionDefinitions.EXT_XML_NAME); 171 if (expectedName == null) { 172 expectedName = logical.getType(); 173 if (Utilities.isAbsoluteUrl(expectedName)) { 174 expectedName = expectedName.substring(expectedName.lastIndexOf("/")+1); 175 } 176 } 177 String expectedNamespace = ExtensionUtilities.readStringExtension(logical, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED); 178 if (matchesNamespace(expectedNamespace, ns) && matchesName(expectedName, name)) { 179 return logical; 180 } else { 181 if (expectedNamespace == null && ns == null) { 182 logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.LOGICAL_MODEL_NAME_MISMATCH, name, expectedName), IssueSeverity.FATAL); 183 } else { 184 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); 185 } 186 return null; 187 } 188 } else { 189 if (ns == 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_NAMESPACE, name), IssueSeverity.FATAL); 191 return null; 192 } 193 if (name == null) { 194 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); 195 return null; 196 } 197 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 198 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) { 199 String type = urlTail(sd.getType()); 200 if(name.equals(type) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ExtensionUtilities.hasAnyOfExtensions(sd, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED)) 201 return sd; 202 String sns = ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED); 203 if ((name.equals(type) || name.equals(sd.getName())) && ns != null && ns.equals(sns)) 204 return sd; 205 } 206 } 207 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); 208 return null; 209 } 210 } 211 212 private Object qn(String ns, String name) { 213 return ns == null ? name : ns+"::"+name; 214 } 215 216 private boolean matchesNamespace(String expectedNamespace, String ns) { 217 if (expectedNamespace == null) { 218 return ns == null || "noNamespace".equals(ns); 219 } else { 220 return expectedNamespace.equals(ns); 221 } 222 } 223 224 private boolean matchesName(String expectedName, String name) { 225 return expectedName != null && expectedName.equals(name); 226 } 227 228 protected String urlTail(String type) { 229 return type == null || !type.contains("/") ? type : type.substring(type.lastIndexOf("/")+1); 230 } 231 232 protected StructureDefinition getDefinition(List<ValidationMessage> errors, int line, int col, String name) throws FHIRFormatError { 233 if (name == null) { 234 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); 235 return null; 236 } 237 // first pass: only look at base definitions 238 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 239 if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) { 240 contextUtilities.generateSnapshot(sd); 241 return sd; 242 } 243 } 244 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 245 if (name.equals(sd.getTypeName()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 246 contextUtilities.generateSnapshot(sd); 247 return sd; 248 } 249 } 250 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 251 if (name.equals(sd.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 252 contextUtilities.generateSnapshot(sd); 253 return sd; 254 } 255 } 256 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); 257 return null; 258 } 259 260 public ILinkResolver getLinkResolver() { 261 return linkResolver; 262 } 263 264 public ParserBase setLinkResolver(ILinkResolver linkResolver) { 265 this.linkResolver = linkResolver; 266 return this; 267 } 268 269 public boolean isShowDecorations() { 270 return showDecorations; 271 } 272 273 public void setShowDecorations(boolean showDecorations) { 274 this.showDecorations = showDecorations; 275 } 276 277 public String getImpliedProfile() { 278 return null; 279 } 280 281 282 public IdRenderingPolicy getIdPolicy() { 283 return idPolicy; 284 } 285 286 public void setIdPolicy(IdRenderingPolicy idPolicy) { 287 this.idPolicy = idPolicy; 288 } 289 290 protected boolean wantCompose(String path, Element e) { 291 if (!"id".equals(e.getName())) { 292 return true; 293 } 294 if (path!=null && path.contains(".")) { 295 return idPolicy.forInner(); 296 } else { 297 return idPolicy.forRoot(); 298 } 299 } 300 301 public boolean hasLogical() { 302 return logical != null; 303 } 304 305 public StructureDefinition getLogical() { 306 return logical; 307 } 308 309 public ParserBase setLogical(StructureDefinition logical) { 310 this.logical = logical; 311 return this; 312 } 313 314 public IDigitalSignatureServices getSignatureServices() { 315 return signatureServices; 316 } 317 318 public void setSignatureServices(IDigitalSignatureServices signatureServices) { 319 this.signatureServices = signatureServices; 320 } 321 322 protected String getReferenceForElement(Element element) { 323 if (element.isPrimitive()) { 324 return element.primitiveValue(); 325 } else { 326 return element.getNamedChildValue("reference"); 327 } 328 } 329 330 public IWorkerContext getContext() { 331 return context; 332 } 333 334 public ValidationPolicy getPolicy() { 335 return policy; 336 } 337 338 public ProfileUtilities getProfileUtilities() { 339 return profileUtilities; 340 } 341 342 public ContextUtilities getContextUtilities() { 343 return contextUtilities; 344 } 345 346 public void setCanonicalFilter(String... paths) { 347 canonicalFilter.clear(); 348 for (String p : paths) { 349 canonicalFilter.add(p); 350 } 351 } 352 353 354}