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