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}