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  protected JsonObject getJsonObjectFromArray(JsonArray array, int i) throws IOException {
180    JsonElement e = array.get(i);
181    if (e.isJsonObject()) {
182      return (JsonObject) e;
183    }
184    if (e.isJsonNull()) {
185      return new JsonObject();
186    }
187    throw new IOException("Array item "+i+" is a "+e.getClass()+" looking for an Object");
188  }
189  
190  /**
191   * 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)
192   * @throws IOException 
193   */
194  @Override
195  public void compose(OutputStream stream, Resource resource) throws IOException {
196    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
197    if (style == OutputStyle.CANONICAL) {
198      json = new JsonCreatorCanonical(osw);
199    } else if (style == OutputStyle.PRETTY) {
200      json = new JsonCreatorDirect(osw, true, false);
201    } else {
202      json = new JsonCreatorDirect(osw, false, false); // use this instead of Gson because this preserves decimal formatting
203    }
204    json.beginObject();
205    composeResource(resource);
206    json.endObject();
207    json.finish();
208    osw.flush();
209  }
210
211  /**
212   * Compose a resource using a pre-existing JsonWriter
213   * @throws IOException 
214   */
215  public void compose(JsonCreator writer, Resource resource) throws IOException {
216    json = writer;
217    composeResource(resource);
218  }
219  
220  @Override
221  public void compose(OutputStream stream, DataType type, String rootName) throws IOException {
222    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
223    if (style == OutputStyle.CANONICAL) {
224      json = new JsonCreatorCanonical(osw);
225    } else if (style == OutputStyle.PRETTY) {
226      json = new JsonCreatorDirect(osw, true, false);
227    } else {
228      json = new JsonCreatorDirect(osw, false, false); // use this instead of Gson because this preserves decimal formatting
229    }
230    json.beginObject();
231    composeTypeInner(type);
232    json.endObject();
233    json.finish();
234    osw.flush();
235  }
236  
237  /* -- json routines --------------------------------------------------- */
238
239  protected JsonCreator json;
240  private boolean htmlPretty;
241  
242  private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException {
243    // the GSON parser is the fastest, but the least robust 
244    if (allowComments || allowUnknownContent) {
245      return JsonTrackingParser.parse(TextFile.streamToString(input), null, allowUnknownContent, allowComments);      
246    } else {
247      return (JsonObject) com.google.gson.JsonParser.parseString(TextFile.streamToString(input));
248    }
249  }
250  
251  protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError {
252    if (json != null && json.has("id"))
253      e.setId(json.get("id").getAsString());
254    if (!Utilities.noString(e.getId()))
255      idMap.put(e.getId(), e);
256    if (json.has("fhir_comments") && handleComments) {
257      JsonArray array = json.getAsJsonArray("fhir_comments");
258      for (int i = 0; i < array.size(); i++) {
259        e.getFormatCommentsPre().add(array.get(i).getAsString());
260      }
261    }
262  }
263  
264  protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError {
265    XhtmlParser prsr = new XhtmlParser();
266    try {
267                return prsr.parse(value, "div").getChildNodes().get(0);
268        } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
269                throw new FHIRFormatError(e.getMessage(), e);
270        }
271  }
272  
273  protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException {
274          return (DomainResource) parseResource(json);
275  }
276
277        protected void writeNull(String name) throws IOException {
278                json.nullValue();
279        }
280        protected void prop(String name, String value) throws IOException {
281                if (name != null)
282                        json.name(name);
283                json.value(value);
284        }
285
286  protected void prop(String name, java.lang.Boolean value) throws IOException {
287    if (name != null)
288      json.name(name);
289    json.value(value);
290  }
291
292  protected void prop(String name, BigDecimal value) throws IOException {
293    if (name != null)
294      json.name(name);
295    json.value(value);
296  }
297
298  protected void propNum(String name, String value) throws IOException {
299    if (name != null)
300      json.name(name);
301    json.valueNum(value);
302  }
303
304  protected void prop(String name, java.lang.Integer value) throws IOException {
305    if (name != null)
306      json.name(name);
307    json.value(value);
308  }
309
310        protected void composeXhtml(String name, XhtmlNode html) throws IOException {
311                if (!Utilities.noString(xhtmlMessage)) {
312      prop(name, "<div>!-- "+xhtmlMessage+" --></div>");
313                } else {
314                XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
315                prop(name, comp.compose(html));
316                }
317        }
318
319        protected void open(String name) throws IOException {
320                if (name != null) 
321                        json.name(name);
322                json.beginObject();
323        }
324
325        protected void close() throws IOException {
326                json.endObject();
327        }
328
329        protected void openArray(String name) throws IOException {
330                if (name != null) 
331                        json.name(name);
332                json.beginArray();
333        }
334
335        protected void closeArray() throws IOException {
336                json.endArray();
337        }
338
339        protected void openObject(String name) throws IOException {
340                if (name != null) 
341                        json.name(name);
342                json.beginObject();
343        }
344
345        protected void closeObject() throws IOException {
346                json.endObject();
347        }
348
349//  protected void composeBinary(String name, Binary element) {
350//    if (element != null) {
351//      prop("resourceType", "Binary");
352//      if (element.getXmlId() != null)
353//        prop("id", element.getXmlId());
354//      prop("contentType", element.getContentType());
355//      prop("content", toString(element.getContent()));
356//    }    
357//    
358//  }
359
360  protected boolean anyHasExtras(List<? extends Element> list) {
361    for (Element e : list) {
362      if (e.hasExtension() || !Utilities.noString(e.getId()))
363        return true;
364    }
365    return false;
366  }
367
368  protected boolean anyHasValue(List<? extends PrimitiveType> list) {
369    for (PrimitiveType e : list) {
370      if (e.hasValue())
371        return true;
372    }
373    return false;
374  }
375
376        protected boolean makeComments(Element element) {
377                return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty());
378        }
379        
380  protected void composeDomainResource(String name, DomainResource e) throws IOException {
381          openObject(name);
382          composeResource(e);
383          close();
384          
385  }
386
387  protected abstract void composeType(String prefix, DataType type) throws IOException;
388
389  
390  abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException;
391
392  protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException {
393          composeStringCore(name, new StringType(value.getValue()), inArray);
394  }    
395
396  abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException;
397
398  protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException {
399          composeStringExtras(name, new StringType(value.getValue()), inArray);
400  }    
401  
402  protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException {
403          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
404  }
405
406  protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException {
407          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
408  }
409
410}