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