001/*
002 * Copyright 2015-2021 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}