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