001package org.hl7.fhir.r5.formats;
002
003import java.io.FileNotFoundException;
004import java.io.IOException;
005import java.io.InputStream;
006
007/*
008  Copyright (c) 2011+, HL7, Inc.
009  All rights reserved.
010  
011  Redistribution and use in source and binary forms, with or without modification, 
012  are permitted provided that the following conditions are met:
013    
014   * Redistributions of source code must retain the above copyright notice, this 
015     list of conditions and the following disclaimer.
016   * Redistributions in binary form must reproduce the above copyright notice, 
017     this list of conditions and the following disclaimer in the documentation 
018     and/or other materials provided with the distribution.
019   * Neither the name of HL7 nor the names of its contributors may be used to 
020     endorse or promote products derived from this software without specific 
021     prior written permission.
022  
023  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
024  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
025  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
026  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
027  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
028  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
029  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
030  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
031  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
032  POSSIBILITY OF SUCH DAMAGE.
033  
034 */
035
036
037
038/*
039Copyright (c) 2011+, HL7, Inc
040All rights reserved.
041
042Redistribution and use in source and binary forms, with or without modification, 
043are permitted provided that the following conditions are met:
044
045 * Redistributions of source code must retain the above copyright notice, this 
046   list of conditions and the following disclaimer.
047 * Redistributions in binary form must reproduce the above copyright notice, 
048   this list of conditions and the following disclaimer in the documentation 
049   and/or other materials provided with the distribution.
050 * Neither the name of HL7 nor the names of its contributors may be used to 
051   endorse or promote products derived from this software without specific 
052   prior written permission.
053
054THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
055ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
056WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
057IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
058INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
059NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
060PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
061WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
062ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
063POSSIBILITY OF SUCH DAMAGE.
064
065*/
066
067import java.lang.reflect.InvocationTargetException;
068import java.math.BigDecimal;
069import java.net.URI;
070
071import org.apache.commons.codec.binary.Base64;
072import org.hl7.fhir.exceptions.FHIRException;
073import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
074import org.hl7.fhir.r5.model.Resource;
075import org.hl7.fhir.utilities.TextFile;
076
077public abstract class FormatUtilities {
078  public static final String ID_REGEX = "[A-Za-z0-9\\-\\.]{1,64}";
079  public static final String FHIR_NS = "http://hl7.org/fhir";
080  public static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
081  public static final String NS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
082  private static final int MAX_SCAN_LENGTH = 1000; // how many characters to scan into content when autodetermining format
083 
084  protected String toString(String value) {
085    return value;
086  }
087  
088  protected String toString(int value) {
089    return java.lang.Integer.toString(value);
090  }
091  
092  protected String toString(boolean value) {
093    return java.lang.Boolean.toString(value);
094  }
095  
096  protected String toString(BigDecimal value) {
097    return value.toString();
098  }
099  
100  protected String toString(URI value) {
101    return value.toString();
102  }
103
104  public static String toString(byte[] value) {
105    byte[] encodeBase64 = Base64.encodeBase64(value);
106    return new String(encodeBase64);
107  }
108  
109        public static boolean isValidId(String tail) {
110          return tail.matches(ID_REGEX);
111  }
112
113  public static String makeId(String candidate) {
114    StringBuilder b = new StringBuilder();
115    for (char c : candidate.toCharArray())
116      if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-')
117        b.append(c);
118    return b.toString();
119  }
120
121  public static ParserBase makeParser(FhirFormat format) {
122    return makeParser(format.name());
123  }
124  
125  public static ParserBase makeParser(String format) {
126    /*
127     * Note: Use fully qualified references to the parsers here in order to avoid adding
128     * a class-level import statement for them. This is because the
129     * XmlParser and JsonParser are huuuuuge classes and classloading them is quite expensive
130     * in cases where they won't actually ever be instantiated (such as when using the
131     * validator in HAPI FHIR).
132     *
133     * See https://github.com/hapifhir/hapi-fhir/issues/3268
134     */
135    try {
136      if ("XML".equalsIgnoreCase(format))
137         return (ParserBase) Class.forName("org.hl7.fhir.r5.formats.XmlParser").getConstructor().newInstance();
138      if ("JSON".equalsIgnoreCase(format))
139        return (ParserBase) Class.forName("org.hl7.fhir.r5.formats.JsonParser").getConstructor().newInstance();
140      if ("TURTLE".equalsIgnoreCase(format))
141        throw new Error("unsupported Format " + format.toString()); // return new TurtleParser();
142      if ("JSONLD".equalsIgnoreCase(format))
143        throw new Error("unsupported Format " + format.toString()); // return new JsonLdParser();
144      if ("VBAR".equalsIgnoreCase(format)) throw new Error("unsupported Format " + format.toString()); //
145      if ("TEXT".equalsIgnoreCase(format)) throw new Error("unsupported Format " + format.toString()); //
146      throw new Error("unsupported Format " + format);
147    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
148      throw new Error("Could not instantiate", e);
149    }
150  }
151
152  public static FhirFormat determineFormat(byte[] source) throws FHIRException {
153    return determineFormat(source, MAX_SCAN_LENGTH);
154  }
155  
156  public static FhirFormat determineFormat(byte[] source, int scanLength) throws FHIRException {
157    if (scanLength == -1)
158      scanLength = source.length;
159    int lt = firstIndexOf(source, '<', scanLength);
160    int ps = firstIndexOf(source, '{', scanLength);
161    int at = firstIndexOf(source, '@', scanLength);
162    if (at < ps && at < lt) return FhirFormat.TURTLE;
163    if (ps < lt) return FhirFormat.JSON;
164    if (lt < ps) return FhirFormat.XML;
165    throw new FHIRException("unable to determine format");
166  }
167
168  private static int firstIndexOf(byte[] source, char c, int scanLength) {
169    for (int i = 0; i < Math.min(source.length, scanLength); i++) {
170      if (source[i] == c)
171        return i;
172    }
173    return Integer.MAX_VALUE;
174  }
175
176  public static Resource loadFile(String path) throws FileNotFoundException, IOException, FHIRException {
177    byte[] src = TextFile.fileToBytes(path);
178    FhirFormat fmt = determineFormat(src);
179    ParserBase parser = makeParser(fmt);
180    return parser.parse(src);
181  }
182
183
184  public static Resource loadFile(InputStream source) throws FileNotFoundException, IOException, FHIRException {
185    byte[] src = TextFile.streamToBytes(source);
186    FhirFormat fmt = determineFormat(src);
187    ParserBase parser = makeParser(fmt);
188    return parser.parse(src);
189  }
190
191
192  public static Resource loadFileTight(String path) throws FileNotFoundException, IOException, FHIRException {
193    byte[] src = TextFile.fileToBytes(path);
194    FhirFormat fmt = determineFormat(src);
195    ParserBase parser = makeParser(fmt);
196    parser.setAllowUnknownContent(false);
197    return parser.parse(src);
198  }
199
200  public static Resource loadFileTight(InputStream source) throws FileNotFoundException, IOException, FHIRException {
201    byte[] src = TextFile.streamToBytes(source);
202    FhirFormat fmt = determineFormat(src);
203    ParserBase parser = makeParser(fmt);
204    parser.setAllowUnknownContent(false);
205    return parser.parse(src);
206  }
207  
208
209}