001package org.hl7.fhir.r4.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.r4.model.DomainResource; 039import org.hl7.fhir.r4.model.Element; 040import org.hl7.fhir.r4.model.IdType; 041import org.hl7.fhir.r4.model.Resource; 042import org.hl7.fhir.r4.model.StringType; 043import org.hl7.fhir.r4.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 parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError; 075 abstract protected Type parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError; 076 abstract protected boolean hasTypeName(JsonObject json, String prefix); 077 abstract protected void composeResource(Resource resource) throws IOException; 078 abstract protected void composeTypeInner(Type type) throws IOException; 079 080 /* -- entry points --------------------------------------------------- */ 081 082 /** 083 * @throws FHIRFormatError 084 * Parse content that is known to be a resource 085 * @throws IOException 086 * @throws 087 */ 088 @Override 089 public Resource parse(InputStream input) throws IOException, FHIRFormatError { 090 JsonObject json = loadJson(input); 091 return parseResource(json); 092 } 093 094 /** 095 * parse xml that is known to be a resource, and that has already been read into a JSON object 096 * @throws IOException 097 * @throws FHIRFormatError 098 */ 099 public Resource parse(JsonObject json) throws FHIRFormatError, IOException { 100 return parseResource(json); 101 } 102 103 @Override 104 public Type parseType(InputStream input, String type) throws IOException, FHIRFormatError { 105 JsonObject json = loadJson(input); 106 return parseType(json, type); 107 } 108 109 @Override 110 public Type parseAnyType(InputStream input, String type) throws IOException, FHIRFormatError { 111 JsonObject json = loadJson(input); 112 return parseAnyType(json, type); 113 } 114 115 /** 116 * 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) 117 * @throws IOException 118 */ 119 @Override 120 public void compose(OutputStream stream, Resource resource) throws IOException { 121 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 122 if (style == OutputStyle.CANONICAL) 123 json = new JsonCreatorCanonical(osw); 124 else 125 json = new JsonCreatorGson(osw); 126 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 127 json.beginObject(); 128 composeResource(resource); 129 json.endObject(); 130 json.finish(); 131 osw.flush(); 132 } 133 134 /** 135 * Compose a resource using a pre-existing JsonWriter 136 * @throws IOException 137 */ 138 public void compose(JsonCreator writer, Resource resource) throws IOException { 139 json = writer; 140 composeResource(resource); 141 } 142 143 @Override 144 public void compose(OutputStream stream, Type type, String rootName) throws IOException { 145 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 146 if (style == OutputStyle.CANONICAL) 147 json = new JsonCreatorCanonical(osw); 148 else 149 json = new JsonCreatorGson(osw); 150 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 151 json.beginObject(); 152 composeTypeInner(type); 153 json.endObject(); 154 json.finish(); 155 osw.flush(); 156 } 157 158 159 160 /* -- json routines --------------------------------------------------- */ 161 162 protected JsonCreator json; 163 private boolean htmlPretty; 164 165 private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException { 166 return parser.parse(TextFile.streamToString(input)).getAsJsonObject(); 167 } 168 169// private JsonObject loadJson(String input) { 170// return parser.parse(input).getAsJsonObject(); 171// } 172// 173 protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError { 174 if (json != null && json.has("id")) 175 e.setId(json.get("id").getAsString()); 176 if (!Utilities.noString(e.getId())) 177 idMap.put(e.getId(), e); 178 if (json.has("fhir_comments") && handleComments) { 179 JsonArray array = json.getAsJsonArray("fhir_comments"); 180 for (int i = 0; i < array.size(); i++) { 181 e.getFormatCommentsPre().add(array.get(i).getAsString()); 182 } 183 } 184 } 185 186 protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError { 187 XhtmlParser prsr = new XhtmlParser(); 188 try { 189 return prsr.parse(value, "div").getChildNodes().get(0); 190 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 191 throw new FHIRFormatError(e.getMessage(), e); 192 } 193 } 194 195 protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException { 196 return (DomainResource) parseResource(json); 197 } 198 199 protected void writeNull(String name) throws IOException { 200 json.nullValue(); 201 } 202 protected void prop(String name, String value) throws IOException { 203 if (name != null) 204 json.name(name); 205 json.value(value); 206 } 207 208 protected void prop(String name, java.lang.Boolean value) throws IOException { 209 if (name != null) 210 json.name(name); 211 json.value(value); 212 } 213 214 protected void prop(String name, BigDecimal value) throws IOException { 215 if (name != null) 216 json.name(name); 217 json.value(value); 218 } 219 220 protected void prop(String name, java.lang.Integer value) throws IOException { 221 if (name != null) 222 json.name(name); 223 json.value(value); 224 } 225 226 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 227 if (!Utilities.noString(xhtmlMessage)) { 228 prop(name, "<div>!-- "+xhtmlMessage+" --></div>"); 229 } else { 230 XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty); 231 prop(name, comp.compose(html)); 232 } 233 } 234 235 protected void open(String name) throws IOException { 236 if (name != null) 237 json.name(name); 238 json.beginObject(); 239 } 240 241 protected void close() throws IOException { 242 json.endObject(); 243 } 244 245 protected void openArray(String name) throws IOException { 246 if (name != null) 247 json.name(name); 248 json.beginArray(); 249 } 250 251 protected void closeArray() throws IOException { 252 json.endArray(); 253 } 254 255 protected void openObject(String name) throws IOException { 256 if (name != null) 257 json.name(name); 258 json.beginObject(); 259 } 260 261 protected void closeObject() throws IOException { 262 json.endObject(); 263 } 264 265// protected void composeBinary(String name, Binary element) { 266// if (element != null) { 267// prop("resourceType", "Binary"); 268// if (element.getXmlId() != null) 269// prop("id", element.getXmlId()); 270// prop("contentType", element.getContentType()); 271// prop("content", toString(element.getContent())); 272// } 273// 274// } 275 276 protected boolean anyHasExtras(List<? extends Element> list) { 277 for (Element e : list) { 278 if (e.hasExtension() || !Utilities.noString(e.getId())) 279 return true; 280 } 281 return false; 282 } 283 284 protected boolean makeComments(Element element) { 285 return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty()); 286 } 287 288 protected void composeDomainResource(String name, DomainResource e) throws IOException { 289 openObject(name); 290 composeResource(e); 291 close(); 292 293 } 294 295 protected abstract void composeType(String prefix, Type type) throws IOException; 296 297 298 abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException; 299 300 protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException { 301 composeStringCore(name, new StringType(value.getValue()), inArray); 302 } 303 304 abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException; 305 306 protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException { 307 composeStringExtras(name, new StringType(value.getValue()), inArray); 308 } 309 310 protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException { 311 parseElementProperties(theAsJsonObject, (Element)theReferenceElement); 312 } 313 314 protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException { 315 parseElementProperties(theAsJsonObject, (Element)theReferenceElement); 316 } 317 318}