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