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