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