All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.scim.sdk.SCIMEndpoint Maven / Gradle / Ivy

/*
 * Copyright 2011-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */

package com.unboundid.scim.sdk;

import com.unboundid.scim.data.Meta;
import com.unboundid.scim.data.ResourceFactory;
import com.unboundid.scim.data.BaseResource;
import com.unboundid.scim.facade.org.apache.wink.client.Resource;
import com.unboundid.scim.marshal.Marshaller;
import com.unboundid.scim.marshal.Unmarshaller;
import com.unboundid.scim.marshal.json.JsonMarshaller;
import com.unboundid.scim.marshal.json.JsonUnmarshaller;
import com.unboundid.scim.marshal.xml.XmlMarshaller;
import com.unboundid.scim.marshal.xml.XmlUnmarshaller;
import com.unboundid.scim.schema.ResourceDescriptor;

import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpException;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.NoHttpResponseException;
import org.apache.http.UnsupportedHttpVersionException;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.RedirectException;
import com.unboundid.scim.facade.org.apache.wink.client.
    ClientAuthenticationException;
import com.unboundid.scim.facade.org.apache.wink.client.ClientConfigException;
import com.unboundid.scim.facade.org.apache.wink.client.ClientResponse;
import com.unboundid.scim.facade.org.apache.wink.client.ClientRuntimeException;
import com.unboundid.scim.facade.org.apache.wink.client.ClientWebException;
import com.unboundid.scim.facade.org.apache.wink.client.RestClient;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;

import static com.unboundid.scim.sdk.SCIMConstants.*;



/**
 * This class represents a SCIM endpoint (ie. Users, Groups, etc.) and handles
 * all protocol-level interactions with the service provider. It acts as a
 * helper class for invoking CRUD operations of resources and processing their
 * results.
 *
 * @param  The type of resource instances handled by this SCIMEndpoint.
 */
public class SCIMEndpoint
{
  private final SCIMService scimService;
  private final ResourceDescriptor resourceDescriptor;
  private final ResourceFactory resourceFactory;
  private final Unmarshaller unmarshaller;
  private final Marshaller marshaller;
  private final MediaType contentType;
  private final MediaType acceptType;
  private final boolean[] overrides = new boolean[3];
  private final RestClient client;
  private final boolean useUrlSuffix;


  /**
   * Create a SCIMEndpoint with the provided information.
   *
   * @param scimService The SCIMService to use.
   * @param restClient The Wink REST client.
   * @param resourceDescriptor The resource descriptor of this endpoint.
   * @param resourceFactory The ResourceFactory that should be used to create
   *                        resource instances.
   */
  SCIMEndpoint(final SCIMService scimService,
               final RestClient restClient,
               final ResourceDescriptor resourceDescriptor,
               final ResourceFactory resourceFactory)
  {
    this.scimService = scimService;
    this.client = restClient;
    this.resourceDescriptor = resourceDescriptor;
    this.resourceFactory = resourceFactory;
    this.contentType = scimService.getContentType();
    this.acceptType = scimService.getAcceptType();
    this.overrides[0] = scimService.isOverridePut();
    this.overrides[1] = scimService.isOverridePatch();
    this.overrides[2] = scimService.isOverrideDelete();
    this.useUrlSuffix = scimService.isUseUrlSuffix();

    if (scimService.getContentType().equals(MediaType.APPLICATION_JSON_TYPE))
    {
      this.marshaller = new JsonMarshaller();
    }
    else
    {
      this.marshaller = new XmlMarshaller();
    }

    if(scimService.getAcceptType().equals(MediaType.APPLICATION_JSON_TYPE))
    {
      this.unmarshaller = new JsonUnmarshaller();
    }
    else
    {
      this.unmarshaller = new XmlUnmarshaller();
    }
  }



  /**
   * Constructs a new instance of a resource object which is empty. This
   * method does not interact with the SCIM service. It creates a local object
   * that may be provided to the {@link SCIMEndpoint#create} method after the
   * attributes have been specified.
   *
   * @return  A new instance of a resource object.
   */
  public R newResource()
  {
    return resourceFactory.createResource(resourceDescriptor, new SCIMObject());
  }

