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