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