  /**
   * Retrieves a resource instance given the ID.
   *
   * @param id The ID of the resource to retrieve.
   * @return The retrieved resource.
   * @throws SCIMException If an error occurs.
   */
  public R get(final String id)
      throws SCIMException
  {
    return get(id, null, (String[]) null);
  }

  /**
   * Retrieves a resource instance given the ID, only if the current version
   * has been modified.
   *
   * @param id The ID of the resource to retrieve.
   * @param etag The entity tag that indicates the entry should be returned
   *             only if the entity tag of the current resource is different
   *             from the provided value and a value of "*" will not return
   *             an entry if the resource still exists. A value of
   *             null indicates unconditional return.
   * @param requestedAttributes The attributes of the resource to retrieve.
   * @return The retrieved resource.
   * @throws SCIMException If an error occurs.
   */
  public R get(final String id, final String etag,
               final String... requestedAttributes)
      throws SCIMException
  {
    final UriBuilder uriBuilder = UriBuilder.fromUri(scimService.getBaseURL());
    uriBuilder.path(resourceDescriptor.getEndpoint());

    // The ServiceProviderConfig is a special case where the id is not
    // specified.
    if (id != null)
    {
      uriBuilder.path(id);
    }

    URI uri = uriBuilder.build();
    Resource clientResource =
        client.resource(completeUri(uri));
    if(!useUrlSuffix)
    {
      clientResource.accept(acceptType);
    }
    clientResource.contentType(contentType);
    addAttributesQuery(clientResource, requestedAttributes);

    if(scimService.getUserAgent() != null)
    {
      clientResource.header(HttpHeaders.USER_AGENT, scimService.getUserAgent());
    }

    if(etag != null && !etag.isEmpty())
    {
      clientResource.header(HttpHeaders.IF_NONE_MATCH, etag);
    }

    ClientResponse response = null;
    try
    {
      response = clientResource.get();
      InputStream entity = response.getEntity(InputStream.class);

      if(response.getStatusType() == Response.Status.OK)
      {
        R resource = unmarshaller.unmarshal(entity, resourceDescriptor,
            resourceFactory);
        addMissingMetaData(response, resource);
        return resource;
      }
      else
      {
        throw createErrorResponseException(response, entity);
      }
    }
    catch(SCIMException e)
    {
      throw e;
    }
    catch(Exception e)
    {
      throw SCIMException.createException(getStatusCode(e),
                                          getExceptionMessage(e), e);
    }
    finally
    {
      if (response != null) {
        response.close();
      }
    }
  }

  /**
   * Retrieves all resource instances that match the provided filter.
   *
   * @param filter The filter that should be used.
   * @return The resource instances that match the provided filter.
   * @throws SCIMException If an error occurs.
   */
  public Resources query(final String filter)
      throws SCIMException
  {
    return query(filter, null, null);
  }

  /**
   * Retrieves all resource instances that match the provided filter.
   * Matching resources are returned sorted according to the provided
   * SortParameters. PageParameters maybe used to specify the range of
   * resource instances that are returned.
   *
   * @param filter The filter that should be used.
   * @param sortParameters The sort parameters that should be used.
   * @param pageParameters The page parameters that should be used.
   * @param requestedAttributes The attributes of the resource to retrieve.
   * @return The resource instances that match the provided filter.
   * @throws SCIMException If an error occurs.
   */
  public Resources  query(final String filter,
                            final SortParameters sortParameters,
                            final PageParameters pageParameters,
                            final String... requestedAttributes)
      throws SCIMException
  {
    return query(filter, sortParameters, pageParameters,
                    null, requestedAttributes);
  }

