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
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.r4.model.DomainResource;
072import org.hl7.fhir.r4.model.Element;
073import org.hl7.fhir.r4.model.IdType;
074import org.hl7.fhir.r4.model.Resource;
075import org.hl7.fhir.r4.model.StringType;
076import org.hl7.fhir.r4.model.Type;
077import org.hl7.fhir.utilities.TextFile;
078import org.hl7.fhir.utilities.Utilities;
079import org.hl7.fhir.utilities.json.JsonTrackingParser;
080import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
081import org.hl7.fhir.utilities.xhtml.XhtmlNode;
082import org.hl7.fhir.utilities.xhtml.XhtmlParser;
083
084import com.google.gson.JsonArray;
085import com.google.gson.JsonElement;
086import com.google.gson.JsonObject;
087import com.google.gson.JsonSyntaxException;
088/**
089 * General parser for JSON content. You instantiate an JsonParser of these, but you 
090 * actually use parse or parseGeneral defined on this class
091 * 
092 * The two classes are separated to keep generated and manually maintained code apart.
093 */
094public abstract class JsonParserBase extends ParserBase implements IParser {
095        
096  @Override
097  public ParserType getType() {
098          return ParserType.JSON;
099  }
100
101        // private static com.google.gson.JsonParser  parser = new com.google.gson.JsonParser();
102  
103  // -- in descendent generated code --------------------------------------
104  
105  abstract protected Resource parseResource(JsonObject json) throws IOException, FHIRFormatError;
106  abstract protected Type parseType(JsonObject json, String type) throws IOException, FHIRFormatError;
107  abstract protected Type parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError;
108  abstract protected Type parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError;
109  abstract protected boolean hasTypeName(JsonObject json, String prefix);
110  abstract protected void composeResource(Resource resource) throws IOException;
111  abstract protected void composeTypeInner(Type type) throws IOException;
112
113  /* -- entry points --------------------------------------------------- */
114
115  /**
116   * @throws FHIRFormatError 
117   * Parse content that is known to be a resource
118   * @throws IOException 
119   * @throws  
120   */
121  @Override
122  public Resource parse(InputStream input) throws IOException, FHIRFormatError {
123    JsonObject json = loadJson(input);
124    return parseResource(json);
125  }
126
127  /**
128   * parse xml that is known to be a resource, and that has already been read into a JSON object  
129   * @throws IOException 
130   * @throws FHIRFormatError 
131   */
132  public Resource parse(JsonObject json) throws FHIRFormatError, IOException {
133    return parseResource(json);
134  }
135
136  @Override
137  public Type parseType(InputStream input, String type) throws IOException, FHIRFormatError {
138    JsonObject json = loadJson(input);
139    return parseType(json, type);
140  }
141
142  @Override
143  public Type parseAnyType(InputStream input, String type) throws IOException, FHIRFormatError {
144    JsonObject json = loadJson(input);
145    return parseAnyType(json, type);
146  }
147
148  protected JsonObject getJObject(JsonObject parent, String name) throws IOException {
149    JsonElement j = parent.get(name);
150    if (j == null) { 
151      return null;
152    }
153    if (!(j instanceof JsonObject)) {
154      throw new IOException("property "+name+" is a "+j.getClass()+" looking for an object");
155    }
156    return (JsonObject) j;
157  }
158  
159
160  /**
161   * 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)
162   * @throws IOException 
163   */
164  @Override
165  public void compose(OutputStream stream, Resource resource) throws IOException {
166    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
167    if (style == OutputStyle.CANONICAL)
168      json = new JsonCreatorCanonical(osw);
169    else
170      json = new JsonCreatorDirect(osw); // use this instead of Gson because this preserves decimal formatting
171    json.setIndent(style == OutputStyle.PRETTY ? "  " : "");
172    json.beginObject();
173    composeResource(resource);
174    json.endObject();
175    json.finish();
176    osw.flush();
177  }
178
179  /**
180   * Compose a resource using a pre-existing JsonWriter
181   * @throws IOException 
182   */
183  public void compose(JsonCreator writer, Resource resource) throws IOException {
184    json = writer;
185    composeResource(resource);
186  }
187  
188  @Override
189  public void compose(OutputStream stream, Type type, String rootName) throws IOException {
190    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
191    if (style == OutputStyle.CANONICAL)
192      json = new JsonCreatorCanonical(osw);
193    else
194      json = new JsonCreatorDirect(osw);// use this instead of Gson because this preserves decimal formatting
195    json.setIndent(style == OutputStyle.PRETTY ? "  " : "");
196    json.beginObject();
197    composeTypeInner(type);
198    json.endObject();
199    json.finish();
200    osw.flush();
201  }
202    
203
204  
205  /* -- json routines --------------------------------------------------- */
206
207  protected JsonCreator json;
208  private boolean htmlPretty;
209  
210  private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException {
211    return JsonTrackingParser.parse(TextFile.streamToString(input), null, allowUnknownContent, allowComments);
212    // return parser.parse(TextFile.streamToString(input)).getAsJsonObject();
213  }
214  
215//  private JsonObject loadJson(String input) {
216//    return parser.parse(input).getAsJsonObject();
217//  }
218//  
219  protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError {
220    if (json != null && json.has("id"))
221      e.setId(json.get("id").getAsString());
222    if (!Utilities.noString(e.getId()))
223      idMap.put(e.getId(), e);
224    if (json.has("fhir_comments") && handleComments) {
225      JsonArray array = json.getAsJsonArray("fhir_comments");
226      for (int i = 0; i < array.size(); i++) {
227        e.getFormatCommentsPre().add(array.get(i).getAsString());
228      }
229    }
230  }
231  
232  protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError {
233    XhtmlParser prsr = new XhtmlParser();
234    try {
235                return prsr.parse(value, "div").getChildNodes().get(0);
236        } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
237                throw new FHIRFormatError(e.getMessage(), e);
238        }
239  }
240  
241  protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException {
242          return (DomainResource) parseResource(json);
243  }
244
245        protected void writeNull(String name) throws IOException {
246                json.nullValue();
247        }
248        protected void prop(String name, String value) throws IOException {
249                if (name != null)
250                        json.name(name);
251                json.value(value);
252        }
253
254  protected void prop(String name, java.lang.Boolean value) throws IOException {
255    if (name != null)
256      json.name(name);
257    json.value(value);
258  }
259
260  protected void prop(String name, BigDecimal value) throws IOException {
261    if (name != null)
262      json.name(name);
263    json.value(value);
264  }
265
266  protected void propNum(String name, String value) throws IOException {
267    if (name != null)
268      json.name(name);
269    json.valueNum(value);
270  }
271
272  protected void prop(String name, java.lang.Integer value) throws IOException {
273    if (name != null)
274      json.name(name);
275    json.value(value);
276  }
277
278        protected void composeXhtml(String name, XhtmlNode html) throws IOException {
279                if (!Utilities.noString(xhtmlMessage)) {
280      prop(name, "<div>!-- "+xhtmlMessage+" --></div>");
281                } else {
282                XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
283                prop(name, comp.compose(html));
284                }
285        }
286
287        protected void open(String name) throws IOException {
288                if (name != null) 
289                        json.name(name);
290                json.beginObject();
291        }
292
293        protected void close() throws IOException {
294                json.endObject();
295        }
296
297        protected void openArray(String name) throws IOException {
298                if (name != null) 
299                        json.name(name);
300                json.beginArray();
301        }
302
303        protected void closeArray() throws IOException {
304                json.endArray();
305        }
306
307        protected void openObject(String name) throws IOException {
308                if (name != null) 
309                        json.name(name);
310                json.beginObject();
311        }
312
313        protected void closeObject() throws IOException {
314                json.endObject();
315        }
316
317//  protected void composeBinary(String name, Binary element) {
318//    if (element != null) {
319//      prop("resourceType", "Binary");
320//      if (element.getXmlId() != null)
321//        prop("id", element.getXmlId());
322//      prop("contentType", element.getContentType());
323//      prop("content", toString(element.getContent()));
324//    }    
325//    
326//  }
327
328  protected boolean anyHasExtras(List<? extends Element> list) {
329          for (Element e : list) {
330                if (e.hasExtension() || !Utilities.noString(e.getId()))
331                        return true;
332          }
333          return false;
334  }
335
336        protected boolean makeComments(Element element) {
337                return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty());
338        }
339        
340  protected void composeDomainResource(String name, DomainResource e) throws IOException {
341          openObject(name);
342          composeResource(e);
343          close();
344          
345  }
346
347  protected abstract void composeType(String prefix, Type type) throws IOException;
348
349  
350  abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException;
351
352  protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException {
353          composeStringCore(name, new StringType(value.getValue()), inArray);
354  }    
355
356  abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException;
357
358  protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException {
359          composeStringExtras(name, new StringType(value.getValue()), inArray);
360  }    
361  
362  protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException {
363          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
364  }
365
366  protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException {
367          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
368  }
369
370}