Java tutorial
/* * Copyright 2011-2018 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 <http://www.gnu.org/licenses>. */ 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 <R> The type of resource instances handled by this SCIMEndpoint. */ public class SCIMEndpoint<R extends BaseResource> { private final SCIMService scimService; private final ResourceDescriptor resourceDescriptor; private final ResourceFactory<R> 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<R> 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 * <code>null</code> 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<R> 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<R> 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<R> query(final String filter, final SortParameters sortParameters, final PageParameters pageParameters, final Map<String, String> 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 <code>null</code> 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 <code>null</code> 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<SCIMAttribute> attributesToUpdate, final List<String> 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 <code>null</code> 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 (<i>draft-scim-api-01</i>), * "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<SCIMAttribute> attributesToUpdate, final List<String> 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<R> diff = new Diff<R>(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<SCIMAttribute> attributesToUpdate, final List<String> 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 <code>null</code> 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<String> 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. * <p> * 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(); } }