  /**
   * Retrieves all resource instances that match the provided filter.
   * Matching resources are returned sorted according to the provided
   * SortParameters. PageParameters maybe used to specify the range of
   * resource instances that are returned. Additional query parameters may
   * be specified using a Map of parameter names to their values.
   *
   * @param filter The filter that should be used.
   * @param sortParameters The sort parameters that should be used.
   * @param pageParameters The page parameters that should be used.
   * @param additionalQueryParams A map of additional query parameters that
   *                              should be included.
   * @param requestedAttributes The attributes of the resource to retrieve.
   * @return The resource instances that match the provided filter.
   * @throws SCIMException If an error occurs.
   */
  public Resources query(final String filter,
                            final SortParameters sortParameters,
                            final PageParameters pageParameters,
                            final Map additionalQueryParams,
                            final String... requestedAttributes)
      throws SCIMException
  {
    URI uri =
        UriBuilder.fromUri(scimService.getBaseURL()).path(
            resourceDescriptor.getEndpoint()).build();
    Resource clientResource =
        client.resource(completeUri(uri));
    if(!useUrlSuffix)
    {
      clientResource.accept(acceptType);
    }
    clientResource.contentType(contentType);
    addAttributesQuery(clientResource, requestedAttributes);
    if(scimService.getUserAgent() != null)
    {
      clientResource.header("User-Agent", scimService.getUserAgent());
    }
    if(filter != null)
    {
      clientResource.queryParam(QUERY_PARAMETER_FILTER, filter);
    }
    if(sortParameters != null)
    {
      clientResource.queryParam(QUERY_PARAMETER_SORT_BY,
          sortParameters.getSortBy().toString());
      if(!sortParameters.isAscendingOrder())
      {
        clientResource.queryParam(QUERY_PARAMETER_SORT_ORDER,
                                  sortParameters.getSortOrder());
      }
    }
    if(pageParameters != null)
    {
      clientResource.queryParam(QUERY_PARAMETER_PAGE_START_INDEX,
          String.valueOf(pageParameters.getStartIndex()));
      if (pageParameters.getCount() > 0)
      {
        clientResource.queryParam(QUERY_PARAMETER_PAGE_SIZE,
                                  String.valueOf(pageParameters.getCount()));
      }
    }
    if(additionalQueryParams != null)
    {
      for (String key : additionalQueryParams.keySet())
      {
        clientResource.queryParam(key, additionalQueryParams.get(key));
      }
    }

    ClientResponse response = null;
    try
    {
      response = clientResource.get();
      InputStream entity = response.getEntity(InputStream.class);

      if(response.getStatusType() == Response.Status.OK)
      {
        return unmarshaller.unmarshalResources(entity, resourceDescriptor,
            resourceFactory);
      }
      else
      {
        throw createErrorResponseException(response, entity);
      }
    }
    catch(SCIMException e)
    {
      throw e;
    }
    catch(Exception e)
    {
      throw SCIMException.createException(getStatusCode(e),
                                          getExceptionMessage(e), e);
    }
    finally
    {
      if (response != null) {
        response.close();
      }
    }
  }


  /**
   * Create the specified resource instance at the service provider and return
   * only the specified attributes from the newly inserted resource.
   *
   * @param resource The resource to create.
   * @param requestedAttributes The attributes of the newly inserted resource
   *                            to retrieve.
   * @return The newly inserted resource returned by the service provider.
   * @throws SCIMException If an error occurs.
   */
  public R create(final R resource,
                  final String... requestedAttributes)
      throws SCIMException
  {

    URI uri =
        UriBuilder.fromUri(scimService.getBaseURL()).path(
            resourceDescriptor.getEndpoint()).build();
    Resource clientResource =
        client.resource(completeUri(uri));
    if(!useUrlSuffix)
    {
      clientResource.accept(acceptType);
    }
    clientResource.contentType(contentType);
    addAttributesQuery(clientResource, requestedAttributes);
    if(scimService.getUserAgent() != null)
    {
      clientResource.header("User-Agent", scimService.getUserAgent());
    }

    StreamingOutput output = new StreamingOutput() {
      public void write(final OutputStream outputStream)
          throws IOException, WebApplicationException {
        try {
          marshaller.marshal(resource, outputStream);
        } catch (Exception e) {
          throw new WebApplicationException(e, Response.Status.BAD_REQUEST);
        }
      }
    };


    ClientResponse response = null;
    try
    {
      response = clientResource.post(output);
      InputStream entity = response.getEntity(InputStream.class);

      if(response.getStatusType() == Response.Status.CREATED)
      {
        R postedResource = unmarshaller.unmarshal(entity, resourceDescriptor,
            resourceFactory);
        addMissingMetaData(response, postedResource);
        return postedResource;
      }
      else
      {
        throw createErrorResponseException(response, entity);
      }
    }
    catch(SCIMException e)
    {
      throw e;
    }
    catch(Exception e)
    {
      throw SCIMException.createException(getStatusCode(e),
                                          getExceptionMessage(e), e);
    }
    finally
    {
      if (response != null) {
        response.close();
      }
    }
  }

