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