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