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