  /**
   * Update the existing resource with the one provided (using the HTTP PUT
   * method).
   *
   * @param resource The modified resource to be updated.
   * @return The updated resource returned by the service provider.
   * @throws SCIMException If an error occurs.
   */
  public R update(final R resource)
      throws SCIMException
  {
    if(resource.getId() == null)
    {
      throw new InvalidResourceException("Resource must have a valid ID");
    }
    return update(resource.getId(),
                  resource.getMeta() == null ?
                      null : resource.getMeta().getVersion(),
                  resource);
  }

  /**
   * Update the existing resource with the one provided (using the HTTP PUT
   * method). This update is conditional upon the provided entity tag matching
   * the tag from the current resource. If (and only if) they match, the update
   * will be performed.
   *
   * @param resource The modified resource to be updated.
   * @param etag The entity tag value that is the expected value for the target
   *             resource. A value of null will not set an
   *             etag precondition and a value of "*" will perform an
   *             unconditional update.
   * @param requestedAttributes The attributes of updated resource
   *                            to return.
   * @return The updated resource returned by the service provider.
   * @throws SCIMException If an error occurs.
   */
  @Deprecated
  public R update(final R resource, final String etag,
                  final String... requestedAttributes)
      throws SCIMException
  {
    String id = resource.getId();
    if(id == null)
    {
      throw new InvalidResourceException("Resource must have a valid ID");
    }
    return update(id, etag, resource, requestedAttributes);
  }

  /**
   * Update the existing resource with the one provided (using the HTTP PUT
   * method). This update is conditional upon the provided entity tag matching
   * the tag from the current resource. If (and only if) they match, the update
   * will be performed.
   *
   * @param id The ID of the resource to update.
   * @param etag The entity tag value that is the expected value for the target
   *             resource. A value of null will not set an
   *             etag precondition and a value of "*" will perform an
   *             unconditional update.
   * @param resource The modified resource to be updated.
   * @param requestedAttributes The attributes of updated resource
   *                            to return.
   * @return The updated resource returned by the service provider.
   * @throws SCIMException If an error occurs.
   */
  public R update(final String id, final String etag, final R resource,
                  final String... requestedAttributes)
      throws SCIMException
  {
    URI uri =
        UriBuilder.fromUri(scimService.getBaseURL()).path(
            resourceDescriptor.getEndpoint()).path(id).build();
    Resource clientResource =
        client.resource(completeUri(uri));
    if(!useUrlSuffix)
    {
      clientResource.accept(acceptType);
    }
    clientResource.contentType(contentType);
    addAttributesQuery(clientResource, requestedAttributes);
    if(scimService.getUserAgent() != null)
    {
      clientResource.header(HttpHeaders.USER_AGENT, scimService.getUserAgent());
    }
    if(etag != null && !etag.isEmpty())
    {
      clientResource.header(HttpHeaders.IF_MATCH, etag);
    }

    StreamingOutput output = new StreamingOutput() {
      public void write(final OutputStream outputStream)
          throws IOException, WebApplicationException {
        try {
          marshaller.marshal(resource, outputStream);
        } catch (Exception e) {
          throw new WebApplicationException(e, Response.Status.BAD_REQUEST);
        }
      }
    };

    ClientResponse response = null;
    try
    {
      if(overrides[0])
      {
        clientResource.header("X-HTTP-Method-Override", "PUT");
        response = clientResource.post(output);
      }
      else
      {
        response = clientResource.put(output);
      }

      InputStream entity = response.getEntity(InputStream.class);

      if(response.getStatusType() == Response.Status.OK)
      {
        R postedResource = unmarshaller.unmarshal(entity, resourceDescriptor,
            resourceFactory);
        addMissingMetaData(response, postedResource);
        return postedResource;
      }
      else
      {
        throw createErrorResponseException(response, entity);
      }
    }
    catch(SCIMException e)
    {
      throw e;
    }
    catch(Exception e)
    {
      throw SCIMException.createException(getStatusCode(e),
                                          getExceptionMessage(e), e);
    }
    finally
    {
      if (response != null) {
        response.close();
      }
    }
  }

