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