
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; 038import java.util.ArrayList; 039 040import org.hl7.fhir.exceptions.DefinitionException; 041import org.hl7.fhir.exceptions.FHIRException; 042import org.hl7.fhir.exceptions.FHIRFormatError; 043import org.hl7.fhir.r5.context.ContextUtilities; 044import org.hl7.fhir.r5.context.IWorkerContext; 045import org.hl7.fhir.r5.formats.FormatUtilities; 046import org.hl7.fhir.r5.formats.IParser.OutputStyle; 047import org.hl7.fhir.r5.model.StructureDefinition; 048import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 049import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 050import org.hl7.fhir.r5.utils.ToolingExtensions; 051import org.hl7.fhir.utilities.Utilities; 052import org.hl7.fhir.utilities.i18n.I18nConstants; 053import org.hl7.fhir.utilities.validation.ValidationMessage; 054import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 055import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 056import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 057 058public abstract class ParserBase { 059 060 public enum IdRenderingPolicy { 061 All, None, RootOnly, NotRoot; 062 063 boolean forRoot() { 064 return this == All || this == RootOnly; 065 } 066 067 boolean forInner() { 068 return this == All || this == NotRoot; 069 } 070 } 071 072 public class NamedElement { 073 private String name; 074 private Element element; 075 public NamedElement(String name, Element element) { 076 super(); 077 this.name = name; 078 this.element = element; 079 } 080 public String getName() { 081 return name; 082 } 083 public Element getElement() { 084 return element; 085 } 086 087 } 088 089 public interface ILinkResolver { 090 String resolveType(String type); 091 String resolveProperty(Property property); 092 String resolvePage(String string); 093 } 094 095 public enum ValidationPolicy { NONE, QUICK, EVERYTHING } 096 097 public boolean isPrimitive(String code) { 098 StructureDefinition sd = context.fetchTypeDefinition(code); 099 if (sd != null) { 100 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 101 } 102 103 return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "uuid", "xhtml", "url", "canonical"); 104 105 } 106 107 protected IWorkerContext context; 108 protected ValidationPolicy policy; 109 protected List<ValidationMessage> errors; 110 protected ILinkResolver linkResolver; 111 protected boolean showDecorations; 112 protected IdRenderingPolicy idPolicy = IdRenderingPolicy.All; 113 protected StructureDefinition logical; 114 115 public ParserBase(IWorkerContext context) { 116 super(); 117 this.context = context; 118 policy = ValidationPolicy.NONE; 119 } 120 121 public void setupValidation(ValidationPolicy policy, List<ValidationMessage> errors) { 122 this.policy = policy; 123 this.errors = errors; 124 } 125 126 public abstract List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException; 127 128 public Element parseSingle(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { 129 if (errors == null) { 130 errors = new ArrayList<>(); 131 } 132 List<NamedElement> res = parse(stream); 133 if (res == null) { 134 throw new FHIRException("Parsing FHIR content failed: "+errorSummary()); 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()); 137 } 138 if (res.size() != 1) { 139 throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed"); 140 } 141 return res.get(0).getElement(); 142 } 143 144 private String errorSummary() { 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 //FIXME: i18n should be done here 155 public void logError(String ruleDate, int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError { 156 if (errors != null) { 157 if (policy == ValidationPolicy.EVERYTHING) { 158 ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level); 159 msg.setRuleDate(ruleDate); 160 errors.add(msg); 161 } else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK)) 162 throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col)); 163 } 164 } 165 166 167 protected StructureDefinition getDefinition(int line, int col, String ns, String name) throws FHIRFormatError { 168 if (ns == null) { 169 logError(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); 170 return null; 171 } 172 if (name == null) { 173 logError(ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL); 174 return null; 175 } 176 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 177 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) { 178 String type = urlTail(sd.getType()); 179 if(name.equals(type) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 180 return sd; 181 String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 182 if ((name.equals(type) || name.equals(sd.getName())) && ns != null && ns.equals(sns)) 183 return sd; 184 } 185 } 186 logError(ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAMESPACENAME_, ns, name), IssueSeverity.FATAL); 187 return null; 188 } 189 190 private String urlTail(String type) { 191 return type == null || !type.contains("/") ? type : type.substring(type.lastIndexOf("/")+1); 192 } 193 194 protected StructureDefinition getDefinition(int line, int col, String name) throws FHIRFormatError { 195 if (name == null) { 196 logError(ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL); 197 return null; 198 } 199 // first pass: only look at base definitions 200 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 201 if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) { 202 new ContextUtilities(context).generateSnapshot(sd); 203 return sd; 204 } 205 } 206 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 207 if (name.equals(sd.getType()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 208 new ContextUtilities(context).generateSnapshot(sd); 209 return sd; 210 } 211 } 212 logError(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); 213 return null; 214 } 215 216 public ILinkResolver getLinkResolver() { 217 return linkResolver; 218 } 219 220 public ParserBase setLinkResolver(ILinkResolver linkResolver) { 221 this.linkResolver = linkResolver; 222 return this; 223 } 224 225 public boolean isShowDecorations() { 226 return showDecorations; 227 } 228 229 public void setShowDecorations(boolean showDecorations) { 230 this.showDecorations = showDecorations; 231 } 232 233 public String getImpliedProfile() { 234 return null; 235 } 236 237 238 public IdRenderingPolicy getIdPolicy() { 239 return idPolicy; 240 } 241 242 public void setIdPolicy(IdRenderingPolicy idPolicy) { 243 this.idPolicy = idPolicy; 244 } 245 246 protected boolean wantCompose(String path, Element e) { 247 if (!"id".equals(e.getName())) { 248 return true; 249 } 250 if (path!=null && path.contains(".")) { 251 return idPolicy.forInner(); 252 } else { 253 return idPolicy.forRoot(); 254 } 255 } 256 257 public boolean hasLogical() { 258 return logical != null; 259 } 260 261 public StructureDefinition getLogical() { 262 return logical; 263 } 264 265 public ParserBase setLogical(StructureDefinition logical) { 266 this.logical = logical; 267 return this; 268 } 269 270}