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;
038import java.util.ArrayList;
039
040import org.hl7.fhir.exceptions.DefinitionException;
041import org.hl7.fhir.exceptions.FHIRException;
042import org.hl7.fhir.exceptions.FHIRFormatError;
043import org.hl7.fhir.r5.context.ContextUtilities;
044import org.hl7.fhir.r5.context.IWorkerContext;
045import org.hl7.fhir.r5.formats.FormatUtilities;
046import org.hl7.fhir.r5.formats.IParser.OutputStyle;
047import org.hl7.fhir.r5.model.StructureDefinition;
048import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
049import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
050import org.hl7.fhir.r5.utils.ToolingExtensions;
051import org.hl7.fhir.utilities.Utilities;
052import org.hl7.fhir.utilities.i18n.I18nConstants;
053import org.hl7.fhir.utilities.validation.ValidationMessage;
054import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
055import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
056import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
057
058public abstract class ParserBase {
059
060  public enum IdRenderingPolicy {
061    All, None, RootOnly, NotRoot;
062
063    boolean forRoot() {
064      return this == All || this == RootOnly;
065    }
066
067    boolean forInner() {
068      return this == All || this == NotRoot;
069    }
070  }
071
072  public class NamedElement {
073    private String name;
074    private Element element;
075    public NamedElement(String name, Element element) {
076      super();
077      this.name = name;
078      this.element = element;
079    }
080    public String getName() {
081      return name;
082    }
083    public Element getElement() {
084      return element;
085    }
086    
087  }
088
089  public interface ILinkResolver {
090    String resolveType(String type);
091    String resolveProperty(Property property);
092    String resolvePage(String string);
093  }
094  
095  public enum ValidationPolicy { NONE, QUICK, EVERYTHING }
096
097  public boolean isPrimitive(String code) {
098    StructureDefinition sd = context.fetchTypeDefinition(code);
099    if (sd != null) {
100      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
101    }
102
103    return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "uuid", "xhtml", "url", "canonical");
104    
105        }
106
107        protected IWorkerContext context;
108        protected ValidationPolicy policy;
109  protected List<ValidationMessage> errors;
110  protected ILinkResolver linkResolver;
111  protected boolean showDecorations;
112  protected IdRenderingPolicy idPolicy = IdRenderingPolicy.All;
113  protected StructureDefinition logical;
114  
115        public ParserBase(IWorkerContext context) {
116                super();
117                this.context = context;
118                policy = ValidationPolicy.NONE;
119        }
120
121        public void setupValidation(ValidationPolicy policy, List<ValidationMessage> errors) {
122          this.policy = policy;
123          this.errors = errors;
124        }
125
126  public abstract List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException;
127  
128  public Element parseSingle(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
129    if (errors == null) {
130      errors = new ArrayList<>();
131    }
132    List<NamedElement> res = parse(stream);
133    if (res == null) {
134      throw new FHIRException("Parsing FHIR content failed: "+errorSummary());      
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());
137    }
138    if (res.size() != 1) {
139      throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed");
140    }
141    return res.get(0).getElement();
142  }
143
144        private String errorSummary() {
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        //FIXME: i18n should be done here
155        public void logError(String ruleDate, int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError {
156          if (errors != null) {
157            if (policy == ValidationPolicy.EVERYTHING) {
158              ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level);
159              msg.setRuleDate(ruleDate);
160              errors.add(msg);
161            } else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK))
162              throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col));
163          }
164        }
165        
166        
167        protected StructureDefinition getDefinition(int line, int col, String ns, String name) throws FHIRFormatError {
168    if (ns == null) {
169      logError(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);
170      return null;
171    }
172    if (name == null) {
173      logError(ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
174      return null;
175        }
176          for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
177            if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) {
178              String type = urlTail(sd.getType());
179        if(name.equals(type) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
180                return sd;
181              String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
182              if ((name.equals(type) || name.equals(sd.getName())) && ns != null && ns.equals(sns))
183                return sd;
184            }
185          }
186          logError(ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAMESPACENAME_, ns, name), IssueSeverity.FATAL);
187          return null;
188  }
189
190  private String urlTail(String type) {
191    return type == null || !type.contains("/") ? type : type.substring(type.lastIndexOf("/")+1);
192  }
193
194  protected StructureDefinition getDefinition(int line, int col, String name) throws FHIRFormatError {
195    if (name == null) {
196      logError(ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
197      return null;
198        }
199    // first pass: only look at base definitions
200          for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
201            if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) {
202              new ContextUtilities(context).generateSnapshot(sd); 
203              return sd;
204            }
205          }
206    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
207      if (name.equals(sd.getType()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
208        new ContextUtilities(context).generateSnapshot(sd); 
209        return sd;
210      }
211    }
212          logError(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);
213          return null;
214  }
215
216  public ILinkResolver getLinkResolver() {
217    return linkResolver;
218  }
219
220  public ParserBase setLinkResolver(ILinkResolver linkResolver) {
221    this.linkResolver = linkResolver;
222    return this;
223  }
224
225  public boolean isShowDecorations() {
226    return showDecorations;
227  }
228
229  public void setShowDecorations(boolean showDecorations) {
230    this.showDecorations = showDecorations;
231  }
232
233  public String getImpliedProfile() {
234    return null;
235  }
236
237
238  public IdRenderingPolicy getIdPolicy() {
239    return idPolicy;
240  }
241
242  public void setIdPolicy(IdRenderingPolicy idPolicy) {
243    this.idPolicy = idPolicy;
244  }
245
246  protected boolean wantCompose(String path, Element e) {
247    if (!"id".equals(e.getName())) {
248      return true;
249    }
250    if (path!=null && path.contains(".")) {
251      return idPolicy.forInner();
252    } else {
253      return idPolicy.forRoot();
254    }
255  }
256
257  public boolean hasLogical() {
258    return logical != null;
259  }
260
261  public StructureDefinition getLogical() {
262    return logical;
263  }
264
265  public ParserBase setLogical(StructureDefinition logical) {
266    this.logical = logical;
267    return this;
268  }
269
270}