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}