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