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