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