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