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