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.FhirContext;
023import jakarta.annotation.Nonnull;
024import org.apache.commons.lang3.Validate;
025import org.hl7.fhir.instance.model.api.IBase;
026import org.hl7.fhir.instance.model.api.IBaseParameters;
027
028/**
029 * This utility class can be used to create
030 * <a href="https://smilecdr.com/fhir_standard/fhir_patch.html">FHIRPath Patch</a>
031 * documents.
032 *
033 * @since 8.6.0
034 */
035public class FhirPatchBuilder {
036        public static final String PARAMETER_ALLOW_MULTIPLE_MATCHES = "allowMultipleMatches";
037
038        private final FhirContext myContext;
039        private final IBaseParameters myPatch;
040
041        public FhirPatchBuilder(@Nonnull FhirContext theFhirContext) {
042                Validate.notNull(theFhirContext, "theFhirContext must not be null");
043                myContext = theFhirContext;
044                myPatch = ParametersUtil.newInstance(myContext);
045        }
046
047        /**
048         * Add a new <b>ADD</b> operation to the FHIR Patch
049         */
050        public IAddStep1 add() {
051                return new AddBuilder();
052        }
053
054        /**
055         * Add a new <b>ADD</b> operation to the FHIR Patch
056         */
057        public IInsertStep1 insert() {
058                return new InsertBuilder();
059        }
060
061        /**
062         * Add a new <b>DELETE</b> operation to the FHIR Patch
063         */
064        public IDeleteStep1 delete() {
065                return new DeleteBuilder();
066        }
067
068        /**
069         * Add a new <b>REPLACE</b> operation to the FHIR Patch
070         */
071        public IReplaceStep1 replace() {
072                return new ReplaceBuilder();
073        }
074
075        /**
076         * Add a new <b>MOVE</b> operation to the FHIR Patch
077         */
078        public IMoveStep1 move() {
079                return new MoveBuilder();
080        }
081
082        /**
083         * Create and return the generated FHIRPath Patch Parameters document.
084         */
085        public IBaseParameters build() {
086                return myPatch;
087        }
088
089        /*
090         * NOTE: See the JavaDoc for BaseOperationBuilder below for an explanation
091         * of all the interfaces that follow.
092         */
093
094        /**
095         * This interface is returned after the final property for a given operation.
096         */
097        public interface IStepComplete {
098
099                /**
100                 * @return Returns a reference to the {@link FhirPatchBuilder} instance, so
101                 *      that additional operations can be added to the chain, or {@link #build()}
102                 *      can be called to return the created patch document.
103                 */
104                FhirPatchBuilder andThen();
105        }
106
107        /**
108         * Interface exposing the <code>path</code> property
109         *
110         * @param <T> The interface corresponding to the next step
111         */
112        public interface IStepPath<T> {
113
114                /**
115                 * The path to the element which will have an element added to it.
116                 */
117                T path(String thePath);
118        }
119
120        /**
121         * Interface exposing the <code>name</code> property
122         *
123         * @param <T> The interface corresponding to the next step
124         */
125        public interface IStepName<T> {
126
127                /**
128                 * The name of the element to add
129                 */
130                T name(String theName);
131        }
132
133        /**
134         * Interface exposing the <code>value</code> property
135         *
136         * @param <T> The interface corresponding to the next step
137         */
138        public interface IStepValue<T> {
139
140                /**
141                 * The new value
142                 */
143                T value(IBase theValue);
144        }
145
146        /**
147         * Interface exposing the <code>index</code> property
148         *
149         * @param <T> The interface corresponding to the next step
150         */
151        public interface IStepIndex<T> {
152
153                /**
154                 * An index associated with the elements at the given path
155                 */
156                T index(int theIndex);
157        }
158
159        /**
160         * Interface exposing the <code>source</code> property
161         *
162         * @param <T> The interface corresponding to the next step
163         */
164        public interface IStepSource<T> {
165
166                /**
167                 * An index associated with the elements at the given path
168                 */
169                T source(int theIndex);
170        }
171
172        /**
173         * Interface exposing the <code>destination</code> property
174         *
175         * @param <T> The interface corresponding to the next step
176         */
177        public interface IStepDestination<T> {
178
179                /**
180                 * An index associated with the elements at the given path
181                 */
182                T destination(int theIndex);
183        }
184
185        /**
186         * Step 1 for creating an <b>ADD</b> operation, returned by
187         * calling {@link #add()}
188         */
189        public interface IAddStep1 extends IStepPath<IAddStep2> {}
190
191        public interface IAddStep2 extends IStepName<IAddStep3> {}
192
193        public interface IAddStep3 extends IStepValue<IStepComplete> {}
194
195        /**
196         * Step 1 for creating an <b>INSERT</b> operation, returned by
197         * calling {@link #insert()}
198         */
199        public interface IInsertStep1 extends IStepPath<IInsertStep2> {}
200
201        public interface IInsertStep2 extends IStepIndex<IInsertStep3> {}
202
203        public interface IInsertStep3 extends IStepValue<IStepComplete> {}
204
205        /**
206         * Step 1 for creating an <b>DELETE</b> operation, returned by
207         * calling {@link #delete()}
208         */
209        public interface IDeleteStep1 extends IStepPath<IDeleteStepAfter> {}
210
211        public interface IDeleteStepAfter extends IStepComplete {
212
213                /**
214                 * Marks this operation as allowing multiple matches to be deleted. Per the FHIR Patch
215                 * specification, it is an error for the path to match multiple elements. This
216                 * method prevents the existence of multiple matches from causing an error, and deletes
217                 * all matches.
218                 * <p>
219                 * Note: This method adds a HAPI FHIR-specific parameter to the patch document and may
220                 * not work on other implementations.
221                 * </p>
222                 */
223                @SuppressWarnings("UnusedReturnValue")
224                IDeleteStepAfter allowMultipleMatches();
225        }
226
227        /**
228         * Step 1 for creating an <b>REPLACE</b> operation, returned by
229         * calling {@link #replace()}
230         */
231        public interface IReplaceStep1 extends IStepPath<IReplaceStep2> {}
232
233        public interface IReplaceStep2 extends IStepValue<IStepComplete> {}
234
235        /**
236         * Step 1 for creating a <b>MOVE</b> operation, returned by
237         * calling {@link #move()}
238         */
239        public interface IMoveStep1 extends IStepPath<IMoveStep2> {}
240
241        public interface IMoveStep2 extends IStepSource<IMoveStep3> {}
242
243        public interface IMoveStep3 extends IStepDestination<IStepComplete> {}
244
245        /**
246         * An instance of {@literal BaseOperationBuilder} is created for each new operation being
247         * created. This class has setters for each of the potential properties that the different
248         * patch operations can have, e.g. {@link #add()} takes "path", "name", and "value",
249         * {@link #delete()} takes only "path", etc.
250         * <p>
251         * This class uses some generics trickery to guide the creation of patch operations so that
252         * only the actual applicable properties are exposed depending on the specific operation.
253         * </p>
254         * <p>
255         * Each operation (add, delete, etc.) also has a concrete subclass of this class which actually
256         * builds the <code>Parameters.parameter</code> entry for the given operation type.
257         * </p>
258         *
259         * @param <RET_PATH>        The interface representing the next step after adding the path property
260         * @param <RET_NAME>        The interface representing the next step after adding the name property
261         * @param <RET_VALUE>       The interface representing the next step after adding the value property
262         * @param <RET_INDEX>       The interface representing the next step after adding the index property
263         * @param <RET_SOURCE>      The interface representing the next step after adding the source property
264         * @param <RET_DESTINATION> The interface representing the next step after adding the destination property
265         */
266        private abstract class BaseOperationBuilder<RET_PATH, RET_NAME, RET_VALUE, RET_INDEX, RET_SOURCE, RET_DESTINATION>
267                        implements IStepPath<RET_PATH>,
268                                        IStepName<RET_NAME>,
269                                        IStepValue<RET_VALUE>,
270                                        IStepIndex<RET_INDEX>,
271                                        IStepSource<RET_SOURCE>,
272                                        IStepDestination<RET_DESTINATION>,
273                                        IStepComplete {
274
275                protected String myPath;
276                protected String myName;
277                protected IBase myValue;
278                protected Integer myIndex;
279                protected Integer mySource;
280                protected Integer myDestination;
281
282                protected BaseOperationBuilder() {
283                        super();
284                }
285
286                @SuppressWarnings("unchecked")
287                @Override
288                public RET_PATH path(String thePath) {
289                        Validate.notBlank(thePath, "thePath must not be blank");
290                        myPath = thePath;
291                        return (RET_PATH) this;
292                }
293
294                @SuppressWarnings("unchecked")
295                @Override
296                public RET_NAME name(String theName) {
297                        Validate.notBlank(theName, "theName must not be blank");
298                        myName = theName;
299                        return (RET_NAME) this;
300                }
301
302                @SuppressWarnings("unchecked")
303                @Override
304                public RET_VALUE value(@Nonnull IBase theValue) {
305                        Validate.notNull(theValue, "theValue must not be null");
306                        myValue = theValue;
307                        return (RET_VALUE) this;
308                }
309
310                @SuppressWarnings("unchecked")
311                @Override
312                public RET_INDEX index(int theIndex) {
313                        Validate.isTrue(theIndex >= 0, "theIndex must not be negative");
314                        myIndex = theIndex;
315                        return (RET_INDEX) this;
316                }
317
318                @SuppressWarnings("unchecked")
319                @Override
320                public RET_DESTINATION destination(int theIndex) {
321                        Validate.isTrue(theIndex >= 0, "theIndex must not be negative");
322                        myDestination = theIndex;
323                        return (RET_DESTINATION) this;
324                }
325
326                @SuppressWarnings("unchecked")
327                @Override
328                public RET_SOURCE source(int theIndex) {
329                        Validate.isTrue(theIndex >= 0, "theIndex must not be negative");
330                        mySource = theIndex;
331                        return (RET_SOURCE) this;
332                }
333
334                @Override
335                public FhirPatchBuilder andThen() {
336                        return FhirPatchBuilder.this;
337                }
338        }
339
340        private class AddBuilder extends BaseOperationBuilder<IAddStep2, IAddStep3, IStepComplete, Void, Void, Void>
341                        implements IAddStep1, IAddStep2, IAddStep3 {
342
343                @Override
344                public IStepComplete value(@Nonnull IBase theValue) {
345                        super.value(theValue);
346
347                        IBase operation = ParametersUtil.addParameterToParameters(myContext, myPatch, "operation");
348                        ParametersUtil.addPartString(myContext, operation, "type", "add");
349                        ParametersUtil.addPartString(myContext, operation, "path", myPath);
350                        ParametersUtil.addPartString(myContext, operation, "name", myName);
351                        ParametersUtil.addPart(myContext, operation, "value", myValue);
352
353                        return this;
354                }
355        }
356
357        private class InsertBuilder
358                        extends BaseOperationBuilder<IInsertStep2, Void, IStepComplete, IInsertStep3, Void, Void>
359                        implements IInsertStep1, IInsertStep2, IInsertStep3 {
360
361                @Override
362                public IStepComplete value(@Nonnull IBase theValue) {
363                        super.value(theValue);
364
365                        IBase operation = ParametersUtil.addParameterToParameters(myContext, myPatch, "operation");
366                        ParametersUtil.addPartString(myContext, operation, "type", "insert");
367                        ParametersUtil.addPartString(myContext, operation, "path", myPath);
368                        ParametersUtil.addPartInteger(myContext, operation, "index", myIndex);
369                        ParametersUtil.addPart(myContext, operation, "value", myValue);
370
371                        return this;
372                }
373        }
374
375        private class DeleteBuilder extends BaseOperationBuilder<IDeleteStepAfter, Void, Void, Void, Void, Void>
376                        implements IDeleteStep1, IDeleteStepAfter {
377
378                private IBase myOperation;
379                private IBase myAllowMultipleMatches;
380
381                @Override
382                public IDeleteStepAfter allowMultipleMatches() {
383                        if (myAllowMultipleMatches == null) {
384                                myAllowMultipleMatches =
385                                                ParametersUtil.addPartBoolean(myContext, myOperation, PARAMETER_ALLOW_MULTIPLE_MATCHES, true);
386                        }
387                        return this;
388                }
389
390                @Override
391                public IDeleteStepAfter path(String thePath) {
392                        super.path(thePath);
393
394                        myOperation = ParametersUtil.addParameterToParameters(myContext, myPatch, "operation");
395                        ParametersUtil.addPartString(myContext, myOperation, "type", "delete");
396                        ParametersUtil.addPartString(myContext, myOperation, "path", myPath);
397
398                        return this;
399                }
400        }
401
402        private class ReplaceBuilder extends BaseOperationBuilder<IReplaceStep2, Void, IStepComplete, Void, Void, Void>
403                        implements IReplaceStep1, IReplaceStep2 {
404
405                @Override
406                public IStepComplete value(@Nonnull IBase theValue) {
407                        super.value(theValue);
408
409                        IBase operation = ParametersUtil.addParameterToParameters(myContext, myPatch, "operation");
410                        ParametersUtil.addPartString(myContext, operation, "type", "replace");
411                        ParametersUtil.addPartString(myContext, operation, "path", myPath);
412                        ParametersUtil.addPart(myContext, operation, "value", myValue);
413
414                        return this;
415                }
416        }
417
418        private class MoveBuilder extends BaseOperationBuilder<IMoveStep2, Void, Void, Void, IMoveStep3, IStepComplete>
419                        implements IMoveStep1, IMoveStep2, IMoveStep3 {
420
421                @Override
422                public IStepComplete destination(int theDestination) {
423                        super.destination(theDestination);
424
425                        IBase operation = ParametersUtil.addParameterToParameters(myContext, myPatch, "operation");
426                        ParametersUtil.addPartString(myContext, operation, "type", "move");
427                        ParametersUtil.addPartString(myContext, operation, "path", myPath);
428                        ParametersUtil.addPartInteger(myContext, operation, "source", mySource);
429                        ParametersUtil.addPartInteger(myContext, operation, "destination", myDestination);
430
431                        return this;
432                }
433        }
434}