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}