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