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