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