  /**
   * Update the existing resource with the one provided (using the HTTP PATCH
   * method). Note that if the {@code attributesToDelete} parameter is
   * specified, those attributes will be removed from the resource before the
   * {@code attributesToUpdate} are merged into the resource.
   *
   * @param resource The resource to update.
   * @param attributesToUpdate The list of attributes (and their new values) to
   *                           update on the resource.
   * @param attributesToDelete The list of attributes to delete on the resource.
   * @return The updated resource returned by the service provider, or
   *         an empty resource with returned meta data if the service provider
   *         returned no content.
   * @throws SCIMException If an error occurs.
   */
  public R update(final R resource,
                  final List attributesToUpdate,
                  final List attributesToDelete)
      throws SCIMException
  {
    if(resource.getId() == null)
    {
      throw new InvalidResourceException("Resource must have a valid ID");
    }

    return update(resource.getId(),
        resource.getMeta() == null ?
            null : resource.getMeta().getVersion(),
        attributesToUpdate,
        attributesToDelete);
  }

  /**
   * Update the existing resource with the one provided (using the HTTP PATCH
   * method). Note that if the {@code attributesToDelete} parameter is
   * specified, those attributes will be removed from the resource before the
   * {@code attributesToUpdate} are merged into the resource.
   *
   * @param id The ID of the resource to update.
   * @param etag The entity tag value that is the expected value for the target
   *             resource. A value of null will not set an
   *             etag precondition and a value of "*" will perform an
   *             unconditional update.
   * @param attributesToUpdate The list of attributes (and their new values) to
   *                           update on the resource. These attributes should
   *                           conform to Section 3.2.2 of the SCIM 1.1
   *                           specification (draft-scim-api-01),
   *                           "Modifying Resources with PATCH".
   * @param attributesToDelete The list of attributes to delete on the resource.
   * @param requestedAttributes The attributes of updated resource to return.
   * @return The updated resource returned by the service provider, or
   *         an empty resource with returned meta data if the
   *         {@code requestedAttributes} parameter was not specified and
   *         the service provider returned no content.
   * @throws SCIMException If an error occurs.
   */
  public R update(final String id, final String etag,
                  final List attributesToUpdate,
                  final List attributesToDelete,
                  final String... requestedAttributes)
          throws SCIMException
  {
    URI uri =
            UriBuilder.fromUri(scimService.getBaseURL()).path(
                    resourceDescriptor.getEndpoint()).path(id).build();
    Resource clientResource =
        client.resource(completeUri(uri));
    if(!useUrlSuffix)
    {
      clientResource.accept(acceptType);
    }
    clientResource.contentType(contentType);
    addAttributesQuery(clientResource, requestedAttributes);

    if(scimService.getUserAgent() != null)
    {
      clientResource.header(HttpHeaders.USER_AGENT, scimService.getUserAgent());
    }
    if(etag != null && !etag.isEmpty())
    {
      clientResource.header(HttpHeaders.IF_MATCH, etag);
    }

    Diff diff = new Diff(resourceDescriptor, attributesToDelete,
                               attributesToUpdate);
    final BaseResource resource =
            diff.toPartialResource(resourceFactory, true);

    StreamingOutput output = new StreamingOutput() {
      public void write(final OutputStream outputStream)
              throws IOException, WebApplicationException {
        try {
          marshaller.marshal(resource, outputStream);
        } catch (Exception e) {
          throw new WebApplicationException(e, Response.Status.BAD_REQUEST);
        }
      }
    };

    ClientResponse response = null;
    try
    {
      if(overrides[1])
      {
        clientResource.header("X-HTTP-Method-Override", "PATCH");
        response = clientResource.post(output);
      }
      else
      {
        try
        {
          // WINK client doesn't have an invoke method where it always
          // returns a ClientResponse like the other put, post, and get methods.
          // This throws a ClientWebException if the server returns a non 200
          // code.
          response =
              clientResource.invoke("PATCH", ClientResponse.class, output);
        }
        catch (ClientWebException e)
        {
          response = e.getResponse();
        }
      }

      InputStream entity = response.getEntity(InputStream.class);

      if(response.getStatusType() == Response.Status.OK)
      {
        R patchedResource = unmarshaller.unmarshal(entity, resourceDescriptor,
                resourceFactory);
        addMissingMetaData(response, patchedResource);
        return patchedResource;
      }
      else if (response.getStatusType() == Response.Status.NO_CONTENT)
      {
        R emptyResource =
            resourceFactory.createResource(resourceDescriptor,
                new SCIMObject());
        emptyResource.setId(id);
        addMissingMetaData(response, emptyResource);
        return emptyResource;
      }
      else
      {
        throw createErrorResponseException(response, entity);
      }
    }
    catch(SCIMException e)
    {
      throw e;
    }
    catch(Exception e)
    {
      throw SCIMException.createException(getStatusCode(e),
                                          getExceptionMessage(e), e);
    }
    finally
    {
      if (response != null) {
        response.close();
      }
    }
  }

