001package org.hl7.fhir.r4.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/* 033Copyright (c) 2011+, HL7, Inc 034All rights reserved. 035 036Redistribution and use in source and binary forms, with or without modification, 037are permitted provided that the following conditions are met: 038 039 * Redistributions of source code must retain the above copyright notice, this 040 list of conditions and the following disclaimer. 041 * Redistributions in binary form must reproduce the above copyright notice, 042 this list of conditions and the following disclaimer in the documentation 043 and/or other materials provided with the distribution. 044 * Neither the name of HL7 nor the names of its contributors may be used to 045 endorse or promote products derived from this software without specific 046 prior written permission. 047 048THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 049ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 050WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 051IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 052INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 053NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 054PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 055WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 056ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 057POSSIBILITY OF SUCH DAMAGE. 058 059*/ 060 061import java.io.IOException; 062import java.io.InputStream; 063import java.io.OutputStream; 064import java.io.OutputStreamWriter; 065import java.math.BigDecimal; 066import java.util.List; 067 068import org.hl7.fhir.exceptions.FHIRFormatError; 069import org.hl7.fhir.instance.model.api.IIdType; 070import org.hl7.fhir.r4.model.DomainResource; 071import org.hl7.fhir.r4.model.Element; 072import org.hl7.fhir.r4.model.IdType; 073import org.hl7.fhir.r4.model.Resource; 074import org.hl7.fhir.r4.model.StringType; 075import org.hl7.fhir.r4.model.Type; 076import org.hl7.fhir.utilities.TextFile; 077import org.hl7.fhir.utilities.Utilities; 078import org.hl7.fhir.utilities.json.JsonTrackingParser; 079import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 080import org.hl7.fhir.utilities.xhtml.XhtmlNode; 081import org.hl7.fhir.utilities.xhtml.XhtmlParser; 082 083import com.google.gson.JsonArray; 084import com.google.gson.JsonElement; 085import com.google.gson.JsonObject; 086import com.google.gson.JsonSyntaxException; 087 088/** 089 * General parser for JSON content. You instantiate an JsonParser of these, but 090 * you actually use parse or parseGeneral defined on this class 091 * 092 * The two classes are separated to keep generated and manually maintained code 093 * 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 103 // com.google.gson.JsonParser(); 104 105 // -- in descendent generated code -------------------------------------- 106 107 abstract protected Resource parseResource(JsonObject json) throws IOException, FHIRFormatError; 108 109 abstract protected Type parseType(JsonObject json, String type) throws IOException, FHIRFormatError; 110 111 abstract protected Type parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError; 112 113 abstract protected Type parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError; 114 115 abstract protected boolean hasTypeName(JsonObject json, String prefix); 116 117 abstract protected void composeResource(Resource resource) throws IOException; 118 119 abstract protected void composeTypeInner(Type type) throws IOException; 120 121 /* -- entry points --------------------------------------------------- */ 122 123 /** 124 * @throws FHIRFormatError Parse content that is known to be a resource 125 * @throws IOException 126 * @throws 127 */ 128 @Override 129 public Resource parse(InputStream input) throws IOException, FHIRFormatError { 130 JsonObject json = loadJson(input); 131 return parseResource(json); 132 } 133 134 /** 135 * parse xml that is known to be a resource, and that has already been read into 136 * a JSON object 137 * 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 Type parseType(InputStream input, String type) throws IOException, FHIRFormatError { 147 JsonObject json = loadJson(input); 148 return parseType(json, type); 149 } 150 151 @Override 152 public Type 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 /** 169 * Compose a resource to a stream, possibly using pretty presentation for a 170 * human reader (used in the spec, for example, but not normally in production) 171 * 172 * @throws IOException 173 */ 174 @Override 175 public void compose(OutputStream stream, Resource resource) throws IOException { 176 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 177 if (style == OutputStyle.CANONICAL) 178 json = new JsonCreatorCanonical(osw); 179 else 180 json = new JsonCreatorDirect(osw); // use this instead of Gson because this preserves decimal formatting 181 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 182 json.beginObject(); 183 composeResource(resource); 184 json.endObject(); 185 json.finish(); 186 osw.flush(); 187 } 188 189 /** 190 * Compose a resource using a pre-existing JsonWriter 191 * 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, Type 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 /* -- json routines --------------------------------------------------- */ 215 216 protected JsonCreator json; 217 private boolean htmlPretty; 218 219 private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException { 220 return JsonTrackingParser.parse(TextFile.streamToString(input), null, allowUnknownContent, allowComments); 221 // return parser.parse(TextFile.streamToString(input)).getAsJsonObject(); 222 } 223 224// private JsonObject loadJson(String input) { 225// return parser.parse(input).getAsJsonObject(); 226// } 227// 228 protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError { 229 if (json != null && json.has("id")) 230 e.setId(json.get("id").getAsString()); 231 if (!Utilities.noString(e.getId())) 232 idMap.put(e.getId(), e); 233 if (json.has("fhir_comments") && handleComments) { 234 JsonArray array = json.getAsJsonArray("fhir_comments"); 235 for (int i = 0; i < array.size(); i++) { 236 e.getFormatCommentsPre().add(array.get(i).getAsString()); 237 } 238 } 239 } 240 241 protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError { 242 XhtmlParser prsr = new XhtmlParser(); 243 try { 244 return prsr.parse(value, "div").getChildNodes().get(0); 245 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 246 throw new FHIRFormatError(e.getMessage(), e); 247 } 248 } 249 250 protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException { 251 return (DomainResource) parseResource(json); 252 } 253 254 protected void writeNull(String name) throws IOException { 255 json.nullValue(); 256 } 257 258 protected void prop(String name, String value) throws IOException { 259 if (name != null) 260 json.name(name); 261 json.value(value); 262 } 263 264 protected void prop(String name, java.lang.Boolean value) throws IOException { 265 if (name != null) 266 json.name(name); 267 json.value(value); 268 } 269 270 protected void prop(String name, BigDecimal value) throws IOException { 271 if (name != null) 272 json.name(name); 273 json.value(value); 274 } 275 276 protected void propNum(String name, String value) throws IOException { 277 if (name != null) 278 json.name(name); 279 json.valueNum(value); 280 } 281 282 protected void prop(String name, java.lang.Integer value) throws IOException { 283 if (name != null) 284 json.name(name); 285 json.value(value); 286 } 287 288 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 289 if (!Utilities.noString(xhtmlMessage)) { 290 prop(name, "<div>!-- " + xhtmlMessage + " --></div>"); 291 } else { 292 XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty); 293 prop(name, comp.compose(html)); 294 } 295 } 296 297 protected void open(String name) throws IOException { 298 if (name != null) 299 json.name(name); 300 json.beginObject(); 301 } 302 303 protected void close() throws IOException { 304 json.endObject(); 305 } 306 307 protected void openArray(String name) throws IOException { 308 if (name != null) 309 json.name(name); 310 json.beginArray(); 311 } 312 313 protected void closeArray() throws IOException { 314 json.endArray(); 315 } 316 317 protected void openObject(String name) throws IOException { 318 if (name != null) 319 json.name(name); 320 json.beginObject(); 321 } 322 323 protected void closeObject() throws IOException { 324 json.endObject(); 325 } 326 327// protected void composeBinary(String name, Binary element) { 328// if (element != null) { 329// prop("resourceType", "Binary"); 330// if (element.getXmlId() != null) 331// prop("id", element.getXmlId()); 332// prop("contentType", element.getContentType()); 333// prop("content", toString(element.getContent())); 334// } 335// 336// } 337 338 protected boolean anyHasExtras(List<? extends Element> list) { 339 for (Element e : list) { 340 if (e.hasExtension() || !Utilities.noString(e.getId())) 341 return true; 342 } 343 return false; 344 } 345 346 protected boolean makeComments(Element element) { 347 return handleComments && (style != OutputStyle.CANONICAL) 348 && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty()); 349 } 350 351 protected void composeDomainResource(String name, DomainResource e) throws IOException { 352 openObject(name); 353 composeResource(e); 354 close(); 355 356 } 357 358 protected abstract void composeType(String prefix, Type type) throws IOException; 359 360 abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException; 361 362 protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException { 363 composeStringCore(name, new StringType(value.getValue()), inArray); 364 } 365 366 abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException; 367 368 protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException { 369 composeStringExtras(name, new StringType(value.getValue()), inArray); 370 } 371 372 protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) 373 throws FHIRFormatError, IOException { 374 parseElementProperties(theAsJsonObject, (Element) theReferenceElement); 375 } 376 377 protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) 378 throws FHIRFormatError, IOException { 379 parseElementProperties(theAsJsonObject, (Element) theReferenceElement); 380 } 381 382}