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