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