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