  /**
   * Update the existing resource with the one provided (using the HTTP PATCH
   * method). Note that if the {@code attributesToDelete} parameter is
   * specified, those attributes will be removed from the resource before the
   * {@code attributesToUpdate} are merged into the resource.
   *
   * @param id The ID of the resource to update.
   * @param attributesToUpdate The list of attributes (and their new values) to
   *                           update on the resource.
   * @param attributesToDelete The list of attributes to delete on the resource.
   * @throws SCIMException If an error occurs.
   */
  @Deprecated
  public void update(final String id,
                     final List attributesToUpdate,
                     final List attributesToDelete)
           throws SCIMException
  {
    update(id, null, attributesToUpdate, attributesToDelete);
  }

  /**
   * Delete the resource instance specified by the provided ID.
   *
   * @param id The ID of the resource to delete.
   * @throws SCIMException If an error occurs.
   */
  @Deprecated
  public void delete(final String id)
      throws SCIMException
  {
    delete(id, null);
  }

  /**
   * Delete the specified resource instance.
   *
   * @param resource The resource to delete.
   * @throws SCIMException If an error occurs.
   */
  public void delete(final R resource)
      throws SCIMException
  {
    delete(resource.getId(), resource.getMeta() == null ?
        null : resource.getMeta().getVersion());
  }

