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