001package ca.uhn.fhir.parser; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 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 023import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 025import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 026import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; 027import ca.uhn.fhir.context.ConfigurationException; 028import ca.uhn.fhir.context.FhirContext; 029import ca.uhn.fhir.context.RuntimeChildContainedResources; 030import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; 031import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; 032import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; 033import ca.uhn.fhir.context.RuntimeResourceDefinition; 034import ca.uhn.fhir.i18n.Msg; 035import ca.uhn.fhir.model.api.ExtensionDt; 036import ca.uhn.fhir.model.api.IPrimitiveDatatype; 037import ca.uhn.fhir.model.api.IResource; 038import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; 039import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 040import ca.uhn.fhir.model.api.Tag; 041import ca.uhn.fhir.model.api.TagList; 042import ca.uhn.fhir.model.api.annotation.Child; 043import ca.uhn.fhir.model.base.composite.BaseCodingDt; 044import ca.uhn.fhir.model.base.composite.BaseContainedDt; 045import ca.uhn.fhir.model.primitive.IdDt; 046import ca.uhn.fhir.model.primitive.InstantDt; 047import ca.uhn.fhir.narrative.INarrativeGenerator; 048import ca.uhn.fhir.parser.json.JsonLikeArray; 049import ca.uhn.fhir.parser.json.JsonLikeObject; 050import ca.uhn.fhir.parser.json.JsonLikeStructure; 051import ca.uhn.fhir.parser.json.JsonLikeValue; 052import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; 053import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; 054import ca.uhn.fhir.parser.json.JsonLikeWriter; 055import ca.uhn.fhir.parser.json.jackson.JacksonStructure; 056import ca.uhn.fhir.rest.api.EncodingEnum; 057import ca.uhn.fhir.util.ElementUtil; 058import org.apache.commons.lang3.StringUtils; 059import org.apache.commons.lang3.Validate; 060import org.apache.commons.text.WordUtils; 061import org.hl7.fhir.instance.model.api.IBase; 062import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype; 063import org.hl7.fhir.instance.model.api.IBaseDecimalDatatype; 064import org.hl7.fhir.instance.model.api.IBaseExtension; 065import org.hl7.fhir.instance.model.api.IBaseHasExtensions; 066import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; 067import org.hl7.fhir.instance.model.api.IBaseIntegerDatatype; 068import org.hl7.fhir.instance.model.api.IBaseResource; 069import org.hl7.fhir.instance.model.api.IDomainResource; 070import org.hl7.fhir.instance.model.api.IIdType; 071import org.hl7.fhir.instance.model.api.INarrative; 072import org.hl7.fhir.instance.model.api.IPrimitiveType; 073 074import java.io.IOException; 075import java.io.Reader; 076import java.io.Writer; 077import java.math.BigDecimal; 078import java.util.ArrayList; 079import java.util.Collections; 080import java.util.Iterator; 081import java.util.List; 082import java.util.Map; 083 084import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE; 085import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE; 086import static org.apache.commons.lang3.StringUtils.defaultString; 087import static org.apache.commons.lang3.StringUtils.isBlank; 088import static org.apache.commons.lang3.StringUtils.isNotBlank; 089 090/** 091 * This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use 092 * {@link FhirContext#newJsonParser()} to get an instance. 093 */ 094public class JsonParser extends BaseParser implements IJsonLikeParser { 095 096 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParser.HeldExtension.class); 097 098 private boolean myPrettyPrint; 099 100 /** 101 * Do not use this constructor, the recommended way to obtain a new instance of the JSON parser is to invoke 102 * {@link FhirContext#newJsonParser()}. 103 * 104 * @param theParserErrorHandler 105 */ 106 public JsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { 107 super(theContext, theParserErrorHandler); 108 } 109 110 private boolean addToHeldComments(int valueIdx, List<String> theCommentsToAdd, ArrayList<ArrayList<String>> theListToAddTo) { 111 if (theCommentsToAdd.size() > 0) { 112 theListToAddTo.ensureCapacity(valueIdx); 113 while (theListToAddTo.size() <= valueIdx) { 114 theListToAddTo.add(null); 115 } 116 if (theListToAddTo.get(valueIdx) == null) { 117 theListToAddTo.set(valueIdx, new ArrayList<>()); 118 } 119 theListToAddTo.get(valueIdx).addAll(theCommentsToAdd); 120 return true; 121 } 122 return false; 123 } 124 125 private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem, 126 CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theContainingElement) { 127 boolean retVal = false; 128 if (ext.size() > 0) { 129 Boolean encodeExtension = null; 130 for (IBaseExtension<?, ?> next : ext) { 131 132 if (next.isEmpty()) { 133 continue; 134 } 135 136 // Make sure we respect _summary and _elements 137 if (encodeExtension == null) { 138 encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, theContainingElement); 139 } 140 141 if (encodeExtension) { 142 HeldExtension extension = new HeldExtension(next, theIsModifier, theChildElem, theParent); 143 list.ensureCapacity(valueIdx); 144 while (list.size() <= valueIdx) { 145 list.add(null); 146 } 147 ArrayList<HeldExtension> extensionList = list.get(valueIdx); 148 if (extensionList == null) { 149 extensionList = new ArrayList<>(); 150 list.set(valueIdx, extensionList); 151 } 152 extensionList.add(extension); 153 retVal = true; 154 } 155 } 156 } 157 return retVal; 158 } 159 160 private void addToHeldIds(int theValueIdx, ArrayList<String> theListToAddTo, String theId) { 161 theListToAddTo.ensureCapacity(theValueIdx); 162 while (theListToAddTo.size() <= theValueIdx) { 163 theListToAddTo.add(null); 164 } 165 if (theListToAddTo.get(theValueIdx) == null) { 166 theListToAddTo.set(theValueIdx, theId); 167 } 168 } 169 170 // private void assertObjectOfType(JsonLikeValue theResourceTypeObj, Object theValueType, String thePosition) { 171 // if (theResourceTypeObj == null) { 172 // throw new DataFormatException(Msg.code(1836) + "Invalid JSON content detected, missing required element: '" + thePosition + "'"); 173 // } 174 // 175 // if (theResourceTypeObj.getValueType() != theValueType) { 176 // throw new DataFormatException(Msg.code(1837) + "Invalid content of element " + thePosition + ", expected " + theValueType); 177 // } 178 // } 179 180 private void beginArray(JsonLikeWriter theEventWriter, String arrayName) throws IOException { 181 theEventWriter.beginArray(arrayName); 182 } 183 184 private void beginObject(JsonLikeWriter theEventWriter, String arrayName) throws IOException { 185 theEventWriter.beginObject(arrayName); 186 } 187 188 private JsonLikeWriter createJsonWriter(Writer theWriter) throws IOException { 189 JsonLikeStructure jsonStructure = new JacksonStructure(); 190 return jsonStructure.getJsonLikeWriter(theWriter); 191 } 192 193 public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException { 194 if (myPrettyPrint) { 195 theEventWriter.setPrettyPrint(myPrettyPrint); 196 } 197 theEventWriter.init(); 198 199 RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource); 200 encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext); 201 theEventWriter.flush(); 202 } 203 204 @Override 205 protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException { 206 JsonLikeWriter eventWriter = createJsonWriter(theWriter); 207 doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext); 208 eventWriter.close(); 209 } 210 211 @Override 212 public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) { 213 JsonLikeStructure jsonStructure = new JacksonStructure(); 214 jsonStructure.load(theReader); 215 216 T retVal = doParseResource(theResourceType, jsonStructure); 217 218 return retVal; 219 } 220 221 public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, JsonLikeStructure theJsonStructure) { 222 JsonLikeObject object = theJsonStructure.getRootObject(); 223 224 JsonLikeValue resourceTypeObj = object.get("resourceType"); 225 if (resourceTypeObj == null || !resourceTypeObj.isString() || isBlank(resourceTypeObj.getAsString())) { 226 throw new DataFormatException(Msg.code(1838) + "Invalid JSON content detected, missing required element: 'resourceType'"); 227 } 228 229 String resourceType = resourceTypeObj.getAsString(); 230 231 ParserState<? extends IBaseResource> state = ParserState.getPreResourceInstance(this, theResourceType, getContext(), true, getErrorHandler()); 232 state.enteringNewElement(null, resourceType); 233 234 parseChildren(object, state); 235 236 state.endingElement(); 237 state.endingElement(); 238 239 @SuppressWarnings("unchecked") 240 T retVal = (T) state.getObject(); 241 242 return retVal; 243 } 244 245 private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue, 246 BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem, 247 boolean theForceEmpty, EncodeContext theEncodeContext) throws IOException { 248 249 switch (theChildDef.getChildType()) { 250 case ID_DATATYPE: { 251 IIdType value = (IIdType) theNextValue; 252 String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue(); 253 if (isBlank(encodedValue)) { 254 break; 255 } 256 if (theChildName != null) { 257 write(theEventWriter, theChildName, encodedValue); 258 } else { 259 theEventWriter.write(encodedValue); 260 } 261 break; 262 } 263 case PRIMITIVE_DATATYPE: { 264 final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue; 265 final String valueStr = value.getValueAsString(); 266 if (isBlank(valueStr)) { 267 if (theForceEmpty) { 268 theEventWriter.writeNull(); 269 } 270 break; 271 } 272 273 // check for the common case first - String value types 274 Object valueObj = value.getValue(); 275 if (valueObj instanceof String) { 276 if (theChildName != null) { 277 theEventWriter.write(theChildName, valueStr); 278 } else { 279 theEventWriter.write(valueStr); 280 } 281 break; 282 } else if (valueObj instanceof Long) { 283 if (theChildName != null) { 284 theEventWriter.write(theChildName, (long) valueObj); 285 } else { 286 theEventWriter.write((long) valueObj); 287 } 288 break; 289 } 290 291 if (value instanceof IBaseIntegerDatatype) { 292 if (theChildName != null) { 293 write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue()); 294 } else { 295 theEventWriter.write(((IBaseIntegerDatatype) value).getValue()); 296 } 297 } else if (value instanceof IBaseDecimalDatatype) { 298 BigDecimal decimalValue = ((IBaseDecimalDatatype) value).getValue(); 299 decimalValue = new BigDecimal(decimalValue.toString()) { 300 private static final long serialVersionUID = 1L; 301 302 @Override 303 public String toString() { 304 return value.getValueAsString(); 305 } 306 }; 307 if (theChildName != null) { 308 write(theEventWriter, theChildName, decimalValue); 309 } else { 310 theEventWriter.write(decimalValue); 311 } 312 } else if (value instanceof IBaseBooleanDatatype) { 313 if (theChildName != null) { 314 write(theEventWriter, theChildName, ((IBaseBooleanDatatype) value).getValue()); 315 } else { 316 Boolean booleanValue = ((IBaseBooleanDatatype) value).getValue(); 317 if (booleanValue != null) { 318 theEventWriter.write(booleanValue.booleanValue()); 319 } 320 } 321 } else { 322 if (theChildName != null) { 323 write(theEventWriter, theChildName, valueStr); 324 } else { 325 theEventWriter.write(valueStr); 326 } 327 } 328 break; 329 } 330 case RESOURCE_BLOCK: 331 case COMPOSITE_DATATYPE: { 332 if (theChildName != null) { 333 theEventWriter.beginObject(theChildName); 334 } else { 335 theEventWriter.beginObject(); 336 } 337 encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem, theEncodeContext); 338 theEventWriter.endObject(); 339 break; 340 } 341 case CONTAINED_RESOURCE_LIST: 342 case CONTAINED_RESOURCES: { 343 List<IBaseResource> containedResources = getContainedResources().getContainedResources(); 344 if (containedResources.size() > 0) { 345 beginArray(theEventWriter, theChildName); 346 347 for (IBaseResource next : containedResources) { 348 IIdType resourceId = getContainedResources().getResourceId(next); 349 String value = resourceId.getValue(); 350 encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(value), theEncodeContext); 351 } 352 353 theEventWriter.endArray(); 354 } 355 break; 356 } 357 case PRIMITIVE_XHTML_HL7ORG: 358 case PRIMITIVE_XHTML: { 359 if (!isSuppressNarratives()) { 360 IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue; 361 if (theChildName != null) { 362 write(theEventWriter, theChildName, dt.getValueAsString()); 363 } else { 364 theEventWriter.write(dt.getValueAsString()); 365 } 366 } else { 367 if (theChildName != null) { 368 // do nothing 369 } else { 370 theEventWriter.writeNull(); 371 } 372 } 373 break; 374 } 375 case RESOURCE: 376 IBaseResource resource = (IBaseResource) theNextValue; 377 RuntimeResourceDefinition def = getContext().getResourceDefinition(resource); 378 379 theEncodeContext.pushPath(def.getName(), true); 380 encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, theContainedResource, theEncodeContext); 381 theEncodeContext.popPath(); 382 383 break; 384 case UNDECL_EXT: 385 default: 386 throw new IllegalStateException(Msg.code(1839) + "Should not have this state here: " + theChildDef.getChildType().name()); 387 } 388 389 } 390 391 private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter, 392 boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException { 393 394 { 395 String elementId = getCompositeElementId(theElement); 396 if (isNotBlank(elementId)) { 397 write(theEventWriter, "id", elementId); 398 } 399 } 400 401 boolean haveWrittenExtensions = false; 402 Iterable<CompositeChildElement> compositeChildElements = super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext); 403 for (CompositeChildElement nextChildElem : compositeChildElements) { 404 405 BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); 406 407 if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension") 408 || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) { 409 if (!haveWrittenExtensions) { 410 extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, getContext().getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent, theEncodeContext, theContainedResource); 411 haveWrittenExtensions = true; 412 } 413 continue; 414 } 415 416 if (nextChild instanceof RuntimeChildNarrativeDefinition) { 417 INarrativeGenerator gen = getContext().getNarrativeGenerator(); 418 if (gen != null) { 419 INarrative narr; 420 if (theResource instanceof IResource) { 421 narr = ((IResource) theResource).getText(); 422 } else if (theResource instanceof IDomainResource) { 423 narr = ((IDomainResource) theResource).getText(); 424 } else { 425 narr = null; 426 } 427 if (narr != null && narr.isEmpty()) { 428 gen.populateResourceNarrative(getContext(), theResource); 429 if (!narr.isEmpty()) { 430 RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; 431 String childName = nextChild.getChildNameByDatatype(child.getDatatype()); 432 BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); 433 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false, theEncodeContext); 434 continue; 435 } 436 } 437 } 438 } else if (nextChild instanceof RuntimeChildContainedResources) { 439 String childName = nextChild.getValidChildNames().iterator().next(); 440 BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName); 441 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false, theEncodeContext); 442 continue; 443 } 444 445 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 446 values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext); 447 448 if (values == null || values.isEmpty()) { 449 continue; 450 } 451 452 String currentChildName = null; 453 boolean inArray = false; 454 455 ArrayList<ArrayList<HeldExtension>> extensions = new ArrayList<>(0); 456 ArrayList<ArrayList<HeldExtension>> modifierExtensions = new ArrayList<>(0); 457 ArrayList<ArrayList<String>> comments = new ArrayList<>(0); 458 ArrayList<String> ids = new ArrayList<>(0); 459 460 int valueIdx = 0; 461 for (IBase nextValue : values) { 462 463 if (nextValue == null || nextValue.isEmpty()) { 464 if (nextValue instanceof BaseContainedDt) { 465 if (theContainedResource || getContainedResources().isEmpty()) { 466 continue; 467 } 468 } else { 469 continue; 470 } 471 } 472 473 BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue); 474 if (childNameAndDef == null) { 475 continue; 476 } 477 478 /* 479 * Often the two values below will be the same thing. There are cases though 480 * where they will not be. An example would be Observation.value, which is 481 * a choice type. If the value contains a Quantity, then: 482 * nextChildGenericName = "value" 483 * nextChildSpecificName = "valueQuantity" 484 */ 485 String nextChildSpecificName = childNameAndDef.getChildName(); 486 String nextChildGenericName = nextChild.getElementName(); 487 488 theEncodeContext.pushPath(nextChildGenericName, false); 489 490 BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef(); 491 boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE; 492 493 if ((childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && theContainedResource) { 494 continue; 495 } 496 497 boolean force = false; 498 if (primitive) { 499 if (nextValue instanceof ISupportsUndeclaredExtensions) { 500 List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions(); 501 force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); 502 503 ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions(); 504 force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); 505 } else { 506 if (nextValue instanceof IBaseHasExtensions) { 507 IBaseHasExtensions element = (IBaseHasExtensions) nextValue; 508 List<? extends IBaseExtension<?, ?>> ext = element.getExtension(); 509 force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); 510 } 511 if (nextValue instanceof IBaseHasModifierExtensions) { 512 IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue; 513 List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension(); 514 force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); 515 } 516 } 517 if (nextValue.hasFormatComment()) { 518 force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPre(), comments); 519 force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPost(), comments); 520 } 521 String elementId = getCompositeElementId(nextValue); 522 if (isNotBlank(elementId)) { 523 force = true; 524 addToHeldIds(valueIdx, ids, elementId); 525 } 526 } 527 528 if (currentChildName == null || !currentChildName.equals(nextChildSpecificName)) { 529 if (inArray) { 530 theEventWriter.endArray(); 531 } 532 BaseRuntimeChildDefinition replacedParentDefinition = nextChild.getReplacedParentDefinition(); 533 if (isMultipleCardinality(nextChild.getMax()) || (replacedParentDefinition != null && isMultipleCardinality(replacedParentDefinition.getMax()))) { 534 beginArray(theEventWriter, nextChildSpecificName); 535 inArray = true; 536 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext); 537 } else { 538 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, nextChildSpecificName, theContainedResource, nextChildElem, false, theEncodeContext); 539 } 540 currentChildName = nextChildSpecificName; 541 } else { 542 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext); 543 } 544 545 valueIdx++; 546 theEncodeContext.popPath(); 547 } 548 549 if (inArray) { 550 theEventWriter.endArray(); 551 } 552 553 554 if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) { 555 if (inArray) { 556 // If this is a repeatable field, the extensions go in an array too 557 beginArray(theEventWriter, '_' + currentChildName); 558 } else { 559 beginObject(theEventWriter, '_' + currentChildName); 560 } 561 562 for (int i = 0; i < valueIdx; i++) { 563 boolean haveContent = false; 564 565 List<HeldExtension> heldExts = Collections.emptyList(); 566 List<HeldExtension> heldModExts = Collections.emptyList(); 567 if (extensions.size() > i && extensions.get(i) != null && extensions.get(i).isEmpty() == false) { 568 haveContent = true; 569 heldExts = extensions.get(i); 570 } 571 572 if (modifierExtensions.size() > i && modifierExtensions.get(i) != null && modifierExtensions.get(i).isEmpty() == false) { 573 haveContent = true; 574 heldModExts = modifierExtensions.get(i); 575 } 576 577 ArrayList<String> nextComments; 578 if (comments.size() > i) { 579 nextComments = comments.get(i); 580 } else { 581 nextComments = null; 582 } 583 if (nextComments != null && nextComments.isEmpty() == false) { 584 haveContent = true; 585 } 586 587 String elementId = null; 588 if (ids.size() > i) { 589 elementId = ids.get(i); 590 haveContent |= isNotBlank(elementId); 591 } 592 593 if (!haveContent) { 594 theEventWriter.writeNull(); 595 } else { 596 if (inArray) { 597 theEventWriter.beginObject(); 598 } 599 if (isNotBlank(elementId)) { 600 write(theEventWriter, "id", elementId); 601 } 602 if (nextComments != null && !nextComments.isEmpty()) { 603 beginArray(theEventWriter, "fhir_comments"); 604 for (String next : nextComments) { 605 theEventWriter.write(next); 606 } 607 theEventWriter.endArray(); 608 } 609 writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext, theContainedResource); 610 if (inArray) { 611 theEventWriter.endObject(); 612 } 613 } 614 } 615 616 if (inArray) { 617 theEventWriter.endArray(); 618 } else { 619 theEventWriter.endObject(); 620 } 621 } 622 } 623 } 624 625 private boolean isMultipleCardinality(int maxCardinality) { 626 return maxCardinality > 1 || maxCardinality == Child.MAX_UNLIMITED; 627 } 628 629 private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException { 630 631 writeCommentsPreAndPost(theNextValue, theEventWriter); 632 encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent, theEncodeContext); 633 } 634 635 @Override 636 public void encodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException { 637 Validate.notNull(theResource, "theResource can not be null"); 638 Validate.notNull(theJsonLikeWriter, "theJsonLikeWriter can not be null"); 639 640 if (theResource.getStructureFhirVersionEnum() != getContext().getVersion().getVersion()) { 641 throw new IllegalArgumentException(Msg.code(1840) + "This parser is for FHIR version " + getContext().getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); 642 } 643 644 EncodeContext encodeContext = new EncodeContext(); 645 String resourceName = getContext().getResourceType(theResource); 646 encodeContext.pushPath(resourceName, true); 647 doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext); 648 } 649 650 private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull, 651 boolean theContainedResource, EncodeContext theEncodeContext) throws IOException { 652 IIdType resourceId = null; 653 654 if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) { 655 resourceId = theResource.getIdElement(); 656 if (theResource.getIdElement().getValue().startsWith("urn:")) { 657 resourceId = null; 658 } 659 } 660 661 if (!theContainedResource) { 662 if (!super.shouldEncodeResourceId(theResource, theEncodeContext)) { 663 resourceId = null; 664 } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) { 665 resourceId = getEncodeForceResourceId(); 666 } 667 } 668 669 encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId, theEncodeContext); 670 } 671 672 private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull, 673 boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws IOException { 674 675 if (!super.shouldEncodeResource(theResDef.getName())) { 676 return; 677 } 678 679 if (!theContainedResource) { 680 setContainedResources(getContext().newTerser().containResources(theResource)); 681 } 682 683 RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource); 684 685 if (theObjectNameOrNull == null) { 686 theEventWriter.beginObject(); 687 } else { 688 beginObject(theEventWriter, theObjectNameOrNull); 689 } 690 691 write(theEventWriter, "resourceType", resDef.getName()); 692 if (theResourceId != null && theResourceId.hasIdPart()) { 693 write(theEventWriter, "id", theResourceId.getIdPart()); 694 final List<HeldExtension> extensions = new ArrayList<>(0); 695 final List<HeldExtension> modifierExtensions = new ArrayList<>(0); 696 // Undeclared extensions 697 extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null, theEncodeContext, theContainedResource); 698 boolean haveExtension = false; 699 if (!extensions.isEmpty()) { 700 haveExtension = true; 701 } 702 703 if (theResourceId.hasFormatComment() || haveExtension) { 704 beginObject(theEventWriter, "_id"); 705 if (theResourceId.hasFormatComment()) { 706 writeCommentsPreAndPost(theResourceId, theEventWriter); 707 } 708 if (haveExtension) { 709 writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource); 710 } 711 theEventWriter.endObject(); 712 } 713 } 714 715 if (theResource instanceof IResource) { 716 IResource resource = (IResource) theResource; 717 // Object securityLabelRawObj = 718 719 List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS); 720 List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES); 721 profiles = super.getProfileTagsForEncoding(resource, profiles); 722 723 TagList tags = getMetaTagsForEncoding(resource, theEncodeContext); 724 InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); 725 IdDt resourceId = resource.getId(); 726 String versionIdPart = resourceId.getVersionIdPart(); 727 if (isBlank(versionIdPart)) { 728 versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); 729 } 730 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource); 731 732 if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) { 733 beginObject(theEventWriter, "meta"); 734 735 if (shouldEncodePath(resource, "meta.versionId")) { 736 writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart); 737 } 738 if (shouldEncodePath(resource, "meta.lastUpdated")) { 739 writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated); 740 } 741 742 if (profiles != null && profiles.isEmpty() == false) { 743 beginArray(theEventWriter, "profile"); 744 for (IIdType profile : profiles) { 745 if (profile != null && isNotBlank(profile.getValue())) { 746 theEventWriter.write(profile.getValue()); 747 } 748 } 749 theEventWriter.endArray(); 750 } 751 752 if (securityLabels.isEmpty() == false) { 753 beginArray(theEventWriter, "security"); 754 for (BaseCodingDt securityLabel : securityLabels) { 755 theEventWriter.beginObject(); 756 theEncodeContext.pushPath("security", false); 757 encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext); 758 theEncodeContext.popPath(); 759 theEventWriter.endObject(); 760 } 761 theEventWriter.endArray(); 762 } 763 764 if (tags != null && tags.isEmpty() == false) { 765 beginArray(theEventWriter, "tag"); 766 for (Tag tag : tags) { 767 if (tag.isEmpty()) { 768 continue; 769 } 770 theEventWriter.beginObject(); 771 writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme()); 772 writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm()); 773 writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel()); 774 theEventWriter.endObject(); 775 } 776 theEventWriter.endArray(); 777 } 778 779 addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext); 780 781 theEventWriter.endObject(); // end meta 782 } 783 } 784 785 encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext); 786 787 theEventWriter.endObject(); 788 } 789 790 791 private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource, 792 boolean theContainedResource, 793 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys, 794 RuntimeResourceDefinition resDef, 795 JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException { 796 if (extensionMetadataKeys.isEmpty()) { 797 return; 798 } 799 800 ExtensionDt metaResource = new ExtensionDt(); 801 for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) { 802 metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue()); 803 } 804 encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext); 805 } 806 807 /** 808 * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object 809 * called _name): resource extensions, and extension extensions 810 */ 811 private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef, 812 IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { 813 List<HeldExtension> extensions = new ArrayList<>(0); 814 List<HeldExtension> modifierExtensions = new ArrayList<>(0); 815 816 // Undeclared extensions 817 extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent, theEncodeContext, theContainedResource); 818 819 // Declared extensions 820 if (theElementDef != null) { 821 extractDeclaredExtensions(theElement, theElementDef, extensions, modifierExtensions, theChildElem); 822 } 823 824 // Write the extensions 825 writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource); 826 } 827 828 private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, 829 CompositeChildElement theChildElem) { 830 for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) { 831 for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) { 832 if (nextValue != null) { 833 if (nextValue.isEmpty()) { 834 continue; 835 } 836 extensions.add(new HeldExtension(nextDef, nextValue, theChildElem)); 837 } 838 } 839 } 840 for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) { 841 for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) { 842 if (nextValue != null) { 843 if (nextValue.isEmpty()) { 844 continue; 845 } 846 modifierExtensions.add(new HeldExtension(nextDef, nextValue, theChildElem)); 847 } 848 } 849 } 850 } 851 852 private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem, 853 CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) { 854 if (theElement instanceof ISupportsUndeclaredExtensions) { 855 ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement; 856 List<ExtensionDt> ext = element.getUndeclaredExtensions(); 857 for (ExtensionDt next : ext) { 858 if (next == null || next.isEmpty()) { 859 continue; 860 } 861 extensions.add(new HeldExtension(next, false, theChildElem, theParent)); 862 } 863 864 ext = element.getUndeclaredModifierExtensions(); 865 for (ExtensionDt next : ext) { 866 if (next == null || next.isEmpty()) { 867 continue; 868 } 869 modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent)); 870 } 871 } else { 872 if (theElement instanceof IBaseHasExtensions) { 873 IBaseHasExtensions element = (IBaseHasExtensions) theElement; 874 List<? extends IBaseExtension<?, ?>> ext = element.getExtension(); 875 Boolean encodeExtension = null; 876 for (IBaseExtension<?, ?> next : ext) { 877 if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { 878 continue; 879 } 880 881 // Make sure we respect _elements and _summary 882 if (encodeExtension == null) { 883 encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, element); 884 } 885 if (encodeExtension) { 886 HeldExtension extension = new HeldExtension(next, false, theChildElem, theParent); 887 extensions.add(extension); 888 } 889 890 } 891 } 892 if (theElement instanceof IBaseHasModifierExtensions) { 893 IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) theElement; 894 List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension(); 895 for (IBaseExtension<?, ?> next : ext) { 896 if (next == null || next.isEmpty()) { 897 continue; 898 } 899 900 HeldExtension extension = new HeldExtension(next, true, theChildElem, theParent); 901 modifierExtensions.add(extension); 902 } 903 } 904 } 905 } 906 907 private boolean isEncodeExtension(CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theElement) { 908 BaseRuntimeElementDefinition<?> runtimeElementDefinition = getContext().getElementDefinition(theElement.getClass()); 909 boolean retVal = true; 910 if (runtimeElementDefinition instanceof BaseRuntimeElementCompositeDefinition) { 911 BaseRuntimeElementCompositeDefinition definition = (BaseRuntimeElementCompositeDefinition) runtimeElementDefinition; 912 BaseRuntimeChildDefinition childDef = definition.getChildByName("extension"); 913 CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext); 914 retVal = c.shouldBeEncoded(theContainedResource); 915 } 916 return retVal; 917 } 918 919 @Override 920 public EncodingEnum getEncoding() { 921 return EncodingEnum.JSON; 922 } 923 924 private JsonLikeArray grabJsonArray(JsonLikeObject theObject, String nextName, String thePosition) { 925 JsonLikeValue object = theObject.get(nextName); 926 if (object == null || object.isNull()) { 927 return null; 928 } 929 if (!object.isArray()) { 930 throw new DataFormatException(Msg.code(1841) + "Syntax error parsing JSON FHIR structure: Expected ARRAY at element '" + thePosition + "', found '" + object.getJsonType() + "'"); 931 } 932 return object.getAsArray(); 933 } 934 935 private void parseAlternates(JsonLikeValue theAlternateVal, ParserState<?> theState, String theElementName, String theAlternateName) { 936 if (theAlternateVal == null || theAlternateVal.isNull()) { 937 return; 938 } 939 940 if (theAlternateVal.isArray()) { 941 JsonLikeArray array = theAlternateVal.getAsArray(); 942 if (array.size() > 1) { 943 throw new DataFormatException(Msg.code(1842) + "Unexpected array of length " + array.size() + " (expected 0 or 1) for element: " + theElementName); 944 } 945 if (array.size() == 0) { 946 return; 947 } 948 parseAlternates(array.get(0), theState, theElementName, theAlternateName); 949 return; 950 } 951 952 JsonLikeValue alternateVal = theAlternateVal; 953 if (alternateVal.isObject() == false) { 954 getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.OBJECT, null, alternateVal.getJsonType(), null); 955 return; 956 } 957 958 JsonLikeObject alternate = alternateVal.getAsObject(); 959 960 for (Iterator<String> keyIter = alternate.keyIterator(); keyIter.hasNext(); ) { 961 String nextKey = keyIter.next(); 962 JsonLikeValue nextVal = alternate.get(nextKey); 963 if ("extension".equals(nextKey)) { 964 boolean isModifier = false; 965 JsonLikeArray array = nextVal.getAsArray(); 966 parseExtension(theState, array, isModifier); 967 } else if ("modifierExtension".equals(nextKey)) { 968 boolean isModifier = true; 969 JsonLikeArray array = nextVal.getAsArray(); 970 parseExtension(theState, array, isModifier); 971 } else if ("id".equals(nextKey)) { 972 if (nextVal.isString()) { 973 theState.attributeValue("id", nextVal.getAsString()); 974 } else { 975 getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, nextVal.getJsonType(), nextVal.getDataType()); 976 } 977 } else if ("fhir_comments".equals(nextKey)) { 978 parseFhirComments(nextVal, theState); 979 } 980 } 981 } 982 983 private void parseChildren(JsonLikeObject theObject, ParserState<?> theState) { 984 int allUnderscoreNames = 0; 985 int handledUnderscoreNames = 0; 986 987 for (Iterator<String> keyIter = theObject.keyIterator(); keyIter.hasNext(); ) { 988 String nextName = keyIter.next(); 989 if ("resourceType".equals(nextName)) { 990 continue; 991 } else if ("extension".equals(nextName)) { 992 JsonLikeArray array = grabJsonArray(theObject, nextName, "extension"); 993 parseExtension(theState, array, false); 994 continue; 995 } else if ("modifierExtension".equals(nextName)) { 996 JsonLikeArray array = grabJsonArray(theObject, nextName, "modifierExtension"); 997 parseExtension(theState, array, true); 998 continue; 999 } else if (nextName.equals("fhir_comments")) { 1000 parseFhirComments(theObject.get(nextName), theState); 1001 continue; 1002 } else if (nextName.charAt(0) == '_') { 1003 allUnderscoreNames++; 1004 continue; 1005 } 1006 1007 JsonLikeValue nextVal = theObject.get(nextName); 1008 String alternateName = '_' + nextName; 1009 JsonLikeValue alternateVal = theObject.get(alternateName); 1010 if (alternateVal != null) { 1011 handledUnderscoreNames++; 1012 } 1013 1014 parseChildren(theState, nextName, nextVal, alternateVal, alternateName, false); 1015 1016 } 1017 1018 // if (elementId != null) { 1019 // IBase object = (IBase) theState.getObject(); 1020 // if (object instanceof IIdentifiableElement) { 1021 // ((IIdentifiableElement) object).setElementSpecificId(elementId); 1022 // } else if (object instanceof IBaseResource) { 1023 // ((IBaseResource) object).getIdElement().setValue(elementId); 1024 // } 1025 // } 1026 1027 /* 1028 * This happens if an element has an extension but no actual value. I.e. 1029 * if a resource has a "_status" element but no corresponding "status" 1030 * element. This could be used to handle a null value with an extension 1031 * for example. 1032 */ 1033 if (allUnderscoreNames > handledUnderscoreNames) { 1034 for (Iterator<String> keyIter = theObject.keyIterator(); keyIter.hasNext(); ) { 1035 String alternateName = keyIter.next(); 1036 if (alternateName.startsWith("_") && alternateName.length() > 1) { 1037 JsonLikeValue nextValue = theObject.get(alternateName); 1038 if (nextValue != null) { 1039 if (nextValue.isObject()) { 1040 String nextName = alternateName.substring(1); 1041 if (theObject.get(nextName) == null) { 1042 theState.enteringNewElement(null, nextName); 1043 parseAlternates(nextValue, theState, alternateName, alternateName); 1044 theState.endingElement(); 1045 } 1046 } else { 1047 getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null); 1048 } 1049 } 1050 } 1051 } 1052 } 1053 1054 } 1055 1056 private void parseChildren(ParserState<?> theState, String theName, JsonLikeValue theJsonVal, JsonLikeValue theAlternateVal, String theAlternateName, boolean theInArray) { 1057 if (theName.equals("id")) { 1058 if (!theJsonVal.isString()) { 1059 getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, theJsonVal.getJsonType(), theJsonVal.getDataType()); 1060 } 1061 } 1062 1063 if (theJsonVal.isArray()) { 1064 JsonLikeArray nextArray = theJsonVal.getAsArray(); 1065 1066 JsonLikeValue alternateVal = theAlternateVal; 1067 if (alternateVal != null && alternateVal.isArray() == false) { 1068 getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.ARRAY, null, alternateVal.getJsonType(), null); 1069 alternateVal = null; 1070 } 1071 1072 JsonLikeArray nextAlternateArray = JsonLikeValue.asArray(alternateVal); // could be null 1073 for (int i = 0; i < nextArray.size(); i++) { 1074 JsonLikeValue nextObject = nextArray.get(i); 1075 JsonLikeValue nextAlternate = null; 1076 if (nextAlternateArray != null && nextAlternateArray.size() >= (i + 1)) { 1077 nextAlternate = nextAlternateArray.get(i); 1078 } 1079 parseChildren(theState, theName, nextObject, nextAlternate, theAlternateName, true); 1080 } 1081 } else if (theJsonVal.isObject()) { 1082 if (!theInArray && theState.elementIsRepeating(theName)) { 1083 getErrorHandler().incorrectJsonType(null, theName, ValueType.ARRAY, null, ValueType.OBJECT, null); 1084 } 1085 1086 theState.enteringNewElement(null, theName); 1087 parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName); 1088 JsonLikeObject nextObject = theJsonVal.getAsObject(); 1089 boolean preResource = false; 1090 if (theState.isPreResource()) { 1091 JsonLikeValue resType = nextObject.get("resourceType"); 1092 if (resType == null || !resType.isString()) { 1093 throw new DataFormatException(Msg.code(1843) + "Missing required element 'resourceType' from JSON resource object, unable to parse"); 1094 } 1095 theState.enteringNewElement(null, resType.getAsString()); 1096 preResource = true; 1097 } 1098 parseChildren(nextObject, theState); 1099 if (preResource) { 1100 theState.endingElement(); 1101 } 1102 theState.endingElement(); 1103 } else if (theJsonVal.isNull()) { 1104 theState.enteringNewElement(null, theName); 1105 parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName); 1106 theState.endingElement(); 1107 } else { 1108 // must be a SCALAR 1109 theState.enteringNewElement(null, theName); 1110 String asString = theJsonVal.getAsString(); 1111 theState.attributeValue("value", asString); 1112 parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName); 1113 theState.endingElement(); 1114 } 1115 } 1116 1117 private void parseExtension(ParserState<?> theState, JsonLikeArray theValues, boolean theIsModifier) { 1118 int allUnderscoreNames = 0; 1119 int handledUnderscoreNames = 0; 1120 1121 for (int i = 0; i < theValues.size(); i++) { 1122 JsonLikeObject nextExtObj = JsonLikeValue.asObject(theValues.get(i)); 1123 JsonLikeValue jsonElement = nextExtObj.get("url"); 1124 String url; 1125 if (null == jsonElement || !(jsonElement.isScalar())) { 1126 String parentElementName; 1127 if (theIsModifier) { 1128 parentElementName = "modifierExtension"; 1129 } else { 1130 parentElementName = "extension"; 1131 } 1132 getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName(parentElementName), "url"); 1133 url = null; 1134 } else { 1135 url = getExtensionUrl(jsonElement.getAsString()); 1136 } 1137 theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl()); 1138 for (Iterator<String> keyIter = nextExtObj.keyIterator(); keyIter.hasNext(); ) { 1139 String next = keyIter.next(); 1140 if ("url".equals(next)) { 1141 continue; 1142 } else if ("extension".equals(next)) { 1143 JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next)); 1144 parseExtension(theState, jsonVal, false); 1145 } else if ("modifierExtension".equals(next)) { 1146 JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next)); 1147 parseExtension(theState, jsonVal, true); 1148 } else if (next.charAt(0) == '_') { 1149 allUnderscoreNames++; 1150 continue; 1151 } else { 1152 JsonLikeValue jsonVal = nextExtObj.get(next); 1153 String alternateName = '_' + next; 1154 JsonLikeValue alternateVal = nextExtObj.get(alternateName); 1155 if (alternateVal != null) { 1156 handledUnderscoreNames++; 1157 } 1158 parseChildren(theState, next, jsonVal, alternateVal, alternateName, false); 1159 } 1160 } 1161 1162 /* 1163 * This happens if an element has an extension but no actual value. I.e. 1164 * if a resource has a "_status" element but no corresponding "status" 1165 * element. This could be used to handle a null value with an extension 1166 * for example. 1167 */ 1168 if (allUnderscoreNames > handledUnderscoreNames) { 1169 for (Iterator<String> keyIter = nextExtObj.keyIterator(); keyIter.hasNext(); ) { 1170 String alternateName = keyIter.next(); 1171 if (alternateName.startsWith("_") && alternateName.length() > 1) { 1172 JsonLikeValue nextValue = nextExtObj.get(alternateName); 1173 if (nextValue != null) { 1174 if (nextValue.isObject()) { 1175 String nextName = alternateName.substring(1); 1176 if (nextExtObj.get(nextName) == null) { 1177 theState.enteringNewElement(null, nextName); 1178 parseAlternates(nextValue, theState, alternateName, alternateName); 1179 theState.endingElement(); 1180 } 1181 } else { 1182 getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null); 1183 } 1184 } 1185 } 1186 } 1187 } 1188 theState.endingElement(); 1189 } 1190 } 1191 1192 private void parseFhirComments(JsonLikeValue theObject, ParserState<?> theState) { 1193 if (theObject.isArray()) { 1194 JsonLikeArray comments = theObject.getAsArray(); 1195 for (int i = 0; i < comments.size(); i++) { 1196 JsonLikeValue nextComment = comments.get(i); 1197 if (nextComment.isString()) { 1198 String commentText = nextComment.getAsString(); 1199 if (commentText != null) { 1200 theState.commentPre(commentText); 1201 } 1202 } 1203 } 1204 } 1205 } 1206 1207 @Override 1208 public <T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure) throws DataFormatException { 1209 1210 /***************************************************** 1211 * ************************************************* * 1212 * ** NOTE: this duplicates most of the code in ** * 1213 * ** BaseParser.parseResource(Class<T>, Reader). ** * 1214 * ** Unfortunately, there is no way to avoid ** * 1215 * ** this without doing some refactoring of the ** * 1216 * ** BaseParser class. ** * 1217 * ************************************************* * 1218 *****************************************************/ 1219 1220 /* 1221 * We do this so that the context can verify that the structure is for 1222 * the correct FHIR version 1223 */ 1224 if (theResourceType != null) { 1225 getContext().getResourceDefinition(theResourceType); 1226 } 1227 1228 // Actually do the parse 1229 T retVal = doParseResource(theResourceType, theJsonLikeStructure); 1230 1231 RuntimeResourceDefinition def = getContext().getResourceDefinition(retVal); 1232 if ("Bundle".equals(def.getName())) { 1233 1234 BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); 1235 BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); 1236 List<IBase> entries = entryChild.getAccessor().getValues(retVal); 1237 if (entries != null) { 1238 for (IBase nextEntry : entries) { 1239 1240 /** 1241 * If Bundle.entry.fullUrl is populated, set the resource ID to that 1242 */ 1243 // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the 1244 // fullUrl idPart 1245 BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl"); 1246 if (fullUrlChild == null) { 1247 continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2 1248 } 1249 List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry); 1250 if (fullUrl != null && !fullUrl.isEmpty()) { 1251 IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0); 1252 if (value.isEmpty() == false) { 1253 List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry); 1254 if (entryResources != null && entryResources.size() > 0) { 1255 IBaseResource res = (IBaseResource) entryResources.get(0); 1256 String versionId = res.getIdElement().getVersionIdPart(); 1257 res.setId(value.getValueAsString()); 1258 if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) { 1259 res.setId(res.getIdElement().withVersion(versionId)); 1260 } 1261 } 1262 } 1263 } 1264 1265 } 1266 } 1267 1268 } 1269 1270 return retVal; 1271 } 1272 1273 @Override 1274 public IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException { 1275 return parseResource(null, theJsonLikeStructure); 1276 } 1277 1278 @Override 1279 public IParser setPrettyPrint(boolean thePrettyPrint) { 1280 myPrettyPrint = thePrettyPrint; 1281 return this; 1282 } 1283 1284 private void write(JsonLikeWriter theEventWriter, String theChildName, Boolean theValue) throws IOException { 1285 if (theValue != null) { 1286 theEventWriter.write(theChildName, theValue.booleanValue()); 1287 } 1288 } 1289 1290 // private void parseExtensionInDstu2Style(boolean theModifier, ParserState<?> theState, String 1291 // theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) { 1292 // String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl); 1293 // theState.enteringNewElementExtension(null, extUrl, theModifier); 1294 // 1295 // for (int extIdx = 0; extIdx < theValues.size(); extIdx++) { 1296 // JsonObject nextExt = theValues.getJsonObject(extIdx); 1297 // for (String nextKey : nextExt.keySet()) { 1298 // // if (nextKey.startsWith("value") && nextKey.length() > 5 && 1299 // // myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(nextKey) != null) { 1300 // JsonElement jsonVal = nextExt.get(nextKey); 1301 // if (jsonVal.getValueType() == ValueType.ARRAY) { 1302 // /* 1303 // * Extension children which are arrays are sub-extensions. Any other value type should be treated as a value. 1304 // */ 1305 // JsonArray arrayValue = (JsonArray) jsonVal; 1306 // parseExtensionInDstu2Style(theModifier, theState, extUrl, nextKey, arrayValue); 1307 // } else { 1308 // parseChildren(theState, nextKey, jsonVal, null, null); 1309 // } 1310 // } 1311 // } 1312 // 1313 // theState.endingElement(); 1314 // } 1315 1316 private void write(JsonLikeWriter theEventWriter, String theChildName, BigDecimal theDecimalValue) throws IOException { 1317 theEventWriter.write(theChildName, theDecimalValue); 1318 } 1319 1320 private void write(JsonLikeWriter theEventWriter, String theChildName, Integer theValue) throws IOException { 1321 theEventWriter.write(theChildName, theValue); 1322 } 1323 1324 private void writeCommentsPreAndPost(IBase theNextValue, JsonLikeWriter theEventWriter) throws IOException { 1325 if (theNextValue.hasFormatComment()) { 1326 beginArray(theEventWriter, "fhir_comments"); 1327 List<String> pre = theNextValue.getFormatCommentsPre(); 1328 if (pre.isEmpty() == false) { 1329 for (String next : pre) { 1330 theEventWriter.write(next); 1331 } 1332 } 1333 List<String> post = theNextValue.getFormatCommentsPost(); 1334 if (post.isEmpty() == false) { 1335 for (String next : post) { 1336 theEventWriter.write(next); 1337 } 1338 } 1339 theEventWriter.endArray(); 1340 } 1341 } 1342 1343 private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions, 1344 List<HeldExtension> modifierExtensions, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { 1345 // Write Extensions 1346 if (extensions.isEmpty() == false) { 1347 theEncodeContext.pushPath("extension", false); 1348 beginArray(theEventWriter, "extension"); 1349 for (HeldExtension next : extensions) { 1350 next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource); 1351 } 1352 theEventWriter.endArray(); 1353 theEncodeContext.popPath(); 1354 } 1355 1356 // Write ModifierExtensions 1357 if (modifierExtensions.isEmpty() == false) { 1358 theEncodeContext.pushPath("modifierExtension", false); 1359 beginArray(theEventWriter, "modifierExtension"); 1360 for (HeldExtension next : modifierExtensions) { 1361 next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource); 1362 } 1363 theEventWriter.endArray(); 1364 theEncodeContext.popPath(); 1365 } 1366 } 1367 1368 private void writeOptionalTagWithTextNode(JsonLikeWriter theEventWriter, String theElementName, IPrimitiveDatatype<?> thePrimitive) throws IOException { 1369 if (thePrimitive == null) { 1370 return; 1371 } 1372 String str = thePrimitive.getValueAsString(); 1373 writeOptionalTagWithTextNode(theEventWriter, theElementName, str); 1374 } 1375 1376 private void writeOptionalTagWithTextNode(JsonLikeWriter theEventWriter, String theElementName, String theValue) throws IOException { 1377 if (StringUtils.isNotBlank(theValue)) { 1378 write(theEventWriter, theElementName, theValue); 1379 } 1380 } 1381 1382 private class HeldExtension implements Comparable<HeldExtension> { 1383 1384 private CompositeChildElement myChildElem; 1385 private RuntimeChildDeclaredExtensionDefinition myDef; 1386 private boolean myModifier; 1387 private IBaseExtension<?, ?> myUndeclaredExtension; 1388 private IBase myValue; 1389 private CompositeChildElement myParent; 1390 1391 public HeldExtension(IBaseExtension<?, ?> theUndeclaredExtension, boolean theModifier, CompositeChildElement theChildElem, CompositeChildElement theParent) { 1392 assert theUndeclaredExtension != null; 1393 myUndeclaredExtension = theUndeclaredExtension; 1394 myModifier = theModifier; 1395 myChildElem = theChildElem; 1396 myParent = theParent; 1397 } 1398 1399 public HeldExtension(RuntimeChildDeclaredExtensionDefinition theDef, IBase theValue, CompositeChildElement theChildElem) { 1400 assert theDef != null; 1401 assert theValue != null; 1402 myDef = theDef; 1403 myValue = theValue; 1404 myChildElem = theChildElem; 1405 } 1406 1407 @Override 1408 public int compareTo(HeldExtension theArg0) { 1409 String url1 = myDef != null ? myDef.getExtensionUrl() : myUndeclaredExtension.getUrl(); 1410 String url2 = theArg0.myDef != null ? theArg0.myDef.getExtensionUrl() : theArg0.myUndeclaredExtension.getUrl(); 1411 url1 = defaultString(getExtensionUrl(url1)); 1412 url2 = defaultString(getExtensionUrl(url2)); 1413 return url1.compareTo(url2); 1414 } 1415 1416 private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { 1417 if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) { 1418 final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0); 1419 final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0); 1420 // Undeclared extensions 1421 extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null, theEncodeContext, theContainedResource); 1422 // Declared extensions 1423 if (def != null) { 1424 extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent); 1425 } 1426 boolean haveContent = false; 1427 if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) { 1428 haveContent = true; 1429 } 1430 if (haveContent) { 1431 beginObject(theEventWriter, '_' + childName); 1432 writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource); 1433 theEventWriter.endObject(); 1434 } 1435 } 1436 } 1437 1438 public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { 1439 if (myUndeclaredExtension != null) { 1440 writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext, theContainedResource); 1441 } else { 1442 theEventWriter.beginObject(); 1443 1444 writeCommentsPreAndPost(myValue, theEventWriter); 1445 1446 JsonParser.write(theEventWriter, "url", getExtensionUrl(myDef.getExtensionUrl())); 1447 1448 /* 1449 * This makes sure that even if the extension contains a reference to a contained 1450 * resource which has a HAPI-assigned ID we'll still encode that ID. 1451 * 1452 * See #327 1453 */ 1454 List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem, theEncodeContext); 1455 1456 // // Check for undeclared extensions on the declared extension 1457 // // (grrrrrr....) 1458 // if (myValue instanceof ISupportsUndeclaredExtensions) { 1459 // ISupportsUndeclaredExtensions value = (ISupportsUndeclaredExtensions)myValue; 1460 // List<ExtensionDt> exts = value.getUndeclaredExtensions(); 1461 // if (exts.size() > 0) { 1462 // ArrayList<IBase> newValueList = new ArrayList<IBase>(); 1463 // newValueList.addAll(preProcessedValue); 1464 // newValueList.addAll(exts); 1465 // preProcessedValue = newValueList; 1466 // } 1467 // } 1468 1469 myValue = preProcessedValue.get(0); 1470 1471 BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass()); 1472 if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) { 1473 extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext, theContainedResource); 1474 } else { 1475 String childName = myDef.getChildNameByDatatype(myValue.getClass()); 1476 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext); 1477 managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext, theContainedResource); 1478 } 1479 1480 theEventWriter.endObject(); 1481 } 1482 } 1483 1484 private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension<?, ?> ext, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { 1485 IBase value = ext.getValue(); 1486 final String extensionUrl = getExtensionUrl(ext.getUrl()); 1487 1488 theEventWriter.beginObject(); 1489 1490 writeCommentsPreAndPost(myUndeclaredExtension, theEventWriter); 1491 1492 String elementId = getCompositeElementId(ext); 1493 if (isNotBlank(elementId)) { 1494 JsonParser.write(theEventWriter, "id", getCompositeElementId(ext)); 1495 } 1496 1497 if (isBlank(extensionUrl)) { 1498 ParseLocation loc = new ParseLocation(theEncodeContext.toString()); 1499 getErrorHandler().missingRequiredElement(loc, "url"); 1500 } 1501 1502 JsonParser.write(theEventWriter, "url", extensionUrl); 1503 1504 boolean noValue = value == null || value.isEmpty(); 1505 if (noValue && ext.getExtension().isEmpty()) { 1506 1507 ParseLocation loc = new ParseLocation(theEncodeContext.toString()); 1508 getErrorHandler().missingRequiredElement(loc, "value"); 1509 ourLog.debug("Extension with URL[{}] has no value", extensionUrl); 1510 1511 } else { 1512 1513 if (!noValue && !ext.getExtension().isEmpty()) { 1514 ParseLocation loc = new ParseLocation(theEncodeContext.toString()); 1515 getErrorHandler().extensionContainsValueAndNestedExtensions(loc); 1516 } 1517 1518 // Write child extensions 1519 if (!ext.getExtension().isEmpty()) { 1520 1521 if (myModifier) { 1522 beginArray(theEventWriter, "modifierExtension"); 1523 } else { 1524 beginArray(theEventWriter, "extension"); 1525 } 1526 1527 for (Object next : ext.getExtension()) { 1528 writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext, theContainedResource); 1529 } 1530 theEventWriter.endArray(); 1531 1532 } 1533 1534 // Write value 1535 if (!noValue) { 1536 theEncodeContext.pushPath("value", false); 1537 1538 /* 1539 * Pre-process value - This is called in case the value is a reference 1540 * since we might modify the text 1541 */ 1542 value = preProcessValues(myDef, theResource, Collections.singletonList(value), myChildElem, theEncodeContext).get(0); 1543 1544 RuntimeChildUndeclaredExtensionDefinition extDef = getContext().getRuntimeChildUndeclaredExtensionDefinition(); 1545 String childName = extDef.getChildNameByDatatype(value.getClass()); 1546 if (childName == null) { 1547 childName = "value" + WordUtils.capitalize(getContext().getElementDefinition(value.getClass()).getName()); 1548 } 1549 BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass()); 1550 if (childDef == null) { 1551 throw new ConfigurationException(Msg.code(1844) + "Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); 1552 } 1553 encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent, false, theEncodeContext); 1554 managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext, theContainedResource); 1555 1556 theEncodeContext.popPath(); 1557 } 1558 } 1559 1560 // theEventWriter.name(myUndeclaredExtension.get); 1561 1562 theEventWriter.endObject(); 1563 } 1564 } 1565 1566 private static void write(JsonLikeWriter theWriter, String theName, String theValue) throws IOException { 1567 theWriter.write(theName, theValue); 1568 } 1569}