001package org.hl7.fhir.r4.formats; 002/* 003Copyright (c) 2011+, HL7, Inc 004All rights reserved. 005 006Redistribution and use in source and binary forms, with or without modification, 007are permitted provided that the following conditions are met: 008 009 * Redistributions of source code must retain the above copyright notice, this 010 list of conditions and the following disclaimer. 011 * Redistributions in binary form must reproduce the above copyright notice, 012 this list of conditions and the following disclaimer in the documentation 013 and/or other materials provided with the distribution. 014 * Neither the name of HL7 nor the names of its contributors may be used to 015 endorse or promote products derived from this software without specific 016 prior written permission. 017 018THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 019ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 020WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 021IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 022INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 023NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 024PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 025WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 026ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 027POSSIBILITY OF SUCH DAMAGE. 028 029*/ 030 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.OutputStream; 034import java.io.OutputStreamWriter; 035import java.math.BigDecimal; 036import java.util.List; 037 038import org.hl7.fhir.r4.model.DomainResource; 039import org.hl7.fhir.r4.model.Element; 040import org.hl7.fhir.r4.model.IdType; 041import org.hl7.fhir.r4.model.Resource; 042import org.hl7.fhir.r4.model.StringType; 043import org.hl7.fhir.r4.model.Type; 044import org.hl7.fhir.r4.utils.formats.JsonTrackingParser; 045import org.hl7.fhir.exceptions.FHIRFormatError; 046import org.hl7.fhir.instance.model.api.IIdType; 047import org.hl7.fhir.utilities.TextFile; 048import org.hl7.fhir.utilities.Utilities; 049import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 050import org.hl7.fhir.utilities.xhtml.XhtmlNode; 051import org.hl7.fhir.utilities.xhtml.XhtmlParser; 052 053import com.google.gson.JsonArray; 054import com.google.gson.JsonObject; 055import com.google.gson.JsonSyntaxException; 056/** 057 * General parser for JSON content. You instantiate an JsonParser of these, but you 058 * actually use parse or parseGeneral defined on this class 059 * 060 * The two classes are separated to keep generated and manually maintained code apart. 061 */ 062public abstract class JsonParserBase extends ParserBase implements IParser { 063 064 @Override 065 public ParserType getType() { 066 return ParserType.JSON; 067 } 068 069 // private static com.google.gson.JsonParser parser = new com.google.gson.JsonParser(); 070 071 // -- in descendent generated code -------------------------------------- 072 073 abstract protected Resource parseResource(JsonObject json) throws IOException, FHIRFormatError; 074 abstract protected Type parseType(JsonObject json, String type) throws IOException, FHIRFormatError; 075 abstract protected Type parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError; 076 abstract protected Type parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError; 077 abstract protected boolean hasTypeName(JsonObject json, String prefix); 078 abstract protected void composeResource(Resource resource) throws IOException; 079 abstract protected void composeTypeInner(Type type) throws IOException; 080 081 /* -- entry points --------------------------------------------------- */ 082 083 /** 084 * @throws FHIRFormatError 085 * Parse content that is known to be a resource 086 * @throws IOException 087 * @throws 088 */ 089 @Override 090 public Resource parse(InputStream input) throws IOException, FHIRFormatError { 091 JsonObject json = loadJson(input); 092 return parseResource(json); 093 } 094 095 /** 096 * parse xml that is known to be a resource, and that has already been read into a JSON object 097 * @throws IOException 098 * @throws FHIRFormatError 099 */ 100 public Resource parse(JsonObject json) throws FHIRFormatError, IOException { 101 return parseResource(json); 102 } 103 104 @Override 105 public Type parseType(InputStream input, String type) throws IOException, FHIRFormatError { 106 JsonObject json = loadJson(input); 107 return parseType(json, type); 108 } 109 110 @Override 111 public Type parseAnyType(InputStream input, String type) throws IOException, FHIRFormatError { 112 JsonObject json = loadJson(input); 113 return parseAnyType(json, type); 114 } 115 116 /** 117 * 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) 118 * @throws IOException 119 */ 120 @Override 121 public void compose(OutputStream stream, Resource resource) throws IOException { 122 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 123 if (style == OutputStyle.CANONICAL) 124 json = new JsonCreatorCanonical(osw); 125 else 126 json = new JsonCreatorDirect(osw); // use this instead of Gson because this preserves decimal formatting 127 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 128 json.beginObject(); 129 composeResource(resource); 130 json.endObject(); 131 json.finish(); 132 osw.flush(); 133 } 134 135 /** 136 * Compose a resource using a pre-existing JsonWriter 137 * @throws IOException 138 */ 139 public void compose(JsonCreator writer, Resource resource) throws IOException { 140 json = writer; 141 composeResource(resource); 142 } 143 144 @Override 145 public void compose(OutputStream stream, Type type, String rootName) throws IOException { 146 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 147 if (style == OutputStyle.CANONICAL) 148 json = new JsonCreatorCanonical(osw); 149 else 150 json = new JsonCreatorDirect(osw);// use this instead of Gson because this preserves decimal formatting 151 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 152 json.beginObject(); 153 composeTypeInner(type); 154 json.endObject(); 155 json.finish(); 156 osw.flush(); 157 } 158 159 160 161 /* -- json routines --------------------------------------------------- */ 162 163 protected JsonCreator json; 164 private boolean htmlPretty; 165 166 private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException { 167 return JsonTrackingParser.parse(TextFile.streamToString(input), null); 168 // return parser.parse(TextFile.streamToString(input)).getAsJsonObject(); 169 } 170 171// private JsonObject loadJson(String input) { 172// return parser.parse(input).getAsJsonObject(); 173// } 174// 175 protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError { 176 if (json != null && json.has("id")) 177 e.setId(json.get("id").getAsString()); 178 if (!Utilities.noString(e.getId())) 179 idMap.put(e.getId(), e); 180 if (json.has("fhir_comments") && handleComments) { 181 JsonArray array = json.getAsJsonArray("fhir_comments"); 182 for (int i = 0; i < array.size(); i++) { 183 e.getFormatCommentsPre().add(array.get(i).getAsString()); 184 } 185 } 186 } 187 188 protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError { 189 XhtmlParser prsr = new XhtmlParser(); 190 try { 191 return prsr.parse(value, "div").getChildNodes().get(0); 192 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 193 throw new FHIRFormatError(e.getMessage(), e); 194 } 195 } 196 197 protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException { 198 return (DomainResource) parseResource(json); 199 } 200 201 protected void writeNull(String name) throws IOException { 202 json.nullValue(); 203 } 204 protected void prop(String name, String value) throws IOException { 205 if (name != null) 206 json.name(name); 207 json.value(value); 208 } 209 210 protected void prop(String name, java.lang.Boolean value) throws IOException { 211 if (name != null) 212 json.name(name); 213 json.value(value); 214 } 215 216 protected void prop(String name, BigDecimal value) throws IOException { 217 if (name != null) 218 json.name(name); 219 json.value(value); 220 } 221 222 protected void propNum(String name, String value) throws IOException { 223 if (name != null) 224 json.name(name); 225 json.valueNum(value); 226 } 227 228 protected void prop(String name, java.lang.Integer value) throws IOException { 229 if (name != null) 230 json.name(name); 231 json.value(value); 232 } 233 234 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 235 if (!Utilities.noString(xhtmlMessage)) { 236 prop(name, "<div>!-- "+xhtmlMessage+" --></div>"); 237 } else { 238 XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty); 239 prop(name, comp.compose(html)); 240 } 241 } 242 243 protected void open(String name) throws IOException { 244 if (name != null) 245 json.name(name); 246 json.beginObject(); 247 } 248 249 protected void close() throws IOException { 250 json.endObject(); 251 } 252 253 protected void openArray(String name) throws IOException { 254 if (name != null) 255 json.name(name); 256 json.beginArray(); 257 } 258 259 protected void closeArray() throws IOException { 260 json.endArray(); 261 } 262 263 protected void openObject(String name) throws IOException { 264 if (name != null) 265 json.name(name); 266 json.beginObject(); 267 } 268 269 protected void closeObject() throws IOException { 270 json.endObject(); 271 } 272 273// protected void composeBinary(String name, Binary element) { 274// if (element != null) { 275// prop("resourceType", "Binary"); 276// if (element.getXmlId() != null) 277// prop("id", element.getXmlId()); 278// prop("contentType", element.getContentType()); 279// prop("content", toString(element.getContent())); 280// } 281// 282// } 283 284 protected boolean anyHasExtras(List<? extends Element> list) { 285 for (Element e : list) { 286 if (e.hasExtension() || !Utilities.noString(e.getId())) 287 return true; 288 } 289 return false; 290 } 291 292 protected boolean makeComments(Element element) { 293 return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty()); 294 } 295 296 protected void composeDomainResource(String name, DomainResource e) throws IOException { 297 openObject(name); 298 composeResource(e); 299 close(); 300 301 } 302 303 protected abstract void composeType(String prefix, Type type) throws IOException; 304 305 306 abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException; 307 308 protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException { 309 composeStringCore(name, new StringType(value.getValue()), inArray); 310 } 311 312 abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException; 313 314 protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException { 315 composeStringExtras(name, new StringType(value.getValue()), inArray); 316 } 317 318 protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException { 319 parseElementProperties(theAsJsonObject, (Element)theReferenceElement); 320 } 321 322 protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException { 323 parseElementProperties(theAsJsonObject, (Element)theReferenceElement); 324 } 325 326}