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