
001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.util; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 024import ca.uhn.fhir.context.FhirContext; 025import ca.uhn.fhir.context.FhirVersionEnum; 026import ca.uhn.fhir.context.RuntimeResourceDefinition; 027import ca.uhn.fhir.model.primitive.IdDt; 028import jakarta.annotation.Nonnull; 029import jakarta.annotation.Nullable; 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.Validate; 032import org.hl7.fhir.instance.model.api.IBase; 033import org.hl7.fhir.instance.model.api.IBaseBackboneElement; 034import org.hl7.fhir.instance.model.api.IBaseBundle; 035import org.hl7.fhir.instance.model.api.IBaseParameters; 036import org.hl7.fhir.instance.model.api.IBaseResource; 037import org.hl7.fhir.instance.model.api.IIdType; 038import org.hl7.fhir.instance.model.api.IPrimitiveType; 039 040import java.util.Date; 041import java.util.Objects; 042import java.util.Optional; 043 044/** 045 * This class can be used to build a Bundle resource to be used as a FHIR transaction. Convenience methods provide 046 * support for setting various bundle fields and working with bundle parts such as metadata and entry 047 * (method and search). 048 * 049 * <p> 050 * <p> 051 * This is not yet complete, and doesn't support all FHIR features. <b>USE WITH CAUTION</b> as the API 052 * may change. 053 * 054 * @since 5.1.0 055 */ 056public class BundleBuilder { 057 058 private final FhirContext myContext; 059 private final IBaseBundle myBundle; 060 private final RuntimeResourceDefinition myBundleDef; 061 private final BaseRuntimeChildDefinition myEntryChild; 062 private final BaseRuntimeChildDefinition myMetaChild; 063 private final BaseRuntimeChildDefinition mySearchChild; 064 private final BaseRuntimeElementDefinition<?> myEntryDef; 065 private final BaseRuntimeElementDefinition<?> myMetaDef; 066 private final BaseRuntimeElementDefinition mySearchDef; 067 private final BaseRuntimeChildDefinition myEntryResourceChild; 068 private final BaseRuntimeChildDefinition myEntryFullUrlChild; 069 private final BaseRuntimeChildDefinition myEntryRequestChild; 070 private final BaseRuntimeElementDefinition<?> myEntryRequestDef; 071 private final BaseRuntimeChildDefinition myEntryRequestUrlChild; 072 private final BaseRuntimeChildDefinition myEntryRequestMethodChild; 073 private final BaseRuntimeElementDefinition<?> myEntryRequestMethodDef; 074 private final BaseRuntimeChildDefinition myEntryRequestIfNoneExistChild; 075 076 /** 077 * Constructor 078 */ 079 public BundleBuilder(FhirContext theContext) { 080 myContext = theContext; 081 082 myBundleDef = myContext.getResourceDefinition("Bundle"); 083 myBundle = (IBaseBundle) myBundleDef.newInstance(); 084 085 myEntryChild = myBundleDef.getChildByName("entry"); 086 myEntryDef = myEntryChild.getChildByName("entry"); 087 088 mySearchChild = myEntryDef.getChildByName("search"); 089 mySearchDef = mySearchChild.getChildByName("search"); 090 091 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 092 myMetaChild = myBundleDef.getChildByName("meta"); 093 myMetaDef = myMetaChild.getChildByName("meta"); 094 } else { 095 myMetaChild = null; 096 myMetaDef = null; 097 } 098 099 myEntryResourceChild = myEntryDef.getChildByName("resource"); 100 myEntryFullUrlChild = myEntryDef.getChildByName("fullUrl"); 101 102 myEntryRequestChild = myEntryDef.getChildByName("request"); 103 myEntryRequestDef = myEntryRequestChild.getChildByName("request"); 104 105 myEntryRequestUrlChild = myEntryRequestDef.getChildByName("url"); 106 107 myEntryRequestMethodChild = myEntryRequestDef.getChildByName("method"); 108 myEntryRequestMethodDef = myEntryRequestMethodChild.getChildByName("method"); 109 110 myEntryRequestIfNoneExistChild = myEntryRequestDef.getChildByName("ifNoneExist"); 111 } 112 113 /** 114 * Sets the specified primitive field on the bundle with the value provided. 115 * 116 * @param theFieldName Name of the primitive field. 117 * @param theFieldValue Value of the field to be set. 118 */ 119 public BundleBuilder setBundleField(String theFieldName, String theFieldValue) { 120 BaseRuntimeChildDefinition typeChild = myBundleDef.getChildByName(theFieldName); 121 Validate.notNull(typeChild, "Unable to find field %s", theFieldName); 122 123 IPrimitiveType<?> type = (IPrimitiveType<?>) 124 typeChild.getChildByName(theFieldName).newInstance(typeChild.getInstanceConstructorArguments()); 125 type.setValueAsString(theFieldValue); 126 typeChild.getMutator().setValue(myBundle, type); 127 return this; 128 } 129 130 private void setBundleFieldIfNotAlreadySet(String theFieldName, String theFieldValue) { 131 BaseRuntimeChildDefinition typeChild = myBundleDef.getChildByName(theFieldName); 132 Validate.notNull(typeChild, "Unable to find field %s", theFieldName); 133 Optional<IBase> firstValue = typeChild.getAccessor().getFirstValueOrNull(myBundle); 134 if (firstValue.isPresent()) { 135 IPrimitiveType<?> value = (IPrimitiveType<?>) firstValue.get(); 136 if (!value.isEmpty()) { 137 return; 138 } 139 } 140 141 setBundleField(theFieldName, theFieldValue); 142 } 143 144 /** 145 * Sets the specified primitive field on the search entry with the value provided. 146 * 147 * @param theSearch Search part of the entry 148 * @param theFieldName Name of the primitive field. 149 * @param theFieldValue Value of the field to be set. 150 */ 151 public BundleBuilder setSearchField(IBase theSearch, String theFieldName, String theFieldValue) { 152 BaseRuntimeChildDefinition typeChild = mySearchDef.getChildByName(theFieldName); 153 Validate.notNull(typeChild, "Unable to find field %s", theFieldName); 154 155 IPrimitiveType<?> type = (IPrimitiveType<?>) 156 typeChild.getChildByName(theFieldName).newInstance(typeChild.getInstanceConstructorArguments()); 157 type.setValueAsString(theFieldValue); 158 typeChild.getMutator().setValue(theSearch, type); 159 return this; 160 } 161 162 public BundleBuilder setSearchField(IBase theSearch, String theFieldName, IPrimitiveType<?> theFieldValue) { 163 BaseRuntimeChildDefinition typeChild = mySearchDef.getChildByName(theFieldName); 164 Validate.notNull(typeChild, "Unable to find field %s", theFieldName); 165 166 typeChild.getMutator().setValue(theSearch, theFieldValue); 167 return this; 168 } 169 170 /** 171 * Adds a FHIRPatch patch bundle to the transaction 172 * 173 * @param theTarget The target resource ID to patch 174 * @param thePatch The FHIRPath Parameters resource 175 * @since 6.3.0 176 */ 177 public PatchBuilder addTransactionFhirPatchEntry(IIdType theTarget, IBaseParameters thePatch) { 178 Validate.notNull(theTarget, "theTarget must not be null"); 179 Validate.notBlank(theTarget.getResourceType(), "theTarget must contain a resource type"); 180 Validate.notBlank(theTarget.getIdPart(), "theTarget must contain an ID"); 181 182 IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest( 183 thePatch, 184 theTarget.getValue(), 185 theTarget.toUnqualifiedVersionless().getValue(), 186 "PATCH"); 187 188 return new PatchBuilder(url); 189 } 190 191 /** 192 * Adds a FHIRPatch patch bundle to the transaction. This method is intended for conditional PATCH operations. If you 193 * know the ID of the resource you wish to patch, use {@link #addTransactionFhirPatchEntry(IIdType, IBaseParameters)} 194 * instead. 195 * 196 * @param thePatch The FHIRPath Parameters resource 197 * @see #addTransactionFhirPatchEntry(IIdType, IBaseParameters) 198 * @since 6.3.0 199 */ 200 public PatchBuilder addTransactionFhirPatchEntry(IBaseParameters thePatch) { 201 IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest(thePatch, null, null, "PATCH"); 202 203 return new PatchBuilder(url); 204 } 205 206 /** 207 * Adds an entry containing an update (PUT) request. 208 * Also sets the Bundle.type value to "transaction" if it is not already set. 209 * 210 * @param theResource The resource to update 211 */ 212 public UpdateBuilder addTransactionUpdateEntry(IBaseResource theResource) { 213 return addTransactionUpdateEntry(theResource, null); 214 } 215 216 /** 217 * Adds an entry containing an update (PUT) request. 218 * Also sets the Bundle.type value to "transaction" if it is not already set. 219 * 220 * @param theResource The resource to update 221 * @param theRequestUrl The url to attach to the Bundle.entry.request.url. If null, will default to the resource ID. 222 */ 223 public UpdateBuilder addTransactionUpdateEntry(IBaseResource theResource, String theRequestUrl) { 224 String fullUrl = null; 225 return addTransactionUpdateEntry(theResource, theRequestUrl, fullUrl); 226 } 227 228 /** 229 * Adds an entry containing an update (PUT) request. 230 * Also sets the Bundle.type value to "transaction" if it is not already set. 231 * 232 * @param theResource The resource to update 233 * @param theRequestUrl The url to attach to the Bundle.entry.request.url. If null, will default to the resource ID. 234 * @param theFullUrl The fullUrl to attach to the entry in {@literal Bundle.entry.fullUrl}. If null, will default to the resource ID. 235 * @since 8.6.0 236 */ 237 @Nonnull 238 public UpdateBuilder addTransactionUpdateEntry(IBaseResource theResource, String theRequestUrl, String theFullUrl) { 239 Validate.notNull(theResource, "theResource must not be null"); 240 241 IIdType id = getIdTypeForUpdate(theResource); 242 if (theFullUrl == null) { 243 theFullUrl = id.toVersionless().getValue(); 244 } 245 246 String verb = "PUT"; 247 String requestUrl = StringUtils.isBlank(theRequestUrl) 248 ? id.toUnqualifiedVersionless().getValue() 249 : theRequestUrl; 250 251 IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest(theResource, theFullUrl, requestUrl, verb); 252 253 return new UpdateBuilder(url); 254 } 255 256 @Nonnull 257 private IPrimitiveType<?> addAndPopulateTransactionBundleEntryRequest( 258 IBaseResource theResource, String theFullUrl, String theRequestUrl, String theHttpVerb) { 259 setBundleFieldIfNotAlreadySet("type", "transaction"); 260 261 IBase request = addEntryAndReturnRequest(theResource, theFullUrl); 262 263 // Bundle.entry.request.url 264 IPrimitiveType<?> url = addRequestUrl(request, theRequestUrl); 265 266 // Bundle.entry.request.method 267 addRequestMethod(request, theHttpVerb); 268 return url; 269 } 270 271 /** 272 * Adds an entry containing an update (UPDATE) request without the body of the resource. 273 * Also sets the Bundle.type value to "transaction" if it is not already set. 274 * 275 * @param theResource The resource to update. 276 */ 277 public void addTransactionUpdateIdOnlyEntry(IBaseResource theResource) { 278 setBundleFieldIfNotAlreadySet("type", "transaction"); 279 280 Validate.notNull(theResource, "theResource must not be null"); 281 282 IIdType id = getIdTypeForUpdate(theResource); 283 String requestUrl = id.toUnqualifiedVersionless().getValue(); 284 String fullUrl = id.toVersionless().getValue(); 285 String httpMethod = "PUT"; 286 287 addIdOnlyEntry(requestUrl, httpMethod, fullUrl); 288 } 289 290 /** 291 * Adds an entry containing an create (POST) request. 292 * Also sets the Bundle.type value to "transaction" if it is not already set. 293 * 294 * @param theResource The resource to create 295 */ 296 public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) { 297 return addTransactionCreateEntry(theResource, null); 298 } 299 300 /** 301 * Adds an entry containing an create (POST) request. 302 * Also sets the Bundle.type value to "transaction" if it is not already set. 303 * 304 * @param theResource The resource to create 305 * @param theFullUrl The fullUrl to attach to the entry. If null, will default to the resource ID. 306 */ 307 public CreateBuilder addTransactionCreateEntry(IBaseResource theResource, @Nullable String theFullUrl) { 308 setBundleFieldIfNotAlreadySet("type", "transaction"); 309 310 IBase request = addEntryAndReturnRequest( 311 theResource, 312 theFullUrl != null ? theFullUrl : theResource.getIdElement().getValue()); 313 314 String resourceType = myContext.getResourceType(theResource); 315 316 // Bundle.entry.request.url 317 addRequestUrl(request, resourceType); 318 319 // Bundle.entry.request.method 320 addRequestMethod(request, "POST"); 321 322 return new CreateBuilder(request); 323 } 324 325 /** 326 * Adds an entry containing a create (POST) request without the body of the resource. 327 * Also sets the Bundle.type value to "transaction" if it is not already set. 328 * 329 * @param theResource The resource to create 330 */ 331 public void addTransactionCreateEntryIdOnly(IBaseResource theResource) { 332 setBundleFieldIfNotAlreadySet("type", "transaction"); 333 334 String requestUrl = myContext.getResourceType(theResource); 335 String fullUrl = theResource.getIdElement().getValue(); 336 String httpMethod = "POST"; 337 338 addIdOnlyEntry(requestUrl, httpMethod, fullUrl); 339 } 340 341 private void addIdOnlyEntry(String theRequestUrl, String theHttpMethod, String theFullUrl) { 342 IBase entry = addEntry(); 343 344 // Bundle.entry.request 345 IBase request = myEntryRequestDef.newInstance(); 346 myEntryRequestChild.getMutator().setValue(entry, request); 347 348 // Bundle.entry.request.url 349 addRequestUrl(request, theRequestUrl); 350 351 // Bundle.entry.request.method 352 addRequestMethod(request, theHttpMethod); 353 354 // Bundle.entry.fullUrl 355 addFullUrl(entry, theFullUrl); 356 } 357 358 /** 359 * Adds an entry containing a delete (DELETE) request. 360 * Also sets the Bundle.type value to "transaction" if it is not already set. 361 * <p> 362 * Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry, 363 * 364 * @param theCondition The conditional URL, e.g. "Patient?identifier=foo|bar" 365 * @since 6.8.0 366 */ 367 public DeleteBuilder addTransactionDeleteConditionalEntry(String theCondition) { 368 Validate.notBlank(theCondition, "theCondition must not be blank"); 369 370 setBundleFieldIfNotAlreadySet("type", "transaction"); 371 return addTransactionDeleteEntry(theCondition); 372 } 373 374 /** 375 * Adds an entry containing a delete (DELETE) request. 376 * Also sets the Bundle.type value to "transaction" if it is not already set. 377 * <p> 378 * Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry, 379 * 380 * @param theResource The resource to delete. 381 */ 382 public DeleteBuilder addTransactionDeleteEntry(IBaseResource theResource) { 383 String resourceType = myContext.getResourceType(theResource); 384 String idPart = theResource.getIdElement().toUnqualifiedVersionless().getIdPart(); 385 return addTransactionDeleteEntry(resourceType, idPart); 386 } 387 388 /** 389 * Adds an entry containing a delete (DELETE) request. 390 * Also sets the Bundle.type value to "transaction" if it is not already set. 391 * <p> 392 * Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry, 393 * 394 * @param theResourceId The resource ID to delete. 395 * @return 396 */ 397 public DeleteBuilder addTransactionDeleteEntry(IIdType theResourceId) { 398 String resourceType = theResourceId.getResourceType(); 399 String idPart = theResourceId.getIdPart(); 400 return addTransactionDeleteEntry(resourceType, idPart); 401 } 402 403 /** 404 * Adds an entry containing a delete (DELETE) request. 405 * Also sets the Bundle.type value to "transaction" if it is not already set. 406 * 407 * @param theResourceType The type resource to delete. 408 * @param theIdPart the ID of the resource to delete. 409 */ 410 public DeleteBuilder addTransactionDeleteEntry(String theResourceType, String theIdPart) { 411 setBundleFieldIfNotAlreadySet("type", "transaction"); 412 IdDt idDt = new IdDt(theIdPart); 413 414 String deleteUrl = idDt.toUnqualifiedVersionless() 415 .withResourceType(theResourceType) 416 .getValue(); 417 418 return addTransactionDeleteEntry(deleteUrl); 419 } 420 421 /** 422 * Adds an entry containing a delete (DELETE) request. 423 * Also sets the Bundle.type value to "transaction" if it is not already set. 424 * 425 * @param theMatchUrl The match URL, e.g. <code>Patient?identifier=http://foo|123</code> 426 * @since 6.3.0 427 */ 428 public BaseOperationBuilder addTransactionDeleteEntryConditional(String theMatchUrl) { 429 Validate.notBlank(theMatchUrl, "theMatchUrl must not be null or blank"); 430 return addTransactionDeleteEntry(theMatchUrl); 431 } 432 433 /** 434 * Adds a DELETE entry using only a conditional URL 435 * 436 * @since 8.6.0 437 */ 438 @Nonnull 439 public DeleteBuilder addTransactionDeleteEntry(String theDeleteUrl) { 440 Validate.notBlank(theDeleteUrl, "theDeleteUrl must not be null or blank"); 441 IBase request = addEntryAndReturnRequest(); 442 443 // Bundle.entry.request.url 444 addRequestUrl(request, theDeleteUrl); 445 446 // Bundle.entry.request.method 447 addRequestMethod(request, "DELETE"); 448 449 return new DeleteBuilder(); 450 } 451 452 private IIdType getIdTypeForUpdate(IBaseResource theResource) { 453 IIdType id = theResource.getIdElement(); 454 if (id.hasIdPart() && !id.hasResourceType()) { 455 String resourceType = myContext.getResourceType(theResource); 456 id = id.withResourceType(resourceType); 457 } 458 return id; 459 } 460 461 public void addFullUrl(IBase theEntry, String theFullUrl) { 462 IPrimitiveType<?> fullUrl = 463 (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance(); 464 fullUrl.setValueAsString(theFullUrl); 465 myEntryFullUrlChild.getMutator().setValue(theEntry, fullUrl); 466 } 467 468 private IPrimitiveType<?> addRequestUrl(IBase request, String theRequestUrl) { 469 IPrimitiveType<?> url = 470 (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance(); 471 url.setValueAsString(theRequestUrl); 472 myEntryRequestUrlChild.getMutator().setValue(request, url); 473 return url; 474 } 475 476 private void addRequestMethod(IBase theRequest, String theMethod) { 477 IPrimitiveType<?> method = (IPrimitiveType<?>) 478 myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments()); 479 method.setValueAsString(theMethod); 480 myEntryRequestMethodChild.getMutator().setValue(theRequest, method); 481 } 482 483 /** 484 * Adds an entry for a Collection bundle type 485 */ 486 public void addCollectionEntry(IBaseResource theResource) { 487 setType("collection"); 488 addEntryAndReturnRequest(theResource); 489 } 490 491 /** 492 * Adds an entry for a Document bundle type 493 */ 494 public void addDocumentEntry(IBaseResource theResource) { 495 setType("document"); 496 addEntryAndReturnRequest(theResource); 497 } 498 499 public void addDocumentEntry(IBaseResource theResource, String theFullUrl) { 500 setType("document"); 501 addEntryAndReturnRequest(theResource, theFullUrl); 502 } 503 504 /** 505 * Adds an entry for a Message bundle type 506 * 507 * @since 8.4.0 508 */ 509 public void addMessageEntry(IBaseResource theResource) { 510 setType("message"); 511 addEntryAndReturnRequest(theResource); 512 } 513 514 /** 515 * Creates new entry and adds it to the bundle 516 * 517 * @return Returns the new entry. 518 */ 519 public IBase addEntry() { 520 return addEntry(myEntryDef.newInstance()); 521 } 522 523 /** 524 * Add an entry to the bundle. 525 * 526 * @param theEntry the entry to add to the bundle. 527 * @return theEntry 528 */ 529 public IBase addEntry(IBase theEntry) { 530 myEntryChild.getMutator().addValue(myBundle, theEntry); 531 return theEntry; 532 } 533 534 /** 535 * Add an entry to the bundle. 536 * 537 * @param theEntry the canonical entry to add to the bundle. It will be converted to a FHIR version specific entry before adding. 538 * @return 539 */ 540 public IBase addEntry(CanonicalBundleEntry theEntry) { 541 IBase bundleEntry = theEntry.toBundleEntry(myContext, myEntryDef.getImplementingClass()); 542 addEntry(bundleEntry); 543 return bundleEntry; 544 } 545 546 /** 547 * Creates new search instance for the specified entry. 548 * Note that this method does not work for DSTU2 model classes, it will only work 549 * on DSTU3+. 550 * 551 * @param entry Entry to create search instance for 552 * @return Returns the search instance 553 */ 554 public IBaseBackboneElement addSearch(IBase entry) { 555 Validate.isTrue( 556 myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), 557 "This method may only be called for FHIR version DSTU3 and above"); 558 559 IBase searchInstance = mySearchDef.newInstance(); 560 mySearchChild.getMutator().setValue(entry, searchInstance); 561 return (IBaseBackboneElement) searchInstance; 562 } 563 564 private IBase addEntryAndReturnRequest(IBaseResource theResource) { 565 IIdType id = theResource.getIdElement(); 566 if (id.hasVersionIdPart()) { 567 id = id.toVersionless(); 568 } 569 return addEntryAndReturnRequest(theResource, id.getValue()); 570 } 571 572 private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) { 573 Validate.notNull(theResource, "theResource must not be null"); 574 575 IBase entry = addEntry(); 576 577 // Bundle.entry.fullUrl 578 addFullUrl(entry, theFullUrl); 579 580 // Bundle.entry.resource 581 myEntryResourceChild.getMutator().setValue(entry, theResource); 582 583 // Bundle.entry.request 584 IBase request = myEntryRequestDef.newInstance(); 585 myEntryRequestChild.getMutator().setValue(entry, request); 586 return request; 587 } 588 589 public IBase addEntryAndReturnRequest() { 590 IBase entry = addEntry(); 591 592 // Bundle.entry.request 593 IBase request = myEntryRequestDef.newInstance(); 594 myEntryRequestChild.getMutator().setValue(entry, request); 595 return request; 596 } 597 598 public IBaseBundle getBundle() { 599 return myBundle; 600 } 601 602 /** 603 * Convenience method which auto-casts the results of {@link #getBundle()} 604 * 605 * @since 6.3.0 606 */ 607 public <T extends IBaseBundle> T getBundleTyped() { 608 return (T) myBundle; 609 } 610 611 /** 612 * Note that this method does not work for DSTU2 model classes, it will only work 613 * on DSTU3+. 614 */ 615 public BundleBuilder setMetaField(String theFieldName, IBase theFieldValue) { 616 Validate.isTrue( 617 myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), 618 "This method may only be called for FHIR version DSTU3 and above"); 619 620 BaseRuntimeChildDefinition.IMutator mutator = 621 myMetaDef.getChildByName(theFieldName).getMutator(); 622 mutator.setValue(myBundle.getMeta(), theFieldValue); 623 return this; 624 } 625 626 /** 627 * Sets the specified entry field. 628 * 629 * @param theEntry The entry instance to set values on 630 * @param theEntryChildName The child field name of the entry instance to be set 631 * @param theValue The field value to set 632 */ 633 public void addToEntry(IBase theEntry, String theEntryChildName, IBase theValue) { 634 addToBase(theEntry, theEntryChildName, theValue, myEntryDef); 635 } 636 637 /** 638 * Sets the specified search field. 639 * 640 * @param theSearch The search instance to set values on 641 * @param theSearchFieldName The child field name of the search instance to be set 642 * @param theSearchFieldValue The field value to set 643 */ 644 public void addToSearch(IBase theSearch, String theSearchFieldName, IBase theSearchFieldValue) { 645 addToBase(theSearch, theSearchFieldName, theSearchFieldValue, mySearchDef); 646 } 647 648 private void addToBase( 649 IBase theBase, String theSearchChildName, IBase theValue, BaseRuntimeElementDefinition mySearchDef) { 650 BaseRuntimeChildDefinition defn = mySearchDef.getChildByName(theSearchChildName); 651 Validate.notNull(defn, "Unable to get child definition %s from %s", theSearchChildName, theBase); 652 defn.getMutator().addValue(theBase, theValue); 653 } 654 655 /** 656 * Creates a new primitive. 657 * 658 * @param theTypeName The element type for the primitive 659 * @param <T> Actual type of the parameterized primitive type interface 660 * @return Returns the new empty instance of the element definition. 661 */ 662 public <T> IPrimitiveType<T> newPrimitive(String theTypeName) { 663 BaseRuntimeElementDefinition primitiveDefinition = myContext.getElementDefinition(theTypeName); 664 Validate.notNull(primitiveDefinition, "Unable to find definition for %s", theTypeName); 665 return (IPrimitiveType<T>) primitiveDefinition.newInstance(); 666 } 667 668 /** 669 * Creates a new primitive instance of the specified element type. 670 * 671 * @param theTypeName Element type to create 672 * @param theInitialValue Initial value to be set on the new instance 673 * @param <T> Actual type of the parameterized primitive type interface 674 * @return Returns the newly created instance 675 */ 676 public <T> IPrimitiveType<T> newPrimitive(String theTypeName, T theInitialValue) { 677 IPrimitiveType<T> retVal = newPrimitive(theTypeName); 678 retVal.setValue(theInitialValue); 679 return retVal; 680 } 681 682 /** 683 * Sets a value for <code>Bundle.type</code>. That this is a coded field so {@literal theType} 684 * must be an actual valid value for this field or a {@link ca.uhn.fhir.parser.DataFormatException} 685 * will be thrown. 686 */ 687 public void setType(String theType) { 688 setBundleField("type", theType); 689 } 690 691 /** 692 * Adds an identifier to <code>Bundle.identifier</code> 693 * 694 * @param theSystem The system 695 * @param theValue The value 696 * @since 6.4.0 697 */ 698 public void setIdentifier(@Nullable String theSystem, @Nullable String theValue) { 699 FhirTerser terser = myContext.newTerser(); 700 IBase identifier = terser.addElement(myBundle, "identifier"); 701 terser.setElement(identifier, "system", theSystem); 702 terser.setElement(identifier, "value", theValue); 703 } 704 705 /** 706 * Sets the timestamp in <code>Bundle.timestamp</code> 707 * 708 * @since 6.4.0 709 */ 710 public void setTimestamp(@Nonnull IPrimitiveType<Date> theTimestamp) { 711 FhirTerser terser = myContext.newTerser(); 712 terser.setElement(myBundle, "Bundle.timestamp", theTimestamp.getValueAsString()); 713 } 714 715 /** 716 * Adds a profile URL to <code>Bundle.meta.profile</code> 717 * 718 * @since 7.4.0 719 */ 720 public void addProfile(String theProfile) { 721 FhirTerser terser = myContext.newTerser(); 722 terser.addElement(myBundle, "Bundle.meta.profile", theProfile); 723 } 724 725 public IBase addSearchMatchEntry(IBaseResource theResource) { 726 setType("searchset"); 727 728 IBase entry = addEntry(); 729 // Bundle.entry.resource 730 myEntryResourceChild.getMutator().setValue(entry, theResource); 731 // Bundle.entry.search 732 IBase search = addSearch(entry); 733 setSearchField(search, "mode", "match"); 734 735 return entry; 736 } 737 738 public class DeleteBuilder extends BaseOperationBuilder { 739 740 // nothing yet 741 742 } 743 744 public class PatchBuilder extends BaseOperationBuilderWithConditionalUrl<PatchBuilder> { 745 746 PatchBuilder(IPrimitiveType<?> theUrl) { 747 super(theUrl); 748 } 749 } 750 751 public class UpdateBuilder extends BaseOperationBuilderWithConditionalUrl<UpdateBuilder> { 752 UpdateBuilder(IPrimitiveType<?> theUrl) { 753 super(theUrl); 754 } 755 } 756 757 public class CreateBuilder extends BaseOperationBuilder { 758 private final IBase myRequest; 759 760 CreateBuilder(IBase theRequest) { 761 myRequest = theRequest; 762 } 763 764 /** 765 * Make this create a Conditional Create 766 */ 767 public CreateBuilder conditional(String theConditionalUrl) { 768 BaseRuntimeElementDefinition<?> stringDefinition = 769 Objects.requireNonNull(myContext.getElementDefinition("string")); 770 IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) stringDefinition.newInstance(); 771 ifNoneExist.setValueAsString(theConditionalUrl); 772 773 myEntryRequestIfNoneExistChild.getMutator().setValue(myRequest, ifNoneExist); 774 775 return this; 776 } 777 } 778 779 public abstract class BaseOperationBuilder { 780 781 /** 782 * Returns a reference to the BundleBuilder instance. 783 * <p> 784 * Calling this method has no effect at all, it is only 785 * provided for easy method chaning if you want to build 786 * your bundle as a single fluent call. 787 * 788 * @since 6.3.0 789 */ 790 public BundleBuilder andThen() { 791 return BundleBuilder.this; 792 } 793 } 794 795 public abstract class BaseOperationBuilderWithConditionalUrl<T extends BaseOperationBuilder> 796 extends BaseOperationBuilder { 797 798 private final IPrimitiveType<?> myUrl; 799 800 BaseOperationBuilderWithConditionalUrl(IPrimitiveType<?> theUrl) { 801 myUrl = theUrl; 802 } 803 804 /** 805 * Make this update a Conditional Update 806 */ 807 @SuppressWarnings("unchecked") 808 public T conditional(String theConditionalUrl) { 809 myUrl.setValueAsString(theConditionalUrl); 810 return (T) this; 811 } 812 } 813}