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