001/* 002 * Copyright 2015-2024 Ping Identity Corporation 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim2.client.requests; 019 020import com.fasterxml.jackson.databind.JsonNode; 021import com.unboundid.scim2.common.Path; 022import com.unboundid.scim2.common.ScimResource; 023import com.unboundid.scim2.common.annotations.NotNull; 024import com.unboundid.scim2.common.annotations.Nullable; 025import com.unboundid.scim2.common.exceptions.ScimException; 026import com.unboundid.scim2.common.messages.PatchOperation; 027import com.unboundid.scim2.common.messages.PatchRequest; 028import com.unboundid.scim2.common.utils.JsonUtils; 029 030import jakarta.ws.rs.ProcessingException; 031import jakarta.ws.rs.client.Entity; 032import jakarta.ws.rs.client.Invocation; 033import jakarta.ws.rs.client.WebTarget; 034import jakarta.ws.rs.core.HttpHeaders; 035import jakarta.ws.rs.core.Response; 036 037import java.util.Collection; 038import java.util.LinkedList; 039import java.util.List; 040 041/** 042 * A builder for SCIM modify requests. 043 */ 044public abstract class ModifyRequestBuilder<T extends ModifyRequestBuilder<T>> 045 extends ResourceReturningRequestBuilder<T> 046{ 047 /** 048 * The list of patch operations to include in the request. 049 */ 050 @NotNull 051 protected final List<PatchOperation> operations; 052 053 /** 054 * The version to match. 055 */ 056 @Nullable 057 protected String version; 058 059 /** 060 * Create a new ModifyRequestBuilder. 061 * 062 * @param target The WebTarget to PATCH. 063 */ 064 private ModifyRequestBuilder(@NotNull final WebTarget target) 065 { 066 super(target); 067 this.operations = new LinkedList<PatchOperation>(); 068 } 069 070 /** 071 * {@inheritDoc} 072 */ 073 @Override 074 @NotNull 075 Invocation.Builder buildRequest() 076 { 077 Invocation.Builder request = super.buildRequest(); 078 if(version != null) 079 { 080 request.header(HttpHeaders.IF_MATCH, version); 081 } 082 return request; 083 } 084 085 /** 086 * A builder for SCIM modify requests for where the returned resource POJO 087 * type will be the same as the original. 088 */ 089 public static final class Generic<T extends ScimResource> 090 extends ModifyRequestBuilder<Generic<T>> 091 { 092 @NotNull 093 private final T resource; 094 095 /** 096 * Create a new generic modify request builder. 097 * 098 * @param target The WebTarget to PATCH. 099 * @param resource The SCIM resource to retrieve. 100 */ 101 public Generic(@NotNull final WebTarget target, @NotNull final T resource) 102 { 103 super(target); 104 this.resource = resource; 105 } 106 107 108 /** 109 * Modify the resource only if the resource has not been modified from the 110 * resource provided. 111 * 112 * @return This builder. 113 */ 114 @NotNull 115 public Generic<T> ifMatch() 116 { 117 this.version = getResourceVersion(resource); 118 return this; 119 } 120 121 /** 122 * Invoke the SCIM modify request. 123 * 124 * @return The successfully modified SCIM resource. 125 * @throws ProcessingException If a JAX-RS runtime exception occurred. 126 * @throws ScimException If the SCIM service provider responded with an error. 127 */ 128 @NotNull 129 @SuppressWarnings("unchecked") 130 public T invoke() throws ScimException 131 { 132 return (T) invoke(resource.getClass()); 133 } 134 135 /** 136 * Invoke the SCIM modify request. 137 * 138 * @param <C> The type of object to return. 139 * @param cls The Java class object used to determine the type to return. 140 * @return The successfully modified SCIM resource. 141 * @throws ProcessingException If a JAX-RS runtime exception occurred. 142 * @throws ScimException If the SCIM service provider responded with an error. 143 */ 144 @NotNull 145 public <C> C invoke(@NotNull final Class<C> cls) throws ScimException 146 { 147 PatchRequest patchRequest = new PatchRequest(operations); 148 Response response = buildRequest().method("PATCH", 149 Entity.entity(patchRequest, getContentType())); 150 try 151 { 152 if (response.getStatusInfo().getFamily() == 153 Response.Status.Family.SUCCESSFUL) 154 { 155 return response.readEntity(cls); 156 } else 157 { 158 throw toScimException(response); 159 } 160 } 161 finally 162 { 163 response.close(); 164 } 165 } 166 } 167 168 169 /** 170 * A builder for SCIM modify requests for where the returned resource POJO 171 * type will be provided. 172 */ 173 public static final class Typed extends ModifyRequestBuilder<Typed> 174 { 175 /** 176 * Create a new generic modify request builder. 177 * 178 * @param target The WebTarget to PATCH. 179 */ 180 public Typed(@NotNull final WebTarget target) 181 { 182 super(target); 183 } 184 185 /** 186 * Modify the resource only if the resource has not been modified since the 187 * provided version. 188 * 189 * @param version The version of the resource to compare. 190 * @return This builder. 191 */ 192 @NotNull 193 public Typed ifMatch(@Nullable final String version) 194 { 195 this.version = version; 196 return this; 197 } 198 199 /** 200 * Invoke the SCIM modify request. 201 * 202 * @param <T> The type of object to return. 203 * @param cls The Java class object used to determine the type to return. 204 * @return The successfully modified SCIM resource. 205 * @throws ProcessingException If a JAX-RS runtime exception occurred. 206 * @throws ScimException If the SCIM service provider responded with an error. 207 */ 208 @NotNull 209 public <T> T invoke(@NotNull final Class<T> cls) throws ScimException 210 { 211 PatchRequest patchRequest = new PatchRequest(operations); 212 Response response = buildRequest().method("PATCH", 213 Entity.entity(patchRequest, getContentType())); 214 try 215 { 216 if(response.getStatusInfo().getFamily() == 217 Response.Status.Family.SUCCESSFUL) 218 { 219 return response.readEntity(cls); 220 } 221 else 222 { 223 throw toScimException(response); 224 } 225 } 226 finally 227 { 228 response.close(); 229 } 230 } 231 } 232 233 /** 234 * Set value of the attribute specified by the path, replacing any existing 235 * value(s). 236 * 237 * @param path The path to the attribute whose value to set. 238 * @param object The value to set. 239 * 240 * @return This patch operation request. 241 * @throws ScimException If the path is invalid. 242 */ 243 @NotNull 244 public T replaceValue(@NotNull final String path, 245 @Nullable final Object object) 246 throws ScimException 247 { 248 return replaceValue(Path.fromString(path), object); 249 } 250 251 /** 252 * Set value of the attribute specified by the path, replacing any existing 253 * value(s). 254 * 255 * @param path The path to the attribute whose value to set. 256 * @param object The value to set. 257 * 258 * @return This patch operation request. 259 */ 260 @NotNull 261 public T replaceValue(@NotNull final Path path, @Nullable final Object object) 262 { 263 JsonNode newObjectNode = JsonUtils.valueToNode(object); 264 return addOperation(PatchOperation.replace(path, newObjectNode)); 265 } 266 267 /** 268 * Set values of the attribute specified by the path, replacing any existing 269 * values. 270 * 271 * @param path The path to the attribute whose value to set. 272 * @param objects The value(s) to set. 273 * 274 * @return This patch operation request. 275 * @throws ScimException If the path is invalid. 276 */ 277 @NotNull 278 public T replaceValues(@NotNull final String path, 279 @Nullable final Collection<Object> objects) 280 throws ScimException 281 { 282 return replaceValues(Path.fromString(path), objects); 283 } 284 285 /** 286 * Set values of the attribute specified by the path, replacing any existing 287 * values. 288 * 289 * @param path The path to the attribute whose value to set. 290 * @param objects The value(s) to set. 291 * 292 * @return This patch operation request. 293 */ 294 @NotNull 295 public T replaceValues(@NotNull final Path path, 296 @Nullable final Collection<Object> objects) 297 { 298 JsonNode newObjectNode = JsonUtils.valueToNode(objects); 299 return addOperation(PatchOperation.replace(path, newObjectNode)); 300 } 301 302 /** 303 * Set values of the attribute specified by the path, replacing any existing 304 * values. 305 * 306 * @param path The path to the attribute whose value to set. 307 * @param objects The value(s) to set. 308 * 309 * @return This patch operation request. 310 * @throws ScimException If the path is invalid. 311 */ 312 @NotNull 313 public T replaceValues(@NotNull final String path, 314 @NotNull final Object... objects) 315 throws ScimException 316 { 317 return replaceValues(Path.fromString(path), objects); 318 319 } 320 321 /** 322 * Set values of the attribute specified by the path, replacing any existing 323 * values. 324 * 325 * @param path The path to the attribute whose value to set. 326 * @param objects The value(s) to set. 327 * 328 * @return This patch operation request. 329 */ 330 @NotNull 331 public T replaceValues(@NotNull final Path path, 332 @NotNull final Object... objects) 333 { 334 JsonNode newObjectNode = JsonUtils.valueToNode(objects); 335 return addOperation(PatchOperation.replace(path, newObjectNode)); 336 } 337 338 /** 339 * Add values to the multi-valued attribute specified by the path. 340 * 341 * @param path The path to the multi-valued attribute. 342 * @param objects The values to add. 343 * 344 * @return This patch operation request. 345 * @throws ScimException If the path is invalid. 346 */ 347 @NotNull 348 public T addValues(@NotNull final String path, 349 @NotNull final Collection<?> objects) 350 throws ScimException 351 { 352 return addValues(Path.fromString(path), objects); 353 } 354 355 /** 356 * Add values to the multi-valued attribute specified by the path. 357 * 358 * @param path The path to the multi-valued attribute. 359 * @param objects The values to add. 360 * 361 * @return This patch operation request. 362 */ 363 @NotNull 364 public T addValues(@NotNull final Path path, 365 @NotNull final Collection<?> objects) 366 { 367 JsonNode newObjectNode = JsonUtils.valueToNode(objects); 368 return addOperation(PatchOperation.add(path, newObjectNode)); 369 } 370 371 /** 372 * Add values to the multi-valued attribute specified by the path. 373 * 374 * @param path The path to the multi-valued attribute. 375 * @param objects The values to add. 376 * 377 * @return This patch operation request. 378 * @throws ScimException If the path is invalid. 379 */ 380 @NotNull 381 public T addValues(@NotNull final String path, 382 @NotNull final Object... objects) 383 throws ScimException 384 { 385 return addValues(Path.fromString(path), objects); 386 387 388 } 389 390 /** 391 * Add values to the multi-valued attribute specified by the path. 392 * 393 * @param path The path to the multi-valued attribute. 394 * @param objects The values to add. 395 * 396 * @return This patch operation request. 397 */ 398 @NotNull 399 public T addValues(@NotNull final Path path, 400 @NotNull final Object... objects) 401 { 402 JsonNode newObjectNode = JsonUtils.valueToNode(objects); 403 return addOperation(PatchOperation.add(path, newObjectNode)); 404 } 405 406 /** 407 * Remove all values of the attribute specified by the path. 408 * 409 * @param path The path to the attribute whose value to remove. 410 * 411 * @return This patch operation request. 412 * @throws ScimException If the path is invalid. 413 */ 414 @NotNull 415 public T removeValues(@NotNull final String path) 416 throws ScimException 417 { 418 return removeValues(Path.fromString(path)); 419 420 } 421 422 /** 423 * Remove all values of the attribute specified by the path. 424 * 425 * @param path The path to the attribute whose value to remove. 426 * 427 * @return This patch operation request. 428 * @throws ScimException If the path is invalid. 429 */ 430 @NotNull 431 public T removeValues(@NotNull final Path path) 432 throws ScimException 433 { 434 return addOperation(PatchOperation.remove(path)); 435 } 436 437 /** 438 * Add a new patch operation this this patch request. 439 * 440 * @param op The patch operation to add. 441 * 442 * @return This patch operation request. 443 */ 444 @NotNull 445 @SuppressWarnings("unchecked") 446 public T addOperation(@NotNull final PatchOperation op) 447 { 448 operations.add(op); 449 return (T) this; 450 } 451}