001package org.hl7.fhir.r5.formats;
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/*
034Copyright (c) 2011+, HL7, Inc
035All rights reserved.
036
037Redistribution and use in source and binary forms, with or without modification, 
038are permitted provided that the following conditions are met:
039
040 * Redistributions of source code must retain the above copyright notice, this 
041   list of conditions and the following disclaimer.
042 * Redistributions in binary form must reproduce the above copyright notice, 
043   this list of conditions and the following disclaimer in the documentation 
044   and/or other materials provided with the distribution.
045 * Neither the name of HL7 nor the names of its contributors may be used to 
046   endorse or promote products derived from this software without specific 
047   prior written permission.
048
049THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
050ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
051WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
052IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
053INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
054NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
055PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
056WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
057ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
058POSSIBILITY OF SUCH DAMAGE.
059
060*/
061
062import java.io.IOException;
063import java.io.InputStream;
064import java.io.OutputStream;
065import java.io.OutputStreamWriter;
066import java.math.BigDecimal;
067import java.util.List;
068
069import org.hl7.fhir.exceptions.FHIRFormatError;
070import org.hl7.fhir.instance.model.api.IIdType;
071import org.hl7.fhir.r5.model.DataType;
072import org.hl7.fhir.r5.model.DomainResource;
073import org.hl7.fhir.r5.model.Element;
074import org.hl7.fhir.r5.model.IdType;
075import org.hl7.fhir.r5.model.PrimitiveType;
076import org.hl7.fhir.r5.model.Resource;
077import org.hl7.fhir.r5.model.StringType;
078import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags;
079import org.hl7.fhir.utilities.TextFile;
080import org.hl7.fhir.utilities.Utilities;
081import org.hl7.fhir.utilities.json.JsonTrackingParser;
082import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
083import org.hl7.fhir.utilities.xhtml.XhtmlNode;
084import org.hl7.fhir.utilities.xhtml.XhtmlParser;
085
086import com.google.gson.JsonArray;
087import com.google.gson.JsonElement;
088import com.google.gson.JsonObject;
089import com.google.gson.JsonSyntaxException;
090import org.slf4j.LoggerFactory;
091
092/**
093 * General parser for JSON content. You instantiate an JsonParser of these, but you 
094 * actually use parse or parseGeneral defined on this class
095 * 
096 * The two classes are separated to keep generated and manually maintained code apart.
097 */
098public abstract class JsonParserBase extends ParserBase implements IParser {
099
100  static {
101//    LoggerFactory.getLogger("org.hl7.fhir.r5.formats.JsonParserBase").debug("JSON Parser is being loaded");
102    ClassesLoadedFlags.ourJsonParserBaseLoaded = true;
103  }
104
105  @Override
106  public ParserType getType() {
107          return ParserType.JSON;
108  }
109
110        // private static com.google.gson.JsonParser  parser = new com.google.gson.JsonParser();
111  
112  // -- in descendent generated code --------------------------------------
113  
114  abstract protected Resource parseResource(JsonObject json) throws IOException, FHIRFormatError;
115  abstract protected DataType parseType(JsonObject json, String type) throws IOException, FHIRFormatError;
116  abstract protected DataType parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError;
117  abstract protected DataType parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError;
118  abstract protected boolean hasTypeName(JsonObject json, String prefix);
119  abstract protected void composeResource(Resource resource) throws IOException;
120  abstract protected void composeTypeInner(DataType type) throws IOException;
121
122  /* -- entry points --------------------------------------------------- */
123
124  /**
125   * @throws FHIRFormatError 
126   * Parse content that is known to be a resource
127   * @throws IOException 
128   * @throws  
129   */
130  @Override
131  public Resource parse(InputStream input) throws IOException, FHIRFormatError {
132    JsonObject json = loadJson(input);
133    return parseResource(json);
134  }
135
136  /**
137   * parse xml that is known to be a resource, and that has already been read into a JSON object  
138   * @throws IOException 
139   * @throws FHIRFormatError 
140   */
141  public Resource parse(JsonObject json) throws FHIRFormatError, IOException {
142    return parseResource(json);
143  }
144
145  @Override
146  public DataType parseType(InputStream input, String type) throws IOException, FHIRFormatError {
147    JsonObject json = loadJson(input);
148    return parseType(json, type);
149  }
150
151  @Override
152  public DataType parseAnyType(InputStream input, String type) throws IOException, FHIRFormatError {
153    JsonObject json = loadJson(input);
154    return parseAnyType(json, type);
155  }
156
157  protected JsonObject getJObject(JsonObject parent, String name) throws IOException {
158    JsonElement j = parent.get(name);
159    if (j == null) { 
160      return null;
161    }
162    if (!(j instanceof JsonObject)) {
163      throw new IOException("property "+name+" is a "+j.getClass()+" looking for an object");
164    }
165    return (JsonObject) j;
166  }
167  
168  protected JsonArray getJArray(JsonObject parent, String name) throws IOException {
169    JsonElement j = parent.get(name);
170    if (j == null) { 
171      return null;
172    }
173    if (!(j instanceof JsonArray)) {
174      throw new IOException("property "+name+" is a "+j.getClass()+" looking for an Array");
175    }
176    return (JsonArray) j;
177  }
178  
179  /**
180   * Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
181   * @throws IOException 
182   */
183  @Override
184  public void compose(OutputStream stream, Resource resource) throws IOException {
185    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
186    if (style == OutputStyle.CANONICAL) {
187      json = new JsonCreatorCanonical(osw);
188    } else if (style == OutputStyle.PRETTY) {
189      json = new JsonCreatorDirect(osw, true, false);
190    } else {
191      json = new JsonCreatorDirect(osw, false, false); // use this instead of Gson because this preserves decimal formatting
192    }
193    json.beginObject();
194    composeResource(resource);
195    json.endObject();
196    json.finish();
197    osw.flush();
198  }
199
200  /**
201   * Compose a resource using a pre-existing JsonWriter
202   * @throws IOException 
203   */
204  public void compose(JsonCreator writer, Resource resource) throws IOException {
205    json = writer;
206    composeResource(resource);
207  }
208  
209  @Override
210  public void compose(OutputStream stream, DataType type, String rootName) throws IOException {
211    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
212    if (style == OutputStyle.CANONICAL) {
213      json = new JsonCreatorCanonical(osw);
214    } else if (style == OutputStyle.PRETTY) {
215      json = new JsonCreatorDirect(osw, true, false);
216    } else {
217      json = new JsonCreatorDirect(osw, false, false); // use this instead of Gson because this preserves decimal formatting
218    }
219    json.beginObject();
220    composeTypeInner(type);
221    json.endObject();
222    json.finish();
223    osw.flush();
224  }
225  
226  /* -- json routines --------------------------------------------------- */
227
228  protected JsonCreator json;
229  private boolean htmlPretty;
230  
231  private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException {
232    // the GSON parser is the fastest, but the least robust 
233    if (allowComments || allowUnknownContent) {
234      return JsonTrackingParser.parse(TextFile.streamToString(input), null, allowUnknownContent, allowComments);      
235    } else {
236      return (JsonObject) com.google.gson.JsonParser.parseString(TextFile.streamToString(input));
237    }
238  }
239  
240  protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError {
241    if (json != null && json.has("id"))
242      e.setId(json.get("id").getAsString());
243    if (!Utilities.noString(e.getId()))
244      idMap.put(e.getId(), e);
245    if (json.has("fhir_comments") && handleComments) {
246      JsonArray array = json.getAsJsonArray("fhir_comments");
247      for (int i = 0; i < array.size(); i++) {
248        e.getFormatCommentsPre().add(array.get(i).getAsString());
249      }
250    }
251  }
252  
253  protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError {
254    XhtmlParser prsr = new XhtmlParser();
255    try {
256                return prsr.parse(value, "div").getChildNodes().get(0);
257        } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
258                throw new FHIRFormatError(e.getMessage(), e);
259        }
260  }
261  
262  protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException {
263          return (DomainResource) parseResource(json);
264  }
265
266        protected void writeNull(String name) throws IOException {
267                json.nullValue();
268        }
269        protected void prop(String name, String value) throws IOException {
270                if (name != null)
271                        json.name(name);
272                json.value(value);
273        }
274
275  protected void prop(String name, java.lang.Boolean value) throws IOException {
276    if (name != null)
277      json.name(name);
278    json.value(value);
279  }
280
281  protected void prop(String name, BigDecimal value) throws IOException {
282    if (name != null)
283      json.name(name);
284    json.value(value);
285  }
286
287  protected void propNum(String name, String value) throws IOException {
288    if (name != null)
289      json.name(name);
290    json.valueNum(value);
291  }
292
293  protected void prop(String name, java.lang.Integer value) throws IOException {
294    if (name != null)
295      json.name(name);
296    json.value(value);
297  }
298
299        protected void composeXhtml(String name, XhtmlNode html) throws IOException {
300                if (!Utilities.noString(xhtmlMessage)) {
301      prop(name, "<div>!-- "+xhtmlMessage+" --></div>");
302                } else {
303                XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
304                prop(name, comp.compose(html));
305                }
306        }
307
308        protected void open(String name) throws IOException {
309                if (name != null) 
310                        json.name(name);
311                json.beginObject();
312        }
313
314        protected void close() throws IOException {
315                json.endObject();
316        }
317
318        protected void openArray(String name) throws IOException {
319                if (name != null) 
320                        json.name(name);
321                json.beginArray();
322        }
323
324        protected void closeArray() throws IOException {
325                json.endArray();
326        }
327
328        protected void openObject(String name) throws IOException {
329                if (name != null) 
330                        json.name(name);
331                json.beginObject();
332        }
333
334        protected void closeObject() throws IOException {
335                json.endObject();
336        }
337
338//  protected void composeBinary(String name, Binary element) {
339//    if (element != null) {
340//      prop("resourceType", "Binary");
341//      if (element.getXmlId() != null)
342//        prop("id", element.getXmlId());
343//      prop("contentType", element.getContentType());
344//      prop("content", toString(element.getContent()));
345//    }    
346//    
347//  }
348
349  protected boolean anyHasExtras(List<? extends Element> list) {
350    for (Element e : list) {
351      if (e.hasExtension() || !Utilities.noString(e.getId()))
352        return true;
353    }
354    return false;
355  }
356
357  protected boolean anyHasValue(List<? extends PrimitiveType> list) {
358    for (PrimitiveType e : list) {
359      if (e.hasValue())
360        return true;
361    }
362    return false;
363  }
364
365        protected boolean makeComments(Element element) {
366                return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty());
367        }
368        
369  protected void composeDomainResource(String name, DomainResource e) throws IOException {
370          openObject(name);
371          composeResource(e);
372          close();
373          
374  }
375
376  protected abstract void composeType(String prefix, DataType type) throws IOException;
377
378  
379  abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException;
380
381  protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException {
382          composeStringCore(name, new StringType(value.getValue()), inArray);
383  }    
384
385  abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException;
386
387  protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException {
388          composeStringExtras(name, new StringType(value.getValue()), inArray);
389  }    
390  
391  protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException {
392          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
393  }
394
395  protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException {
396          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
397  }
398
399}