  /**
   * Delete the resource instance specified by the provided ID. This delete is
   * conditional upon the provided entity tag matching the tag from the
   * current resource. If (and only if) they match, the delete will be
   * performed.
   *
   * @param id The ID of the resource to delete.
   * @param etag The entity tag value that is the expected value for the target
   *             resource. A value of null will not set an
   *             etag precondition and a value of "*" will perform an
   *             unconditional delete.
   * @throws SCIMException If an error occurs.
   */
  public void delete(final String id, final String etag)
      throws SCIMException
  {
    URI uri =
        UriBuilder.fromUri(scimService.getBaseURL()).path(
            resourceDescriptor.getEndpoint()).path(id).build();
    Resource clientResource =
        client.resource(completeUri(uri));
    if(!useUrlSuffix)
    {
      clientResource.accept(acceptType);
    }
    clientResource.contentType(contentType);
    if(scimService.getUserAgent() != null)
    {
      clientResource.header(HttpHeaders.USER_AGENT, scimService.getUserAgent());
    }
    if(etag != null && !etag.isEmpty())
    {
      clientResource.header(HttpHeaders.IF_MATCH, etag);
    }


    ClientResponse response = null;
    try
    {
      if(overrides[2])
      {
        clientResource.header("X-HTTP-Method-Override", "DELETE");
        response = clientResource.post(null);
      } else
      {
        response = clientResource.delete();
      }
      if(response.getStatusType() != Response.Status.OK)
      {
        InputStream entity = response.getEntity(InputStream.class);
        throw createErrorResponseException(response, entity);
      }
      else
      {
        response.consumeContent();
      }
    }
    catch(SCIMException e)
    {
      throw e;
    }
    catch(ClientRuntimeException cre)
    {
      // Treat SocketTimeoutException like HTTP 100
      Throwable rootCause = StaticUtils.getRootCause(cre);
      if (rootCause == null || !(rootCause instanceof SocketTimeoutException))
      {
        throw cre;
      }
    }
    catch(Exception e)
    {
      throw SCIMException.createException(getStatusCode(e),
                                          getExceptionMessage(e), e);
    }
    finally
    {
      if (response != null) {
        response.close();
      }
    }
  }

  /**
   * Add the attributes query parameter to the client resource request.
   *
   * @param clientResource The Wink client resource.
   * @param requestedAttributes The SCIM attributes to request.
   */
  private void addAttributesQuery(
      final Resource clientResource,
      final String... requestedAttributes)
  {
    if(requestedAttributes != null && requestedAttributes.length > 0)
    {
      StringBuilder stringBuilder = new StringBuilder();
      for(int i = 0; i < requestedAttributes.length; i++)
      {
        stringBuilder.append(requestedAttributes[i]);
        if(i < requestedAttributes.length - 1)
        {
          stringBuilder.append(",");
        }
      }
      clientResource.queryParam("attributes", stringBuilder.toString());
    }
  }

  /**
   * Add meta values from the response header to the meta complex attribute
   * if they are missing.
   *
   * @param response The response from the service provider.
   * @param resource The return resource instance.
   */
  private void addMissingMetaData(final ClientResponse response,
                                  final R resource)
  {
    URI headerLocation = null;
    String headerEtag = null;
    List values;
    if(response.getStatusType() == Response.Status.CREATED ||
                    response.getStatusType().getFamily() ==
                        Response.Status.Family.REDIRECTION)
    {
      values = response.getHeaders().get(HttpHeaders.LOCATION);
    }
    else
    {
      values = response.getHeaders().get(HttpHeaders.CONTENT_LOCATION);
      if(values == null || values.isEmpty())
      {
        // Fall back to using Location header to maintain backwards
        // compatibility.
        values = response.getHeaders().get(HttpHeaders.LOCATION);
      }
    }
    if(values != null && !values.isEmpty())
    {
      headerLocation = URI.create(values.get(0));
    }
    values = response.getHeaders().get(HttpHeaders.ETAG);
    if(values != null && !values.isEmpty())
    {
      headerEtag = values.get(0);
    }
    Meta meta = resource.getMeta();
    if(meta == null)
    {
      meta = new Meta(null, null, null, null);
    }
    boolean modified = false;
    if(headerLocation != null && meta.getLocation() == null)
    {
      meta.setLocation(headerLocation);
      modified = true;
    }
    if(headerEtag != null && meta.getVersion() == null)
    {
      meta.setVersion(headerEtag);
      modified = true;
    }
    if(modified)
    {
      resource.setMeta(meta);
    }
  }



