001    /*
002     * Copyright 2011-2012 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    
018    package com.unboundid.scim.sdk;
019    
020    import com.unboundid.scim.data.GroupResource;
021    import com.unboundid.scim.data.BaseResource;
022    import com.unboundid.scim.data.ResourceFactory;
023    import com.unboundid.scim.data.ServiceProviderConfig;
024    import com.unboundid.scim.data.UserResource;
025    import com.unboundid.scim.schema.CoreSchema;
026    import com.unboundid.scim.schema.ResourceDescriptor;
027    import org.apache.wink.client.ClientConfig;
028    import org.apache.wink.client.RestClient;
029    
030    import javax.ws.rs.core.MediaType;
031    import java.net.URI;
032    import 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     */
041    public 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        return resources.iterator().next();
193      }
194    
195    
196    
197      /**
198       * Retrieves the Service Provider Config from the SCIM service provider.
199       *
200       * @return  The Service Provider Config.
201       *
202       * @throws SCIMException  If the Service Provider Config could not be read.
203       */
204      public ServiceProviderConfig getServiceProviderConfig()
205          throws SCIMException
206      {
207        final SCIMEndpoint<ServiceProviderConfig> endpoint =
208            getEndpoint(CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR,
209                    ServiceProviderConfig.SERVICE_PROVIDER_CONFIG_RESOURCE_FACTORY);
210    
211        // The ServiceProviderConfig is a special case where there is only a
212        // single resource at the endpoint, so the id is not specified.
213        return endpoint.get(null);
214      }
215    
216    
217    
218      /**
219       * Invoke a bulk request. The service provider will perform as
220       * many operations as possible without regard to the number of failures.
221       *
222       * @param operations  The operations to be performed.
223       *
224       * @return  The bulk response.
225       *
226       * @throws SCIMException  If the request fails.
227       */
228      public BulkResponse processBulkRequest(
229          final List<BulkOperation> operations)
230          throws SCIMException
231      {
232        return processBulkRequest(operations, -1);
233      }
234    
235    
236    
237      /**
238       * Invoke a bulk request.
239       *
240       * @param operations    The operations to be performed.
241       * @param failOnErrors  The number of errors that the service provider will
242       *                      accept before the operation is terminated and an
243       *                      error response is returned. A value of -1 indicates
244       *                      the the service provider will continue to perform
245       *                      as many operations as possible without regard to
246       *                      failures.
247       *
248       * @return  The bulk response.
249       *
250       * @throws SCIMException  If the request fails.
251       */
252      public BulkResponse processBulkRequest(
253          final List<BulkOperation> operations,
254          final int failOnErrors)
255          throws SCIMException
256      {
257        final BulkEndpoint request = new BulkEndpoint(this, client);
258    
259        return request.processRequest(operations, failOnErrors);
260      }
261    
262    
263    
264      /**
265       * Retrieves the SCIM Service Provider URL.
266       *
267       * @return The SCIM Service Provider URL.
268       */
269      public URI getBaseURL() {
270        return baseURL;
271      }
272    
273      /**
274       * Retrieves the content media type that should be used when writing data to
275       * the SCIM service provider.
276       *
277       * @return The content media type that should be used when writing data to
278       * the SCIM service provider.
279       */
280      public MediaType getContentType() {
281        return contentType;
282      }
283    
284      /**
285       * Sets the content media type that should be used when writing data to
286       * the SCIM service provider.
287       *
288       * @param contentType he content media type that should be used when writing
289       * data to the SCIM service provider.
290       */
291      public void setContentType(final MediaType contentType) {
292        this.contentType = contentType;
293      }
294    
295      /**
296       * Retrieves the accept media type that should be used when reading data from
297       * the SCIM service provider.
298       *
299       * @return The accept media type that should be used when reading data from
300       * the SCIM service provider.
301       */
302      public MediaType getAcceptType() {
303        return acceptType;
304      }
305    
306      /**
307       * Sets the accept media type that should be used when reading data from
308       * the SCIM service provider.
309       *
310       * @param acceptType The accept media type that should be used when reading
311       * data from the SCIM service provider.
312       */
313      public void setAcceptType(final MediaType acceptType) {
314        this.acceptType = acceptType;
315      }
316    
317      /**
318       * Retrieves the user-agent string that will be used in the HTTP request
319       * headers.
320       *
321       * @return The user-agent string. This may be null, in which case a default
322       * user-agent will be used.
323       */
324      public String getUserAgent() {
325        return userAgent;
326      }
327    
328      /**
329       * Sets the user-agent string to use in the request headers.
330       *
331       * @param userAgent The user-agent string that should be used.
332       */
333      public void setUserAgent(final String userAgent) {
334        this.userAgent = userAgent;
335      }
336    
337      /**
338       * Whether to override DELETE operations with POST.
339       *
340       * @return <code>true</code> to override DELETE operations with POST or
341       * <code>false</code> to use the DELETE method.
342       */
343      public boolean isOverrideDelete() {
344        return overrides[2];
345      }
346    
347      /**
348       * Sets whether to override DELETE operations with POST.
349       *
350       * @param overrideDelete <code>true</code> to override DELETE operations with
351       * POST or <code>false</code> to use the DELETE method.
352       */
353      public void setOverrideDelete(final boolean overrideDelete) {
354        this.overrides[2] = overrideDelete;
355      }
356    
357      /**
358       * Whether to override PATCH operations with POST.
359       *
360       * @return <code>true</code> to override PATCH operations with POST or
361       * <code>false</code> to use the PATCH method.
362       */
363      public boolean isOverridePatch() {
364        return overrides[1];
365      }
366    
367      /**
368       * Sets whether to override PATCH operations with POST.
369       *
370       * @param overridePatch <code>true</code> to override PATCH operations with
371       * POST or <code>false</code> to use the PATCH method.
372       */
373      public void setOverridePatch(final boolean overridePatch) {
374        this.overrides[1] = overridePatch;
375      }
376    
377      /**
378       * Whether to override PUT operations with POST.
379       *
380       * @return <code>true</code> to override PUT operations with POST or
381       * <code>false</code> to use the PUT method.
382       */
383      public boolean isOverridePut() {
384        return overrides[0];
385      }
386    
387      /**
388       * Sets whether to override PUT operations with POST.
389       *
390       * @param overridePut <code>true</code> to override PUT operations with
391       * POST or <code>false</code> to use the PUT method.
392       */
393      public void setOverridePut(final boolean overridePut) {
394        this.overrides[0] = overridePut;
395      }
396    }