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