  /**
   * Returns a SCIM exception representing the error response.
   *
   * @param response  The client response.
   * @param entity    The response content.
   *
   * @return  The SCIM exception representing the error response.
   */
  private SCIMException createErrorResponseException(
      final ClientResponse response,
      final InputStream entity)
  {
    SCIMException scimException = null;

    if(entity != null)
    {
      try
      {
        scimException = unmarshaller.unmarshalError(entity);
      }
      catch (InvalidResourceException e)
      {
        // The response content could not be parsed as a SCIM error
        // response, which is the case if the response is a more general
        // HTTP error. It is better to just provide the HTTP response
        // details in this case.
        Debug.debugException(e);
      }
    }

    if(scimException == null)
    {
      scimException = SCIMException.createException(
          response.getStatusCode(), response.getMessage());
    }

    if(response.getStatusType() == Response.Status.PRECONDITION_FAILED)
    {
      scimException = new PreconditionFailedException(
          scimException.getMessage(),
          response.getHeaders().getFirst(HttpHeaders.ETAG),
          scimException.getCause());
    }
    else if(response.getStatusType() == Response.Status.NOT_MODIFIED)
    {
      scimException = new NotModifiedException(
          scimException.getMessage(),
          response.getHeaders().getFirst(HttpHeaders.ETAG),
          scimException.getCause());
    }

    return scimException;
  }




  /**
   * Returns the complete resource URI by appending the suffix if necessary.
   *
   * @param uri The URI to complete.
   * @return The completed URI.
   */
  private URI completeUri(final URI uri)
  {
    URI completedUri = uri;
    if(useUrlSuffix)
    {
      try
      {
      if(acceptType == MediaType.APPLICATION_JSON_TYPE)
      {
        completedUri = new URI(uri.toString() + ".json");
      }
      else if(acceptType == MediaType.APPLICATION_XML_TYPE)
      {
        completedUri = new URI(uri.toString() + ".xml");
      }
      }
      catch(URISyntaxException e)
      {
        throw new RuntimeException(e);
      }
    }
    return completedUri;
  }

  /**
   * Tries to deduce the most appropriate HTTP response code from the given
   * exception. This method expects most exceptions to be one of 3 or 4
   * expected runtime exceptions that are common to Wink and the Apache Http
   * Client library.
   * 

* Note this method can return -1 for the special case of a * {@link com.unboundid.scim.sdk.ConnectException}, in which the service * provider could not be reached at all. * * @param t the Exception instance to analyze * @return the most appropriate HTTP status code */ static int getStatusCode(final Throwable t) { Throwable rootCause = t; if(rootCause instanceof ClientRuntimeException) { //Pull the underlying cause out of the ClientRuntimeException rootCause = StaticUtils.getRootCause(t); } if(rootCause instanceof HttpResponseException) { HttpResponseException hre = (HttpResponseException) rootCause; return hre.getStatusCode(); } else if(rootCause instanceof HttpException) { if(rootCause instanceof RedirectException) { return 300; } else if(rootCause instanceof AuthenticationException) { return 401; } else if(rootCause instanceof MethodNotSupportedException) { return 501; } else if(rootCause instanceof UnsupportedHttpVersionException) { return 505; } } else if(rootCause instanceof IOException) { if(rootCause instanceof NoHttpResponseException) { return 503; } else if(rootCause instanceof ConnectionClosedException) { return 503; } else { return -1; } } if(t instanceof ClientWebException) { ClientWebException cwe = (ClientWebException) t; return cwe.getResponse().getStatusCode(); } else if(t instanceof ClientAuthenticationException) { return 401; } else if(t instanceof ClientConfigException) { return 400; } else { return 500; } } /** * Extracts the exception message from the root cause of the exception if * possible. * * @param t the original Throwable that was caught. This may be null. * @return the exception message from the root cause of the exception, or * null if the specified Throwable is null or the message cannot be * determined. */ static String getExceptionMessage(final Throwable t) { if(t == null) { return null; } Throwable rootCause = StaticUtils.getRootCause(t); return rootCause.getMessage(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy