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