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, theResource.getIdElement().getValue());
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, theResource.getIdElement().getValue());
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, String theFullUrl) {
479                Validate.notNull(theResource, "theResource must not be null");
480
481                IBase entry = addEntry();
482
483                // Bundle.entry.fullUrl
484                addFullUrl(entry, theFullUrl);
485
486                // Bundle.entry.resource
487                myEntryResourceChild.getMutator().setValue(entry, theResource);
488
489                // Bundle.entry.request
490                IBase request = myEntryRequestDef.newInstance();
491                myEntryRequestChild.getMutator().setValue(entry, request);
492                return request;
493        }
494
495        public IBase addEntryAndReturnRequest() {
496                IBase entry = addEntry();
497
498                // Bundle.entry.request
499                IBase request = myEntryRequestDef.newInstance();
500                myEntryRequestChild.getMutator().setValue(entry, request);
501                return request;
502        }
503
504        public IBaseBundle getBundle() {
505                return myBundle;
506        }
507
508        /**
509         * Convenience method which auto-casts the results of {@link #getBundle()}
510         *
511         * @since 6.3.0
512         */
513        public <T extends IBaseBundle> T getBundleTyped() {
514                return (T) myBundle;
515        }
516
517        /**
518         * Note that this method does not work for DSTU2 model classes, it will only work
519         * on DSTU3+.
520         */
521        public BundleBuilder setMetaField(String theFieldName, IBase theFieldValue) {
522                Validate.isTrue(
523                                myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3),
524                                "This method may only be called for FHIR version DSTU3 and above");
525
526                BaseRuntimeChildDefinition.IMutator mutator =
527                                myMetaDef.getChildByName(theFieldName).getMutator();
528                mutator.setValue(myBundle.getMeta(), theFieldValue);
529                return this;
530        }
531
532        /**
533         * Sets the specified entry field.
534         *
535         * @param theEntry          The entry instance to set values on
536         * @param theEntryChildName The child field name of the entry instance to be set
537         * @param theValue          The field value to set
538         */
539        public void addToEntry(IBase theEntry, String theEntryChildName, IBase theValue) {
540                addToBase(theEntry, theEntryChildName, theValue, myEntryDef);
541        }
542
543        /**
544         * Sets the specified search field.
545         *
546         * @param theSearch           The search instance to set values on
547         * @param theSearchFieldName  The child field name of the search instance to be set
548         * @param theSearchFieldValue The field value to set
549         */
550        public void addToSearch(IBase theSearch, String theSearchFieldName, IBase theSearchFieldValue) {
551                addToBase(theSearch, theSearchFieldName, theSearchFieldValue, mySearchDef);
552        }
553
554        private void addToBase(
555                        IBase theBase, String theSearchChildName, IBase theValue, BaseRuntimeElementDefinition mySearchDef) {
556                BaseRuntimeChildDefinition defn = mySearchDef.getChildByName(theSearchChildName);
557                Validate.notNull(defn, "Unable to get child definition %s from %s", theSearchChildName, theBase);
558                defn.getMutator().addValue(theBase, theValue);
559        }
560
561        /**
562         * Creates a new primitive.
563         *
564         * @param theTypeName The element type for the primitive
565         * @param <T>         Actual type of the parameterized primitive type interface
566         * @return Returns the new empty instance of the element definition.
567         */
568        public <T> IPrimitiveType<T> newPrimitive(String theTypeName) {
569                BaseRuntimeElementDefinition primitiveDefinition = myContext.getElementDefinition(theTypeName);
570                Validate.notNull(primitiveDefinition, "Unable to find definition for %s", theTypeName);
571                return (IPrimitiveType<T>) primitiveDefinition.newInstance();
572        }
573
574        /**
575         * Creates a new primitive instance of the specified element type.
576         *
577         * @param theTypeName     Element type to create
578         * @param theInitialValue Initial value to be set on the new instance
579         * @param <T>             Actual type of the parameterized primitive type interface
580         * @return Returns the newly created instance
581         */
582        public <T> IPrimitiveType<T> newPrimitive(String theTypeName, T theInitialValue) {
583                IPrimitiveType<T> retVal = newPrimitive(theTypeName);
584                retVal.setValue(theInitialValue);
585                return retVal;
586        }
587
588        /**
589         * Sets a value for <code>Bundle.type</code>. That this is a coded field so {@literal theType}
590         * must be an actual valid value for this field or a {@link ca.uhn.fhir.parser.DataFormatException}
591         * will be thrown.
592         */
593        public void setType(String theType) {
594                setBundleField("type", theType);
595        }
596
597        /**
598         * Adds an identifier to <code>Bundle.identifier</code>
599         *
600         * @param theSystem The system
601         * @param theValue  The value
602         * @since 6.4.0
603         */
604        public void setIdentifier(@Nullable String theSystem, @Nullable String theValue) {
605                FhirTerser terser = myContext.newTerser();
606                IBase identifier = terser.addElement(myBundle, "identifier");
607                terser.setElement(identifier, "system", theSystem);
608                terser.setElement(identifier, "value", theValue);
609        }
610
611        /**
612         * Sets the timestamp in <code>Bundle.timestamp</code>
613         *
614         * @since 6.4.0
615         */
616        public void setTimestamp(@Nonnull IPrimitiveType<Date> theTimestamp) {
617                FhirTerser terser = myContext.newTerser();
618                terser.setElement(myBundle, "Bundle.timestamp", theTimestamp.getValueAsString());
619        }
620
621        public class DeleteBuilder extends BaseOperationBuilder {
622
623                // nothing yet
624
625        }
626
627        public class PatchBuilder extends BaseOperationBuilderWithConditionalUrl<PatchBuilder> {
628
629                PatchBuilder(IPrimitiveType<?> theUrl) {
630                        super(theUrl);
631                }
632        }
633
634        public class UpdateBuilder extends BaseOperationBuilderWithConditionalUrl<UpdateBuilder> {
635                UpdateBuilder(IPrimitiveType<?> theUrl) {
636                        super(theUrl);
637                }
638        }
639
640        public class CreateBuilder extends BaseOperationBuilder {
641                private final IBase myRequest;
642
643                CreateBuilder(IBase theRequest) {
644                        myRequest = theRequest;
645                }
646
647                /**
648                 * Make this create a Conditional Create
649                 */
650                public CreateBuilder conditional(String theConditionalUrl) {
651                        BaseRuntimeElementDefinition<?> stringDefinition =
652                                        Objects.requireNonNull(myContext.getElementDefinition("string"));
653                        IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) stringDefinition.newInstance();
654                        ifNoneExist.setValueAsString(theConditionalUrl);
655
656                        myEntryRequestIfNoneExistChild.getMutator().setValue(myRequest, ifNoneExist);
657
658                        return this;
659                }
660        }
661
662        public abstract class BaseOperationBuilder {
663
664                /**
665                 * Returns a reference to the BundleBuilder instance.
666                 * <p>
667                 * Calling this method has no effect at all, it is only
668                 * provided for easy method chaning if you want to build
669                 * your bundle as a single fluent call.
670                 *
671                 * @since 6.3.0
672                 */
673                public BundleBuilder andThen() {
674                        return BundleBuilder.this;
675                }
676        }
677
678        public abstract class BaseOperationBuilderWithConditionalUrl<T extends BaseOperationBuilder>
679                        extends BaseOperationBuilder {
680
681                private final IPrimitiveType<?> myUrl;
682
683                BaseOperationBuilderWithConditionalUrl(IPrimitiveType<?> theUrl) {
684                        myUrl = theUrl;
685                }
686
687                /**
688                 * Make this update a Conditional Update
689                 */
690                @SuppressWarnings("unchecked")
691                public T conditional(String theConditionalUrl) {
692                        myUrl.setValueAsString(theConditionalUrl);
693                        return (T) this;
694                }
695        }
696}