001package org.hl7.fhir.r5.elementmodel; 002 003import java.io.ByteArrayInputStream; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032 */ 033 034 035 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039import java.io.OutputStreamWriter; 040import java.math.BigDecimal; 041import java.nio.charset.StandardCharsets; 042import java.util.ArrayList; 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.List; 046import java.util.Map; 047import java.util.Map.Entry; 048import java.util.Set; 049 050import org.hl7.fhir.exceptions.FHIRException; 051import org.hl7.fhir.exceptions.FHIRFormatError; 052import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 053import org.hl7.fhir.r5.context.ContextUtilities; 054import org.hl7.fhir.r5.context.IWorkerContext; 055import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; 056import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 057import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 058import org.hl7.fhir.r5.formats.IParser.OutputStyle; 059import org.hl7.fhir.r5.formats.JsonCreator; 060import org.hl7.fhir.r5.formats.JsonCreatorCanonical; 061import org.hl7.fhir.r5.formats.JsonCreatorDirect; 062import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 063import org.hl7.fhir.r5.model.ElementDefinition; 064import org.hl7.fhir.r5.model.StructureDefinition; 065import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 066import org.hl7.fhir.utilities.StringPair; 067import org.hl7.fhir.utilities.TextFile; 068import org.hl7.fhir.utilities.Utilities; 069import org.hl7.fhir.utilities.VersionUtilities; 070import org.hl7.fhir.utilities.i18n.I18nConstants; 071import org.hl7.fhir.utilities.json.model.JsonArray; 072import org.hl7.fhir.utilities.json.model.JsonComment; 073import org.hl7.fhir.utilities.json.model.JsonElement; 074import org.hl7.fhir.utilities.json.model.JsonNull; 075import org.hl7.fhir.utilities.json.model.JsonObject; 076import org.hl7.fhir.utilities.json.model.JsonPrimitive; 077import org.hl7.fhir.utilities.json.model.JsonProperty; 078import org.hl7.fhir.utilities.validation.ValidationMessage; 079import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 080import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 081import org.hl7.fhir.utilities.xhtml.XhtmlParser; 082 083 084public class JsonParser extends ParserBase { 085 086 private JsonCreator json; 087 private boolean allowComments; 088 089 private Element baseElement; 090 091 public JsonParser(IWorkerContext context, ProfileUtilities utilities) { 092 super(context, utilities); 093 094 } 095 096 public JsonParser(IWorkerContext context) { 097 super(context); 098 } 099 100 public Element parse(String source, String type) throws Exception { 101 return parse(source, type, false); 102 } 103 104 public Element parse(String source, String type, boolean inner) throws Exception { 105 ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", source.getBytes(StandardCharsets.UTF_8), false); 106 JsonObject obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true); 107 String path = "/"+type; 108 StructureDefinition sd = getDefinition(focusFragment.getErrors(), -1, -1, type); 109 if (sd == null) { 110 return null; 111 } 112 113 if (inner) { 114 // we have an anonymous wrapper that has an arbitrarily named property with the specified type. We're going to invent a snapshot for that 115 sd = new StructureDefinition(); 116 sd.setType("Wrapper"); 117 ElementDefinition bEd = sd.getSnapshot().addElement(); 118 ElementDefinition nEd = sd.getSnapshot().addElement(); 119 bEd.setPath("Wrapper"); 120 nEd.setPath("Wrapper."+obj.getProperties().get(0).getName()); 121 nEd.addType().setCode(type); 122 nEd.setMax(obj.getProperties().get(0).getValue().isJsonArray() ? "*" : "1"); 123 } 124 Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON); 125 result.setPath(type); 126 checkObject(focusFragment.getErrors(), obj, result, path); 127 result.setType(type); 128 parseChildren(focusFragment.getErrors(), path, obj, result, true, null); 129 result.numberChildren(); 130 return result; 131 } 132 133 134 @Override 135 public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException { 136// long start = System.currentTimeMillis(); 137 byte[] content = TextFile.streamToBytes(inStream); 138 ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", content, false); 139 140 ByteArrayInputStream stream = new ByteArrayInputStream(content); 141 142 // if we're parsing at this point, then we're going to use the custom parser 143 String source = TextFile.streamToString(stream); 144 JsonObject obj = null; 145 146 if (policy == ValidationPolicy.EVERYTHING) { 147 try { 148 obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true); 149 } catch (Exception e) { 150 logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1,context.formatMessage(I18nConstants.DOCUMENT), IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL); 151 } 152 } else { 153 obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true); 154 } 155 156 if (obj != null) { 157 focusFragment.setElement(parse(focusFragment.getErrors(), obj)); 158 } 159 List<ValidatedFragment> res = new ArrayList<>(); 160 res.add(focusFragment); 161 162// long t =System.currentTimeMillis()-start; 163// System.out.println("json parser: "+(t)+"ms, "+(content.length/1024)+"kb "+(t == 0 ? "" : " @ "+(content.length / t)+"kb/s")); 164 return res; 165 } 166 167 public Element parse(List<ValidationMessage> errors, JsonObject object) throws FHIRException { 168 return parse(errors, object, null); 169 } 170 171 public Element parse(List<ValidationMessage> errors, JsonObject object, String statedPath) throws FHIRException { 172 if (object == null) { 173 System.out.println("What?"); 174 } 175 StructureDefinition sd = getLogical(); 176 String name; 177 String path; 178 if (sd == null) { 179 JsonElement rt = object.get("resourceType"); 180 if (rt == null) { 181 logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL); 182 return null; 183 } else if (!rt.isJsonString()) { 184 logError(errors, "2022-11-26", line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL); 185 return null; 186 } else { 187 name = rt.asString(); 188 189 sd = getDefinition(errors, line(object), col(object), name); 190 if (sd == null) { 191 return null; 192 } 193 } 194 path = statedPath == null ? name : statedPath; 195 } else { 196 name = sd.getType(); 197 path = statedPath == null ? sd.getTypeTail() : statedPath; 198 } 199 baseElement = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON); 200 checkObject(errors, object, baseElement, path); 201 baseElement.markLocation(line(object), col(object)); 202 baseElement.setType(name); 203 baseElement.setPath(statedPath == null ? baseElement.fhirTypeRoot() : statedPath); 204 parseChildren(errors, path, object, baseElement, true, null); 205 baseElement.numberChildren(); 206 return baseElement; 207 } 208 209 private void checkObject(List<ValidationMessage> errors, JsonObject object, Element b, String path) { 210 b.setNativeObject(object); 211 checkComments(errors, object, b, path); 212 if (policy == ValidationPolicy.EVERYTHING) { 213 if (object.getProperties().size() == 0) { 214 logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR); 215 } 216 } 217 } 218 219 private void checkComments(List<ValidationMessage> errors, JsonElement element, Element b, String path) throws FHIRFormatError { 220 if (element != null && element.hasComments()) { 221 if (allowComments) { 222 for (JsonComment c : element.getComments()) { 223 b.getComments().add(c.getContent()); 224 } 225 } else { 226 for (JsonComment c : element.getComments()) { 227 logError(errors, "2022-11-26", c.getStart().getLine(), c.getStart().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMENTS_NOT_ALLOWED), IssueSeverity.ERROR); 228 } 229 } 230 } 231 } 232 233 private List<Property> parseChildren(List<ValidationMessage> errors, String path, JsonObject object, Element element, boolean hasResourceType, List<Property> properties) throws FHIRException { 234 if (properties == null) { 235 // save time refetching these if we're in a loop 236 properties = element.getProperty().getChildProperties(element.getName(), null); 237 } 238 processChildren(errors, path, object); 239 240 // first pass: process the properties 241 for (Property property : properties) { 242 parseChildItem(errors, path, object.getProperties(), element, property); 243 } 244 245 // second pass: check for things not processed (including duplicates) 246 checkNotProcessed(errors, path, element, hasResourceType, object.getProperties()); 247 248 249 if (object.isExtraComma()) { 250 logError(errors, "2022-11-26", object.getEnd().getLine(), object.getEnd().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR); 251 } 252 return properties; 253 } 254 255 private void checkNotProcessed(List<ValidationMessage> errors, String path, Element element, boolean hasResourceType, List<JsonProperty> children) { 256 if (policy != ValidationPolicy.NONE) { 257 for (JsonProperty e : children) { 258 if (e.getTag() == 0) { 259 StructureDefinition sd = element.getProperty().isLogical() ? getContextUtilities().fetchByJsonName(e.getName()) : null; 260 if (sd != null) { 261 Property property = new Property(context, sd.getSnapshot().getElementFirstRep(), sd, element.getProperty().getUtils(), element.getProperty().getContextUtils()); 262 parseChildItem(errors, path, children, element, property); 263 } else if ("fhir_comments".equals(e.getName()) && (VersionUtilities.isR2BVer(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion()))) { 264 if (!e.getValue().isJsonArray()) { 265 logError(errors, "2022-12-17", line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.ILLEGAL_COMMENT_TYPE, e.getValue().type().toName()), IssueSeverity.ERROR); 266 } else { 267 for (JsonElement c : e.getValue().asJsonArray()) { 268 if (!c.isJsonString()) { 269 logError(errors, "2022-12-17", line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.ILLEGAL_COMMENT_TYPE, c.type().toName()), IssueSeverity.ERROR); 270 } else { 271 element.getComments().add(c.asString()); 272 } 273 } 274 } 275 } else if (hasResourceType && "resourceType".equals(e.getName())) { 276 // nothing 277 } else { 278 JsonProperty p = getFoundJsonPropertyByName(e.getName(), children); 279 if (p != null) { 280 logError(errors, "2022-11-26", line(e.getValue()), col(e.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, e.getName()), IssueSeverity.ERROR); 281 } else { 282 logError(errors, ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getName()), IssueSeverity.ERROR); 283 } 284 } 285 } 286 } 287 } 288 } 289 290 private JsonProperty getFoundJsonPropertyByName(String name, List<JsonProperty> children) { 291 int hash = name.hashCode(); 292 for (JsonProperty p : children) { 293 if (p.getTag() == 1 && hash == p.getNameHash()) { 294 return p; 295 } 296 } 297 return null; 298 } 299 300 private JsonProperty getJsonPropertyByName(String name, List<JsonProperty> children) { 301 int hash = name.hashCode(); 302 for (JsonProperty p : children) { 303 if (p.getTag() == 0 && hash == p.getNameHash()) { 304 return p; 305 } 306 } 307 return null; 308 } 309 310 private JsonProperty getJsonPropertyByBaseName(String name, List<JsonProperty> children) { 311 for (JsonProperty p : children) { 312 if (p.getTag() == 0 && p.getName().startsWith(name)) { 313 return p; 314 } 315 } 316 return null; 317 } 318 319 private void processChildren(List<ValidationMessage> errors, String path, JsonObject object) { 320 for (JsonProperty p : object.getProperties()) { 321 if (p.isUnquotedName()) { 322 logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_NO_QUOTES, p.getName()), IssueSeverity.ERROR); 323 } 324 if (p.isNoComma()) { 325 logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_MISSING), IssueSeverity.ERROR); 326 } 327 } 328 } 329 330 public void parseChildItem(List<ValidationMessage> errors, String path, List<JsonProperty> children, Element context, Property property) { 331 JsonProperty jp = getJsonPropertyByName(property.getJsonName(), children); 332 if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) { 333 if (property.isJsonPrimitiveChoice()) { 334 if (jp != null) { 335 jp.setTag(1); 336 JsonElement je = jp.getValue(); 337 String type = getTypeFromJsonType(je); 338 if (type == null) { 339 logError(errors, ValidationMessage.NO_RULE_DATE, line(je), col(je), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE, describeType(je), property.getName(), property.typeSummary()), IssueSeverity.ERROR); 340 } else if (property.hasType(type)) { 341 Property np = new Property(property.getContext(), property.getDefinition(), property.getStructure(), property.getUtils(), property.getContextUtils(), type); 342 parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, np, path, property.getName(), false); 343 } else { 344 logError(errors, ValidationMessage.NO_RULE_DATE, line(je), col(je), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(je), property.getName(), type, property.typeSummary()), IssueSeverity.ERROR); 345 } 346 } 347 } else { 348 String baseName = property.getJsonName().substring(0, property.getName().length()-3); 349 jp = getJsonPropertyByBaseName(baseName, children); 350 JsonProperty jp1 = getJsonPropertyByBaseName("_"+baseName, children); 351 if (jp != null || jp1 != null) { 352 for (TypeRefComponent type : property.getDefinition().getType()) { 353 String eName = baseName + Utilities.capitalize(type.getWorkingCode()); 354 if ((jp != null && jp.getName().equals(eName) || (jp1 != null && jp1.getName().equals("_"+eName)))) { 355 if (!isPrimitive(type.getWorkingCode()) && jp != null) { 356 parseChildComplex(errors, path, jp, context, property, eName, false); 357 break; 358 } else if (isPrimitive(type.getWorkingCode()) && (jp != null || jp1 != null)) { 359 parseChildPrimitive(errors, jp, jp1, context, property, path, eName, false); 360 break; 361 } 362 } 363 } 364 } 365 } 366 } else if (property.isPrimitive(property.getType(null))) { 367 parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, property, path, property.getJsonName(), property.hasJsonName()); 368 } else if (jp != null) { 369 parseChildComplex(errors, path, jp, context, property, property.getJsonName(), property.hasJsonName()); 370 } 371 } 372 373 374 private String getTypeFromJsonType(JsonElement je) { 375 if (je.isJsonPrimitive()) { 376 JsonPrimitive p = je.asJsonPrimitive(); 377 if (p.isJsonString()) { 378 return "string"; 379 } else if (p.isJsonBoolean()) { 380 return "boolean"; 381 } else { 382 String s = p.asString(); 383 if (Utilities.isInteger(s)) { 384 return "integer"; 385 } else { 386 return "decimal"; 387 } 388 } 389 } else { 390 return null; 391 } 392 } 393 394 private void parseChildComplex(List<ValidationMessage> errors, String path, JsonProperty p, Element element, Property property, String name, boolean isJsonName) throws FHIRException { 395 String npath = path+"."+property.getName(); 396 String fpath = element.getPath()+"."+property.getName(); 397 if (p != null) { p.setTag(1); } 398 JsonElement e = p == null ? null : p.getValue(); 399 if (property.isList() && !property.isJsonKeyArray() && (e instanceof JsonArray)) { 400 JsonArray arr = (JsonArray) e; 401 if (arr.isExtraComma()) { 402 logError(errors, "2022-11-26", arr.getEnd().getLine(), arr.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR); 403 } 404 if (arr.size() == 0) { 405 if (property.canBeEmpty()) { 406 // nothing 407 } else { 408 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR); 409 } 410 } 411 int c = 0; 412 List<Property> properties = null; 413 for (JsonElement am : arr) { 414 properties = parseChildComplexInstance(errors, npath+"["+c+"]", fpath+"["+c+"]", element, property, name, am, c == 0 ? arr : null, path, properties); 415 c++; 416 } 417 } else if (property.isJsonKeyArray()) { 418 String code = property.getJsonKeyProperty(); 419 List<Property> properties = property.getChildProperties(element.getName(), null); 420 if (properties.size() != 2) { 421 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_CHILD_COUNT, propNames(properties)), IssueSeverity.ERROR); 422 } else { 423 Property propK = properties.get(0); 424 Property propV = properties.get(1); 425 if (!propK.getName().equals(code)) { 426 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_PROP_NAME, propNames(properties)), IssueSeverity.ERROR); 427 } else if (!propK.isPrimitive()) { 428 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_PROP_TYPE, propNames(properties), propK.typeSummary()), IssueSeverity.ERROR); 429 } else if (propV.isList()) { 430 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_NO_LIST, propV.getName()), IssueSeverity.ERROR); 431 } else if (propV.isChoice() && propV.getName().endsWith("[x]")) { 432 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_NO_CHOICE, propV.getName()), IssueSeverity.ERROR); 433 } else if (!(e instanceof JsonObject)) { 434 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(e)), IssueSeverity.ERROR); 435 } else { 436 JsonObject o = (JsonObject) e; 437 if (o.isExtraComma()) { 438 logError(errors, "2022-11-26", o.getEnd().getLine(), o.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR); 439 } 440 441 int i = 0; 442 Set<String> names = new HashSet<>(); 443 for (JsonProperty pv : o.getProperties()) { 444 if (names.contains(pv.getName())) { 445 logError(errors, "2022-11-26", line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, pv.getName()), IssueSeverity.ERROR); 446 } else { 447 names.add(pv.getName()); 448 pv.setTag(1); 449 } 450 // create an array entry 451 String npathArr = path+"."+property.getName()+"["+i+"]"; 452 String fpathArr = element.getPath()+"."+property.getName()+"["+i+"]"; 453 454 Element n = new Element(name, property).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON); 455 n.setPath(fpath); 456 element.getChildren().add(n); 457 // handle the key 458 String fpathKey = fpathArr+"."+propK.getName(); 459 Element nKey = new Element(code, propK).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON); 460 checkComments(errors, pv.getValue(), n, fpathArr); 461 nKey.setPath(fpathKey); 462 n.getChildren().add(nKey); 463 nKey.setValue(pv.getName()); 464 465 466 boolean ok = true; 467 Property pvl = propV; 468 if (propV.isJsonPrimitiveChoice()) { 469 ok = false; 470 String type = getTypeFromJsonType(pv.getValue()); 471 if (type == null) { 472 logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE, describeType(pv.getValue()), propV.getName(), propV.typeSummary()), IssueSeverity.ERROR); 473 } else if (propV.hasType(type)) { 474 pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), type); 475 ok = true; 476 } else { 477 logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(pv.getValue()), propV.getName(), type, propV.typeSummary()), IssueSeverity.ERROR); 478 } 479 } 480 if (ok) { 481 // handle the value 482 String npathV = npathArr+"."+pvl.getName(); 483 String fpathV = fpathArr+"."+pvl.getName(); 484 if (propV.isPrimitive(pvl.getType(null))) { 485 parseChildPrimitiveInstance(errors, n, pvl, pvl.getName(), false, npathV, fpathV, pv.getValue(), null); 486 } else if (pv.getValue() instanceof JsonObject || pv.getValue() instanceof JsonNull) { 487 parseChildComplexInstance(errors, npathV, fpathV, n, pvl, pvl.getName(), pv.getValue(), null, null, null); 488 } else { 489 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(pv.getValue())), IssueSeverity.ERROR); 490 } 491 } 492 i++; 493 } 494 } 495 } 496 } else { 497 if (property.isList()) { 498 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(e), name, path), IssueSeverity.ERROR); 499 } 500 parseChildComplexInstance(errors, npath, fpath, element, property, name, e, null, null, null); 501 } 502 } 503 504 private Object propNames(List<Property> properties) { 505 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 506 for (Property p: properties) { 507 b.append(p.getName()); 508 } 509 return b.toString(); 510 } 511 512 private List<Property> parseChildComplexInstance(List<ValidationMessage> errors, String npath, String fpath, Element element, Property property, String name, JsonElement e, JsonElement commentContext, String commentPath, List<Property> properties) throws FHIRException { 513 if (property.hasTypeSpecifier()) { 514 FHIRPathEngine fpe = new FHIRPathEngine(context); 515 String type = null; 516 String cond = null; 517 for (StringPair sp : property.getTypeSpecifiers()) { 518 if (fpe.evaluateToBoolean(null, baseElement, baseElement, element, fpe.parse(sp.getName()))) { 519 type = sp.getValue(); 520 cond = sp.getName(); 521 break; 522 } 523 } 524 if (type != null) { 525 StructureDefinition sd = context.fetchResource(StructureDefinition.class, type); 526 if (sd == null) { 527 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ILLEGAL_TYPE, type, cond), IssueSeverity.ERROR); 528 } else { 529 if (sd.getAbstract()) { 530 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ABSTRACT_TYPE, type, cond), IssueSeverity.ERROR); 531 } 532 property = property.cloneToType(sd); 533 } 534 } else { 535 StructureDefinition sd = context.fetchTypeDefinition(property.getType()); 536 if (sd == null) { 537 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_NM_ILLEGAL_TYPE, property.getType()), IssueSeverity.ERROR); 538 } else if (sd.getAbstract()) { 539 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_NM_ABSTRACT_TYPE, property.getType()), IssueSeverity.ERROR); 540 } 541 } 542 } 543 if (e instanceof JsonObject) { 544 JsonObject child = (JsonObject) e; 545 Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON); 546 n.setPath(fpath); 547 checkComments(errors, commentContext, n, commentPath); 548 checkObject(errors, child, n, npath); 549 element.getChildren().add(n); 550 if (property.isResource()) { 551 parseResource(errors, npath, child, n, property); 552 } else { 553 return parseChildren(errors, npath, child, n, false, properties); 554 } 555 } else if (property.isNullable() && e instanceof JsonNull) { 556 // we create an element marked as a null element so we know something was present 557 JsonNull child = (JsonNull) e; 558 Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON); 559 checkComments(errors, commentContext, n, commentPath); 560 checkComments(errors, child, n, fpath); 561 n.setPath(fpath); 562 element.getChildren().add(n); 563 n.setNull(true); 564 // nothing to do, it's ok, but we treat it like it doesn't exist 565 } else { 566 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE__NOT_, (property.isList() ? "an Array" : "an Object"), describe(e), name, npath), IssueSeverity.ERROR); 567 } 568 return null; 569 } 570 571 private String describe(JsonElement e) { 572 if (e instanceof JsonArray) { 573 return "an Array"; 574 } 575 if (e instanceof JsonObject) { 576 return "an Object"; 577 } 578 if (e instanceof JsonNull) { 579 return "a Null"; 580 } 581 if (e instanceof JsonPrimitive) { 582 return "a Primitive property"; 583 } 584 return null; 585 } 586 587 private String describeType(JsonElement e) { 588 return e.type().toName(); 589 } 590 591// JsonProperty main = children.containsKey(name) ? children.get(name) : null; 592// JsonProperty fork = children.containsKey("_"+name) ? children.get("_"+name) : null; 593 private void parseChildPrimitive(List<ValidationMessage> errors, JsonProperty main, JsonProperty fork, Element element, Property property, String path, String name, boolean isJsonName) throws FHIRException { 594 String npath = path+"."+property.getName(); 595 String fpath = element.getPath()+"."+property.getName(); 596 if (main != null) { main.setTag(1); } 597 if (fork != null) { fork.setTag(1); } 598 599 if (main != null && main.getValue().isJsonString() && main.isUnquotedValue()) { 600 logError(errors, "2022-11-26", line(main.getValue()), col(main.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_VALUE_NO_QUOTES, main.getName(), main.getValue().asString()), IssueSeverity.ERROR); 601 } 602 if (main != null || fork != null) { 603 if (property.isList()) { 604 boolean ok = true; 605 if (!(main == null || main.getValue() instanceof JsonArray)) { 606 logError(errors, ValidationMessage.NO_RULE_DATE, line(main.getValue()), col(main.getValue()), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main.getValue()), name, path), IssueSeverity.ERROR); 607 ok = false; 608 } 609 if (!(fork == null || fork.getValue() instanceof JsonArray)) { 610 logError(errors, ValidationMessage.NO_RULE_DATE, line(fork.getValue()), col(fork.getValue()), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main.getValue()), name, path), IssueSeverity.ERROR); 611 ok = false; 612 } 613 if (ok) { 614 JsonArray arr1 = (JsonArray) (main == null ? null : main.getValue()); 615 JsonArray arr2 = (JsonArray) (fork == null ? null : fork.getValue()); 616 if (arr1 != null && arr1.isExtraComma()) { 617 logError(errors, "2022-11-26", arr1.getEnd().getLine(), arr1.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR); 618 } 619 if (arr2 != null && arr2.isExtraComma()) { 620 logError(errors, "2022-11-26", arr2.getEnd().getLine(), arr2.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR); 621 } 622 623 for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) { 624 JsonElement m = arrI(arr1, i); 625 JsonElement f = arrI(arr2, i); 626 if (m != null && m.isJsonString() && arr1.isUnquoted(i)) { 627 logError(errors, "2022-11-26", line(m), col(m), path+"."+name+"["+i+"]", IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_VALUE_NO_QUOTES, "item", m.asString()), IssueSeverity.ERROR); 628 } 629 parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, m, f); 630 } 631 } 632 } else { 633 parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, main == null ? null : main.getValue(), fork == null ? null : fork.getValue()); 634 } 635 } 636 } 637 638 private JsonElement arrI(JsonArray arr, int i) { 639 return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i); 640 } 641 642 private int arrC(JsonArray arr) { 643 return arr == null ? 0 : arr.size(); 644 } 645 646 private void parseChildPrimitiveInstance(List<ValidationMessage> errors, Element element, Property property, String name, boolean isJsonName, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException { 647 if (main != null && !(main.isJsonBoolean() || main.isJsonNumber() || main.isJsonString())) { 648 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage( 649 I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR); 650 } else if (fork != null && !(fork instanceof JsonObject)) { 651 logError(errors, ValidationMessage.NO_RULE_DATE, line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(fork), name, npath), IssueSeverity.ERROR); 652 } else { 653 Element n = new Element(isJsonName ? property.getName() : name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork)).setFormat(FhirFormat.JSON); 654 if (main != null) { 655 checkComments(errors, main, n, npath); 656 } 657 if (fork != null) { 658 checkComments(errors, fork, n, npath); 659 } 660 n.setPath(fpath); 661 element.getChildren().add(n); 662 if (main != null) { 663 JsonPrimitive p = (JsonPrimitive) main; 664 n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+p.asString() : p.asString()); 665 if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) { 666 try { 667 XhtmlParser xhtml = new XhtmlParser(); 668 n.setXhtml(xhtml.setXmlMode(true).parse(n.getValue(), null).getDocumentElement()); 669 if (policy == ValidationPolicy.EVERYTHING) { 670 for (StringPair s : xhtml.getValidationIssues()) { 671 logError(errors, "2022-11-17", line(main), col(main), npath, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR); 672 } 673 } 674 } catch (Exception e) { 675 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR); 676 } 677 } 678 if (policy == ValidationPolicy.EVERYTHING) { 679 // now we cross-check the primitive format against the stated type 680 if (Utilities.existsInList(n.getType(), "boolean")) { 681 if (!p.isJsonBoolean()) { 682 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_BOOLEAN), IssueSeverity.ERROR); 683 } 684 } else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) { 685 if (!p.isJsonNumber()) 686 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_NUMBER), IssueSeverity.ERROR); 687 } else if (!p.isJsonString()) { 688 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_STRING), IssueSeverity.ERROR); 689 } 690 } 691 } 692 if (fork != null) { 693 JsonObject child = (JsonObject) fork; 694 checkObject(errors, child, n, npath); 695 parseChildren(errors, npath, child, n, false, null); 696 } 697 } 698 } 699 700 701 private void parseResource(List<ValidationMessage> errors, String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException { 702 JsonElement rt = res.get("resourceType"); 703 if (rt == null) { 704 logError(errors, ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL); 705 } else if (!rt.isJsonString()) { 706 logError(errors, "2022-11-26", line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL); 707 } else { 708 String name = rt.asString(); 709 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null)); 710 if (sd == null) { 711 logError(errors, ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL); 712 } else { 713 parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities()), SpecialElement.fromProperty(parent.getProperty()), elementProperty); 714 parent.setType(name); 715 parseChildren(errors, npath, res, parent, true, null); 716 } 717 } 718 if (res.isExtraComma()) { 719 logError(errors, "2022-11-26", res.getEnd().getLine(), res.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR); 720 } 721 722 } 723 724 private int line(JsonElement e) { 725 return e.getStart().getLine(); 726 } 727 728 private int col(JsonElement e) { 729 return e.getEnd().getCol(); 730 } 731 732 733 protected void prop(String name, String value, String link) throws IOException { 734 json.link(link); 735 if (name != null) 736 json.name(name); 737 json.value(value); 738 } 739 740 protected void open(String name, String link) throws IOException { 741 json.link(link); 742 if (name != null) 743 json.name(name); 744 json.beginObject(); 745 } 746 747 protected void close() throws IOException { 748 json.endObject(); 749 } 750 751 protected void openArray(String name, String link) throws IOException { 752 json.link(link); 753 if (name != null) 754 json.name(name); 755 json.beginArray(); 756 } 757 758 protected void closeArray() throws IOException { 759 json.endArray(); 760 } 761 762 763 @Override 764 public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException { 765 if (e.getPath() == null) { 766 e.populatePaths(null); 767 } 768 769 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 770 if (style == OutputStyle.CANONICAL) { 771 json = new JsonCreatorCanonical(osw); 772 } else if (style == OutputStyle.PRETTY) { 773 json = new JsonCreatorDirect(osw, true, allowComments); 774 } else { 775 json = new JsonCreatorDirect(osw, false, allowComments); 776 } 777 checkComposeComments(e); 778 json.beginObject(); 779 prop("resourceType", e.getType(), null); 780 Set<String> done = new HashSet<String>(); 781 for (Element child : e.getChildren()) { 782 compose(e.getName(), e, done, child); 783 } 784 json.endObject(); 785 json.finish(); 786 osw.flush(); 787 } 788 789 private void checkComposeComments(Element e) { 790 for (String s : e.getComments()) { 791 json.comment(s); 792 } 793 } 794 795 public void compose(Element e, JsonCreator json) throws Exception { 796 if (e.getPath() == null) { 797 e.populatePaths(null); 798 } 799 800 this.json = json; 801 checkComposeComments(e); 802 json.beginObject(); 803 804 prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty())); 805 Set<String> done = new HashSet<String>(); 806 for (Element child : e.getChildren()) { 807 compose(e.getName(), e, done, child); 808 } 809 json.endObject(); 810 json.finish(); 811 } 812 813 private void compose(String path, Element e, Set<String> done, Element child) throws IOException { 814 checkComposeComments(child); 815 if (wantCompose(path, child)) { 816 boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList(); 817 if (!isList) {// for specials, ignore the cardinality of the stated type 818 compose(path, child); 819 } else if (!done.contains(child.getName())) { 820 done.add(child.getName()); 821 List<Element> list = e.getChildrenByName(child.getName()); 822 composeList(path, list); 823 } 824 } 825 } 826 827 828 private void composeList(String path, List<Element> list) throws IOException { 829 // there will be at least one element 830 String name = list.get(0).getName(); 831 boolean complex = true; 832 if (list.get(0).isPrimitive()) { 833 boolean prim = false; 834 complex = false; 835 for (Element item : list) { 836 if (item.hasValue()) 837 prim = true; 838 if (item.hasChildren()) 839 complex = true; 840 } 841 if (prim) { 842 openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty())); 843 for (Element item : list) { 844 if (item.hasValue()) { 845 if (linkResolver != null && item.getProperty().isReference()) { 846 String ref = linkResolver.resolveReference(getReferenceForElement(item)); 847 if (ref != null) { 848 json.externalLink(ref); 849 } 850 } 851 primitiveValue(null, item); 852 } else 853 json.nullValue(); 854 } 855 closeArray(); 856 } 857 name = "_"+name; 858 } 859 if (complex) { 860 openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty())); 861 for (Element item : list) { 862 if (item.hasChildren()) { 863 open(null,null); 864 if (item.getProperty().isResource()) { 865 prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType())); 866 } 867 if (linkResolver != null && item.getProperty().isReference()) { 868 String ref = linkResolver.resolveReference(getReferenceForElement(item)); 869 if (ref != null) { 870 json.externalLink(ref); 871 } 872 } 873 Set<String> done = new HashSet<String>(); 874 for (Element child : item.getChildren()) { 875 compose(path+"."+name+"[]", item, done, child); 876 } 877 close(); 878 } else 879 json.nullValue(); 880 } 881 closeArray(); 882 } 883 } 884 885 private void primitiveValue(String name, Element item) throws IOException { 886 if (name != null) { 887 if (linkResolver != null) 888 json.link(linkResolver.resolveProperty(item.getProperty())); 889 json.name(name); 890 } 891 String type = item.getType(); 892 if (Utilities.existsInList(type, "boolean")) 893 json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false)); 894 else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) 895 json.value(new Integer(item.getValue())); 896 else if (Utilities.existsInList(type, "decimal")) 897 try { 898 json.value(new BigDecimal(item.getValue())); 899 } catch (Exception e) { 900 throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue())); 901 } 902 else 903 json.value(item.getValue()); 904 } 905 906 private void compose(String path, Element element) throws IOException { 907 String name = element.getName(); 908 if (element.isPrimitive() || isPrimitive(element.getType())) { 909 if (element.hasValue()) 910 primitiveValue(name, element); 911 name = "_"+name; 912 if (element.getType().equals("xhtml")) 913 json.anchor("end-xhtml"); 914 } 915 if (element.hasChildren()) { 916 open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); 917 if (element.getProperty().isResource()) { 918 prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType())); 919 } 920 if (linkResolver != null && element.getProperty().isReference()) { 921 String ref = linkResolver.resolveReference(getReferenceForElement(element)); 922 if (ref != null) { 923 json.externalLink(ref); 924 } 925 } 926 Set<String> done = new HashSet<String>(); 927 for (Element child : element.getChildren()) { 928 compose(path+"."+element.getName(), element, done, child); 929 } 930 close(); 931 } 932 } 933 934 935 public boolean isAllowComments() { 936 return allowComments; 937 } 938 939 public JsonParser setAllowComments(boolean allowComments) { 940 this.allowComments = allowComments; 941 return this; 942 } 943 944 945}