001/*
002 * Copyright 2011-2013 UnboundID Corp.
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.scim.sdk;
019
020import com.unboundid.scim.data.GroupResource;
021import com.unboundid.scim.data.BaseResource;
022import com.unboundid.scim.data.ResourceFactory;
023import com.unboundid.scim.data.ServiceProviderConfig;
024import com.unboundid.scim.data.UserResource;
025import com.unboundid.scim.schema.CoreSchema;
026import com.unboundid.scim.schema.ResourceDescriptor;
027import org.apache.wink.client.ClientConfig;
028import org.apache.wink.client.RestClient;
029
030import javax.ws.rs.core.MediaType;
031import java.net.URI;
032import java.util.List;
033
034
035
036/**
037 * The SCIMService class represents a client connection to a SCIM service
038 * provider. It handles setting up and configuring the connection which will
039 * be used by the SCIMEndpoints that are obtained form this SCIMService.
040 */
041public class SCIMService
042{
043  private final RestClient client;
044  private final URI baseURL;
045
046  private MediaType acceptType = MediaType.APPLICATION_JSON_TYPE;
047  private MediaType contentType = MediaType.APPLICATION_JSON_TYPE;
048  private final boolean[] overrides = new boolean[3];
049  private String userAgent;
050
051  /**
052   * Constructs a new SCIMService that is configured from the provided
053   * <code>org.apache.wink.client.ClientConfig</code> instance.
054   *
055   * @param baseUrl The SCIM Service Provider URL.
056   * @param clientConfig The configuration to use.
057   */
058  public SCIMService(final URI baseUrl, final ClientConfig clientConfig)
059  {
060    this.baseURL = baseUrl;
061    this.client = new RestClient(clientConfig);
062  }
063
064  /**
065   * Constructs a new SCIMService that uses
066   * <code>java.net.HttpURLConnection</code> for the HTTP layer.
067   *
068   * @param baseUrl The SCIM Service Provider URL.
069   */
070  public SCIMService(final URI baseUrl)
071  {
072    this(baseUrl, new ClientConfig());
073  }
074
075  /**
076   * Constructs a new SCIMService with OAuth authentication support
077   * using the provided credentials. This SCIMService will use
078   * <code>java.net.HttpURLConnection</code> for the HTTP layer.
079
080   * @param baseUrl The SCIM Service Provider URL.
081   * @param oAuthToken The OAuth token.
082   */
083  public SCIMService(final URI baseUrl, final OAuthToken oAuthToken) {
084    this(baseUrl, new ClientConfig().handlers(new OAuthSecurityHandler
085      (oAuthToken)));
086  }
087
088  /**
089   * Constructs a new SCIMService with basic authentication support
090   * using the provided credentials. This SCIMService will use
091   * <code>java.net.HttpURLConnection</code> for the HTTP layer.
092   *
093   * @param baseUrl The SCIM Service Provider URL.
094   * @param username The username.
095   * @param password The password.
096   */
097  public SCIMService(final URI baseUrl, final String username,
098                     final String password)
099  {
100    this(baseUrl, new ClientConfig().handlers(new HttpBasicAuthSecurityHandler
101      (username,password)));
102  }
103
104  /**
105   * Returns a SCIMEndpoint with the current settings that can be used to
106   * invoke CRUD operations. Any changes to the SCIMService configuration will
107   * not be reflected in the returned SCIMEndpoint.
108   *
109   * @param resourceDescriptor The ResourceDescriptor of the endpoint.
110   * @param resourceFactory The ResourceFactory that should be used to
111   *                        create SCIM resource instances.
112   * @param <R> The type of SCIM resource instances.
113   * @return The SCIMEndpoint that can be used to invoke CRUD operations.
114   */
115  public <R extends BaseResource> SCIMEndpoint<R> getEndpoint(
116      final ResourceDescriptor resourceDescriptor,
117      final ResourceFactory<R> resourceFactory)
118  {
119    return new SCIMEndpoint<R>(this, client, resourceDescriptor,
120        resourceFactory);
121  }
122
123  /**
124   * Returns a SCIMEndpoint for the Users endpoint defined in the core schema.
125   *
126   * @return The SCIMEndpoint for the Users endpoint defined in the core schema.
127   */
128  public SCIMEndpoint<UserResource> getUserEndpoint()
129  {
130    return new SCIMEndpoint<UserResource>(this, client,
131        CoreSchema.USER_DESCRIPTOR, UserResource.USER_RESOURCE_FACTORY);
132  }
133
134  /**
135   * Returns a SCIMEndpoint for the Groups endpoint defined in the core schema.
136   *
137   * @return The SCIMEndpoint for the Groups endpoint defined in the
138   *         core schema.
139   */
140  public SCIMEndpoint<GroupResource> getGroupEndpoint()
141  {
142    return new SCIMEndpoint<GroupResource>(this, client,
143        CoreSchema.GROUP_DESCRIPTOR, GroupResource.GROUP_RESOURCE_FACTORY);
144  }
145
146  /**
147   * Returns a SCIMEndpoint for the Schemas endpoint. This endpoint allows for
148   * the retrieval of schema for all service provider supported resources.
149   *
150   * @return The SCIMEndpoint for the Schemas endpoint.
151   */
152  public SCIMEndpoint<ResourceDescriptor> getResourceSchemaEndpoint()
153  {
154    return new SCIMEndpoint<ResourceDescriptor>(this, client,
155        CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR,
156        ResourceDescriptor.RESOURCE_DESCRIPTOR_FACTORY);
157  }
158
159  /**
160   * Retrieves the ResourceDescriptor for the specified resource from the
161   * SCIM service provider.
162   *
163   * @param resourceName The name of the resource.
164   * @param schema The schema URN of the resource or <code>null</code>
165   *        to match only based on the name of the resource.
166   * @return The ResourceDescriptor for the specified resource or
167   *         <code>null</code> if none are found.
168   * @throws SCIMException If the ResourceDescriptor could not be read.
169   */
170  public ResourceDescriptor getResourceDescriptor(final String resourceName,
171                                                  final String schema)
172      throws SCIMException
173  {
174    final SCIMEndpoint<ResourceDescriptor> endpoint =
175        getResourceSchemaEndpoint();
176    String filter = "name eq \"" + resourceName + "\"";
177    if(schema != null)
178    {
179      filter += " and schema eq \"" + schema + "\"";
180    }
181    final Resources<ResourceDescriptor> resources = endpoint.query(filter);
182    if(resources.getTotalResults() == 0)
183    {
184      return null;
185    }
186    if(resources.getTotalResults() > 1)
187    {
188      throw new InvalidResourceException(
189          "The service provider returned multiple resource descriptors " +
190              "with resource name '" + resourceName);
191    }
192
193    ResourceDescriptor descriptor = resources.iterator().next();
194    if ("urn:unboundid:schemas:scim:ldap:1.0".equalsIgnoreCase(
195            descriptor.getSchema()))
196    {
197      //This is a convenience for when we're talking to the UnboundID Directory
198      //REST API; clients could set this themselves, but we'll do it for them
199      //in this case.
200      descriptor.setStrictMode(false);
201    }
202
203    return descriptor;
204  }
205
206
207
208  /**
209   * Retrieves the Service Provider Config from the SCIM service provider.
210   *
211   * @return  The Service Provider Config.
212   *
213   * @throws SCIMException  If the Service Provider Config could not be read.
214   */
215  public ServiceProviderConfig getServiceProviderConfig()
216      throws SCIMException
217  {
218    final SCIMEndpoint<ServiceProviderConfig> endpoint =
219        getEndpoint(CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR,
220                ServiceProviderConfig.SERVICE_PROVIDER_CONFIG_RESOURCE_FACTORY);
221
222    // The ServiceProviderConfig is a special case where there is only a
223    // single resource at the endpoint, so the id is not specified.
224    return endpoint.get(null);
225  }
226
227
228
229  /**
230   * Invoke a bulk request. The service provider will perform as
231   * many operations as possible without regard to the number of failures.
232   *
233   * @param operations  The operations to be performed.
234   *
235   * @return  The bulk response.
236   *
237   * @throws SCIMException  If the request fails.
238   */
239  public BulkResponse processBulkRequest(
240      final List<BulkOperation> operations)
241      throws SCIMException
242  {
243    return processBulkRequest(operations, -1);
244  }
245
246
247
248  /**
249   * Invoke a bulk request.
250   *
251   * @param operations    The operations to be performed.
252   * @param failOnErrors  The number of errors that the service provider will
253   *                      accept before the operation is terminated and an
254   *                      error response is returned. A value of -1 indicates
255   *                      the the service provider will continue to perform
256   *                      as many operations as possible without regard to
257   *                      failures.
258   *
259   * @return  The bulk response.
260   *
261   * @throws SCIMException  If the request fails.
262   */
263  public BulkResponse processBulkRequest(
264      final List<BulkOperation> operations,
265      final int failOnErrors)
266      throws SCIMException
267  {
268    final BulkEndpoint request = new BulkEndpoint(this, client);
269
270    return request.processRequest(operations, failOnErrors);
271  }
272
273
274
275  /**
276   * Retrieves the SCIM Service Provider URL.
277   *
278   * @return The SCIM Service Provider URL.
279   */
280  public URI getBaseURL() {
281    return baseURL;
282  }
283
284  /**
285   * Retrieves the content media type that should be used when writing data to
286   * the SCIM service provider.
287   *
288   * @return The content media type that should be used when writing data to
289   * the SCIM service provider.
290   */
291  public MediaType getContentType() {
292    return contentType;
293  }
294
295  /**
296   * Sets the content media type that should be used when writing data to
297   * the SCIM service provider.
298   *
299   * @param contentType he content media type that should be used when writing
300   * data to the SCIM service provider.
301   */
302  public void setContentType(final MediaType contentType) {
303    this.contentType = contentType;
304  }
305
306  /**
307   * Retrieves the accept media type that should be used when reading data from
308   * the SCIM service provider.
309   *
310   * @return The accept media type that should be used when reading data from
311   * the SCIM service provider.
312   */
313  public MediaType getAcceptType() {
314    return acceptType;
315  }
316
317  /**
318   * Sets the accept media type that should be used when reading data from
319   * the SCIM service provider.
320   *
321   * @param acceptType The accept media type that should be used when reading
322   * data from the SCIM service provider.
323   */
324  public void setAcceptType(final MediaType acceptType) {
325    this.acceptType = acceptType;
326  }
327
328  /**
329   * Retrieves the user-agent string that will be used in the HTTP request
330   * headers.
331   *
332   * @return The user-agent string. This may be null, in which case a default
333   * user-agent will be used.
334   */
335  public String getUserAgent() {
336    return userAgent;
337  }
338
339  /**
340   * Sets the user-agent string to use in the request headers.
341   *
342   * @param userAgent The user-agent string that should be used.
343   */
344  public void setUserAgent(final String userAgent) {
345    this.userAgent = userAgent;
346  }
347
348  /**
349   * Whether to override DELETE operations with POST.
350   *
351   * @return <code>true</code> to override DELETE operations with POST or
352   * <code>false</code> to use the DELETE method.
353   */
354  public boolean isOverrideDelete() {
355    return overrides[2];
356  }
357
358  /**
359   * Sets whether to override DELETE operations with POST.
360   *
361   * @param overrideDelete <code>true</code> to override DELETE operations with
362   * POST or <code>false</code> to use the DELETE method.
363   */
364  public void setOverrideDelete(final boolean overrideDelete) {
365    this.overrides[2] = overrideDelete;
366  }
367
368  /**
369   * Whether to override PATCH operations with POST.
370   *
371   * @return <code>true</code> to override PATCH operations with POST or
372   * <code>false</code> to use the PATCH method.
373   */
374  public boolean isOverridePatch() {
375    return overrides[1];
376  }
377
378  /**
379   * Sets whether to override PATCH operations with POST.
380   *
381   * @param overridePatch <code>true</code> to override PATCH operations with
382   * POST or <code>false</code> to use the PATCH method.
383   */
384  public void setOverridePatch(final boolean overridePatch) {
385    this.overrides[1] = overridePatch;
386  }
387
388  /**
389   * Whether to override PUT operations with POST.
390   *
391   * @return <code>true</code> to override PUT operations with POST or
392   * <code>false</code> to use the PUT method.
393   */
394  public boolean isOverridePut() {
395    return overrides[0];
396  }
397
398  /**
399   * Sets whether to override PUT operations with POST.
400   *
401   * @param overridePut <code>true</code> to override PUT operations with
402   * POST or <code>false</code> to use the PUT method.
403   */
404  public void setOverridePut(final boolean overridePut) {
405    this.overrides[0] = overridePut;
406  }
407}