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