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