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