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