
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}