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