
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 Validate.notNull(theResource, "theResource must not be null"); 225 226 IIdType id = getIdTypeForUpdate(theResource); 227 228 String fullUrl = id.getValue(); 229 String verb = "PUT"; 230 String requestUrl = StringUtils.isBlank(theRequestUrl) 231 ? id.toUnqualifiedVersionless().getValue() 232 : theRequestUrl; 233 234 IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest(theResource, fullUrl, requestUrl, verb); 235 236 return new UpdateBuilder(url); 237 } 238 239 @Nonnull 240 private IPrimitiveType<?> addAndPopulateTransactionBundleEntryRequest( 241 IBaseResource theResource, String theFullUrl, String theRequestUrl, String theHttpVerb) { 242 setBundleFieldIfNotAlreadySet("type", "transaction"); 243 244 IBase request = addEntryAndReturnRequest(theResource, theFullUrl); 245 246 // Bundle.entry.request.url 247 IPrimitiveType<?> url = addRequestUrl(request, theRequestUrl); 248 249 // Bundle.entry.request.method 250 addRequestMethod(request, theHttpVerb); 251 return url; 252 } 253 254 /** 255 * Adds an entry containing an update (UPDATE) request without the body of the resource. 256 * Also sets the Bundle.type value to "transaction" if it is not already set. 257 * 258 * @param theResource The resource to update. 259 */ 260 public void addTransactionUpdateIdOnlyEntry(IBaseResource theResource) { 261 setBundleFieldIfNotAlreadySet("type", "transaction"); 262 263 Validate.notNull(theResource, "theResource must not be null"); 264 265 IIdType id = getIdTypeForUpdate(theResource); 266 String requestUrl = id.toUnqualifiedVersionless().getValue(); 267 String fullUrl = id.getValue(); 268 String httpMethod = "PUT"; 269 270 addIdOnlyEntry(requestUrl, httpMethod, fullUrl); 271 } 272 273 /** 274 * Adds an entry containing an create (POST) request. 275 * Also sets the Bundle.type value to "transaction" if it is not already set. 276 * 277 * @param theResource The resource to create 278 */ 279 public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) { 280 return addTransactionCreateEntry(theResource, null); 281 } 282 283 /** 284 * Adds an entry containing an create (POST) request. 285 * Also sets the Bundle.type value to "transaction" if it is not already set. 286 * 287 * @param theResource The resource to create 288 * @param theFullUrl The fullUrl to attach to the entry. If null, will default to the resource ID. 289 */ 290 public CreateBuilder addTransactionCreateEntry(IBaseResource theResource, @Nullable String theFullUrl) { 291 setBundleFieldIfNotAlreadySet("type", "transaction"); 292 293 IBase request = addEntryAndReturnRequest( 294 theResource, 295 theFullUrl != null ? theFullUrl : theResource.getIdElement().getValue()); 296 297 String resourceType = myContext.getResourceType(theResource); 298 299 // Bundle.entry.request.url 300 addRequestUrl(request, resourceType); 301 302 // Bundle.entry.request.method 303 addRequestMethod(request, "POST"); 304 305 return new CreateBuilder(request); 306 } 307 308 /** 309 * Adds an entry containing a create (POST) request without the body of the resource. 310 * Also sets the Bundle.type value to "transaction" if it is not already set. 311 * 312 * @param theResource The resource to create 313 */ 314 public void addTransactionCreateEntryIdOnly(IBaseResource theResource) { 315 setBundleFieldIfNotAlreadySet("type", "transaction"); 316 317 String requestUrl = myContext.getResourceType(theResource); 318 String fullUrl = theResource.getIdElement().getValue(); 319 String httpMethod = "POST"; 320 321 addIdOnlyEntry(requestUrl, httpMethod, fullUrl); 322 } 323 324 private void addIdOnlyEntry(String theRequestUrl, String theHttpMethod, String theFullUrl) { 325 IBase entry = addEntry(); 326 327 // Bundle.entry.request 328 IBase request = myEntryRequestDef.newInstance(); 329 myEntryRequestChild.getMutator().setValue(entry, request); 330 331 // Bundle.entry.request.url 332 addRequestUrl(request, theRequestUrl); 333 334 // Bundle.entry.request.method 335 addRequestMethod(request, theHttpMethod); 336 337 // Bundle.entry.fullUrl 338 addFullUrl(entry, theFullUrl); 339 } 340 341 /** 342 * Adds an entry containing a delete (DELETE) request. 343 * Also sets the Bundle.type value to "transaction" if it is not already set. 344 * <p> 345 * 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, 346 * 347 * @param theCondition The conditional URL, e.g. "Patient?identifier=foo|bar" 348 * @since 6.8.0 349 */ 350 public DeleteBuilder addTransactionDeleteConditionalEntry(String theCondition) { 351 Validate.notBlank(theCondition, "theCondition must not be blank"); 352 353 setBundleFieldIfNotAlreadySet("type", "transaction"); 354 return addDeleteEntry(theCondition); 355 } 356 357 /** 358 * Adds an entry containing a delete (DELETE) request. 359 * Also sets the Bundle.type value to "transaction" if it is not already set. 360 * <p> 361 * 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, 362 * 363 * @param theResource The resource to delete. 364 */ 365 public DeleteBuilder addTransactionDeleteEntry(IBaseResource theResource) { 366 String resourceType = myContext.getResourceType(theResource); 367 String idPart = theResource.getIdElement().toUnqualifiedVersionless().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 * <p> 375 * 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, 376 * 377 * @param theResourceId The resource ID to delete. 378 * @return 379 */ 380 public DeleteBuilder addTransactionDeleteEntry(IIdType theResourceId) { 381 String resourceType = theResourceId.getResourceType(); 382 String idPart = theResourceId.getIdPart(); 383 return addTransactionDeleteEntry(resourceType, idPart); 384 } 385 386 /** 387 * Adds an entry containing a delete (DELETE) request. 388 * Also sets the Bundle.type value to "transaction" if it is not already set. 389 * 390 * @param theResourceType The type resource to delete. 391 * @param theIdPart the ID of the resource to delete. 392 */ 393 public DeleteBuilder addTransactionDeleteEntry(String theResourceType, String theIdPart) { 394 setBundleFieldIfNotAlreadySet("type", "transaction"); 395 IdDt idDt = new IdDt(theIdPart); 396 397 String deleteUrl = idDt.toUnqualifiedVersionless() 398 .withResourceType(theResourceType) 399 .getValue(); 400 401 return addDeleteEntry(deleteUrl); 402 } 403 404 /** 405 * Adds an entry containing a delete (DELETE) request. 406 * Also sets the Bundle.type value to "transaction" if it is not already set. 407 * 408 * @param theMatchUrl The match URL, e.g. <code>Patient?identifier=http://foo|123</code> 409 * @since 6.3.0 410 */ 411 public BaseOperationBuilder addTransactionDeleteEntryConditional(String theMatchUrl) { 412 Validate.notBlank(theMatchUrl, "theMatchUrl must not be null or blank"); 413 return addDeleteEntry(theMatchUrl); 414 } 415 416 @Nonnull 417 private DeleteBuilder addDeleteEntry(String theDeleteUrl) { 418 IBase request = addEntryAndReturnRequest(); 419 420 // Bundle.entry.request.url 421 addRequestUrl(request, theDeleteUrl); 422 423 // Bundle.entry.request.method 424 addRequestMethod(request, "DELETE"); 425 426 return new DeleteBuilder(); 427 } 428 429 private IIdType getIdTypeForUpdate(IBaseResource theResource) { 430 IIdType id = theResource.getIdElement(); 431 if (id.hasIdPart() && !id.hasResourceType()) { 432 String resourceType = myContext.getResourceType(theResource); 433 id = id.withResourceType(resourceType); 434 } 435 return id; 436 } 437 438 public void addFullUrl(IBase theEntry, String theFullUrl) { 439 IPrimitiveType<?> fullUrl = 440 (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance(); 441 fullUrl.setValueAsString(theFullUrl); 442 myEntryFullUrlChild.getMutator().setValue(theEntry, fullUrl); 443 } 444 445 private IPrimitiveType<?> addRequestUrl(IBase request, String theRequestUrl) { 446 IPrimitiveType<?> url = 447 (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance(); 448 url.setValueAsString(theRequestUrl); 449 myEntryRequestUrlChild.getMutator().setValue(request, url); 450 return url; 451 } 452 453 private void addRequestMethod(IBase theRequest, String theMethod) { 454 IPrimitiveType<?> method = (IPrimitiveType<?>) 455 myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments()); 456 method.setValueAsString(theMethod); 457 myEntryRequestMethodChild.getMutator().setValue(theRequest, method); 458 } 459 460 /** 461 * Adds an entry for a Collection bundle type 462 */ 463 public void addCollectionEntry(IBaseResource theResource) { 464 setType("collection"); 465 addEntryAndReturnRequest(theResource); 466 } 467 468 /** 469 * Adds an entry for a Document bundle type 470 */ 471 public void addDocumentEntry(IBaseResource theResource) { 472 setType("document"); 473 addEntryAndReturnRequest(theResource); 474 } 475 476 /** 477 * Creates new entry and adds it to the bundle 478 * 479 * @return Returns the new entry. 480 */ 481 public IBase addEntry() { 482 IBase entry = myEntryDef.newInstance(); 483 myEntryChild.getMutator().addValue(myBundle, entry); 484 return entry; 485 } 486 487 /** 488 * Creates new search instance for the specified entry. 489 * Note that this method does not work for DSTU2 model classes, it will only work 490 * on DSTU3+. 491 * 492 * @param entry Entry to create search instance for 493 * @return Returns the search instance 494 */ 495 public IBaseBackboneElement addSearch(IBase entry) { 496 Validate.isTrue( 497 myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), 498 "This method may only be called for FHIR version DSTU3 and above"); 499 500 IBase searchInstance = mySearchDef.newInstance(); 501 mySearchChild.getMutator().setValue(entry, searchInstance); 502 return (IBaseBackboneElement) searchInstance; 503 } 504 505 private IBase addEntryAndReturnRequest(IBaseResource theResource) { 506 IIdType id = theResource.getIdElement(); 507 if (id.hasVersionIdPart()) { 508 id = id.toVersionless(); 509 } 510 return addEntryAndReturnRequest(theResource, id.getValue()); 511 } 512 513 private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) { 514 Validate.notNull(theResource, "theResource must not be null"); 515 516 IBase entry = addEntry(); 517 518 // Bundle.entry.fullUrl 519 addFullUrl(entry, theFullUrl); 520 521 // Bundle.entry.resource 522 myEntryResourceChild.getMutator().setValue(entry, theResource); 523 524 // Bundle.entry.request 525 IBase request = myEntryRequestDef.newInstance(); 526 myEntryRequestChild.getMutator().setValue(entry, request); 527 return request; 528 } 529 530 public IBase addEntryAndReturnRequest() { 531 IBase entry = addEntry(); 532 533 // Bundle.entry.request 534 IBase request = myEntryRequestDef.newInstance(); 535 myEntryRequestChild.getMutator().setValue(entry, request); 536 return request; 537 } 538 539 public IBaseBundle getBundle() { 540 return myBundle; 541 } 542 543 /** 544 * Convenience method which auto-casts the results of {@link #getBundle()} 545 * 546 * @since 6.3.0 547 */ 548 public <T extends IBaseBundle> T getBundleTyped() { 549 return (T) myBundle; 550 } 551 552 /** 553 * Note that this method does not work for DSTU2 model classes, it will only work 554 * on DSTU3+. 555 */ 556 public BundleBuilder setMetaField(String theFieldName, IBase theFieldValue) { 557 Validate.isTrue( 558 myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), 559 "This method may only be called for FHIR version DSTU3 and above"); 560 561 BaseRuntimeChildDefinition.IMutator mutator = 562 myMetaDef.getChildByName(theFieldName).getMutator(); 563 mutator.setValue(myBundle.getMeta(), theFieldValue); 564 return this; 565 } 566 567 /** 568 * Sets the specified entry field. 569 * 570 * @param theEntry The entry instance to set values on 571 * @param theEntryChildName The child field name of the entry instance to be set 572 * @param theValue The field value to set 573 */ 574 public void addToEntry(IBase theEntry, String theEntryChildName, IBase theValue) { 575 addToBase(theEntry, theEntryChildName, theValue, myEntryDef); 576 } 577 578 /** 579 * Sets the specified search field. 580 * 581 * @param theSearch The search instance to set values on 582 * @param theSearchFieldName The child field name of the search instance to be set 583 * @param theSearchFieldValue The field value to set 584 */ 585 public void addToSearch(IBase theSearch, String theSearchFieldName, IBase theSearchFieldValue) { 586 addToBase(theSearch, theSearchFieldName, theSearchFieldValue, mySearchDef); 587 } 588 589 private void addToBase( 590 IBase theBase, String theSearchChildName, IBase theValue, BaseRuntimeElementDefinition mySearchDef) { 591 BaseRuntimeChildDefinition defn = mySearchDef.getChildByName(theSearchChildName); 592 Validate.notNull(defn, "Unable to get child definition %s from %s", theSearchChildName, theBase); 593 defn.getMutator().addValue(theBase, theValue); 594 } 595 596 /** 597 * Creates a new primitive. 598 * 599 * @param theTypeName The element type for the primitive 600 * @param <T> Actual type of the parameterized primitive type interface 601 * @return Returns the new empty instance of the element definition. 602 */ 603 public <T> IPrimitiveType<T> newPrimitive(String theTypeName) { 604 BaseRuntimeElementDefinition primitiveDefinition = myContext.getElementDefinition(theTypeName); 605 Validate.notNull(primitiveDefinition, "Unable to find definition for %s", theTypeName); 606 return (IPrimitiveType<T>) primitiveDefinition.newInstance(); 607 } 608 609 /** 610 * Creates a new primitive instance of the specified element type. 611 * 612 * @param theTypeName Element type to create 613 * @param theInitialValue Initial value to be set on the new instance 614 * @param <T> Actual type of the parameterized primitive type interface 615 * @return Returns the newly created instance 616 */ 617 public <T> IPrimitiveType<T> newPrimitive(String theTypeName, T theInitialValue) { 618 IPrimitiveType<T> retVal = newPrimitive(theTypeName); 619 retVal.setValue(theInitialValue); 620 return retVal; 621 } 622 623 /** 624 * Sets a value for <code>Bundle.type</code>. That this is a coded field so {@literal theType} 625 * must be an actual valid value for this field or a {@link ca.uhn.fhir.parser.DataFormatException} 626 * will be thrown. 627 */ 628 public void setType(String theType) { 629 setBundleField("type", theType); 630 } 631 632 /** 633 * Adds an identifier to <code>Bundle.identifier</code> 634 * 635 * @param theSystem The system 636 * @param theValue The value 637 * @since 6.4.0 638 */ 639 public void setIdentifier(@Nullable String theSystem, @Nullable String theValue) { 640 FhirTerser terser = myContext.newTerser(); 641 IBase identifier = terser.addElement(myBundle, "identifier"); 642 terser.setElement(identifier, "system", theSystem); 643 terser.setElement(identifier, "value", theValue); 644 } 645 646 /** 647 * Sets the timestamp in <code>Bundle.timestamp</code> 648 * 649 * @since 6.4.0 650 */ 651 public void setTimestamp(@Nonnull IPrimitiveType<Date> theTimestamp) { 652 FhirTerser terser = myContext.newTerser(); 653 terser.setElement(myBundle, "Bundle.timestamp", theTimestamp.getValueAsString()); 654 } 655 656 /** 657 * Adds a profile URL to <code>Bundle.meta.profile</code> 658 * 659 * @since 7.4.0 660 */ 661 public void addProfile(String theProfile) { 662 FhirTerser terser = myContext.newTerser(); 663 terser.addElement(myBundle, "Bundle.meta.profile", theProfile); 664 } 665 666 public class DeleteBuilder extends BaseOperationBuilder { 667 668 // nothing yet 669 670 } 671 672 public class PatchBuilder extends BaseOperationBuilderWithConditionalUrl<PatchBuilder> { 673 674 PatchBuilder(IPrimitiveType<?> theUrl) { 675 super(theUrl); 676 } 677 } 678 679 public class UpdateBuilder extends BaseOperationBuilderWithConditionalUrl<UpdateBuilder> { 680 UpdateBuilder(IPrimitiveType<?> theUrl) { 681 super(theUrl); 682 } 683 } 684 685 public class CreateBuilder extends BaseOperationBuilder { 686 private final IBase myRequest; 687 688 CreateBuilder(IBase theRequest) { 689 myRequest = theRequest; 690 } 691 692 /** 693 * Make this create a Conditional Create 694 */ 695 public CreateBuilder conditional(String theConditionalUrl) { 696 BaseRuntimeElementDefinition<?> stringDefinition = 697 Objects.requireNonNull(myContext.getElementDefinition("string")); 698 IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) stringDefinition.newInstance(); 699 ifNoneExist.setValueAsString(theConditionalUrl); 700 701 myEntryRequestIfNoneExistChild.getMutator().setValue(myRequest, ifNoneExist); 702 703 return this; 704 } 705 } 706 707 public abstract class BaseOperationBuilder { 708 709 /** 710 * Returns a reference to the BundleBuilder instance. 711 * <p> 712 * Calling this method has no effect at all, it is only 713 * provided for easy method chaning if you want to build 714 * your bundle as a single fluent call. 715 * 716 * @since 6.3.0 717 */ 718 public BundleBuilder andThen() { 719 return BundleBuilder.this; 720 } 721 } 722 723 public abstract class BaseOperationBuilderWithConditionalUrl<T extends BaseOperationBuilder> 724 extends BaseOperationBuilder { 725 726 private final IPrimitiveType<?> myUrl; 727 728 BaseOperationBuilderWithConditionalUrl(IPrimitiveType<?> theUrl) { 729 myUrl = theUrl; 730 } 731 732 /** 733 * Make this update a Conditional Update 734 */ 735 @SuppressWarnings("unchecked") 736 public T conditional(String theConditionalUrl) { 737 myUrl.setValueAsString(theConditionalUrl); 738 return (T) this; 739 } 740 } 741}