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