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.schema; 019 020import com.unboundid.scim.data.AttributeValueResolver; 021import com.unboundid.scim.data.BaseResource; 022import com.unboundid.scim.data.ResourceFactory; 023import com.unboundid.scim.sdk.InvalidResourceException; 024import com.unboundid.scim.sdk.SCIMConstants; 025import com.unboundid.scim.sdk.SCIMObject; 026 027import java.util.Collection; 028import java.util.HashMap; 029import java.util.Map; 030import java.util.Set; 031 032import static com.unboundid.scim.sdk.StaticUtils.toLowerCase; 033 034 035 036/** 037 * This class provides methods that describe the schema for a SCIM resource. It 038 * may be used to help read and write SCIM objects in their external XML and 039 * JSON representation, and to convert SCIM objects to and from LDAP entries. 040 */ 041public class ResourceDescriptor extends BaseResource 042{ 043 /** 044 * A <code>ResourceFactory</code> for creating <code>ResourceDescriptor</code> 045 * instances. 046 */ 047 public static final ResourceFactory<ResourceDescriptor> 048 RESOURCE_DESCRIPTOR_FACTORY = new ResourceFactory<ResourceDescriptor>() { 049 /** 050 * {@inheritDoc} 051 */ 052 public ResourceDescriptor createResource( 053 final ResourceDescriptor resourceDescriptor, 054 final SCIMObject scimObject) { 055 ResourceDescriptor rd = 056 new ResourceDescriptor(resourceDescriptor, scimObject); 057 058 if (scimObject.getSchemas().contains( 059 "urn:unboundid:schemas:scim:ldap:1.0")) 060 { 061 //This is a convenience for when we're talking to the UnboundID 062 //Directory REST API; clients could set this themselves, but we'll do 063 //it for them in this case. 064 rd.setStrictMode(false); 065 } 066 return rd; 067 } 068 }; 069 070 /** 071 * A schema -> name -> AttributeDescriptor map to quickly look up 072 * attributes. The attribute descriptors are keyed by the lower case 073 * attribute name because attribute names are case-insensitive. Likewise, 074 * the schema key is lower case because schema URNs are case-insensitive. 075 */ 076 private Map<String, Map<String, AttributeDescriptor>> attributesCache; 077 078 /** 079 * Whether to use "strict mode" when looking up an attribute 080 * that doesn't exist in the attributesCache. 081 */ 082 private boolean strictMode = true; 083 084 /** 085 * Constructs a new ResourceDescriptor from a existing SCIMObject. 086 * 087 * @param resourceDescriptor The Resource Schema descriptor. 088 * @param scimObject The SCIMObject containing the schema. 089 */ 090 ResourceDescriptor(final ResourceDescriptor resourceDescriptor, 091 final SCIMObject scimObject) { 092 super(resourceDescriptor, scimObject); 093 } 094 095 /** 096 * Constructs a new empty ResourceDescriptor. 097 * 098 * @param resourceDescriptor The Resource Schema descriptor. 099 */ 100 private ResourceDescriptor(final ResourceDescriptor resourceDescriptor) { 101 super(resourceDescriptor); 102 } 103 104 /** 105 * Retrieves the attribute descriptor for a specified attribute. 106 * 107 * @param schema The attribute descriptor's associated schema URN. 108 * @param name The name of the attribute whose descriptor is to be retrieved. 109 * 110 * @return The attribute descriptor for the specified attribute. 111 * @throws InvalidResourceException if there is no such attribute. 112 */ 113 public AttributeDescriptor getAttribute(final String schema, 114 final String name) 115 throws InvalidResourceException 116 { 117 initAttributesCache(); 118 AttributeDescriptor attributeDescriptor = null; 119 Map<String, AttributeDescriptor> map = 120 attributesCache.get(toLowerCase(schema)); 121 if(map != null) 122 { 123 attributeDescriptor = map.get(toLowerCase(name)); 124 } 125 if(attributeDescriptor == null) 126 { 127 if (strictMode || SCIMConstants.SCHEMA_URI_CORE.equalsIgnoreCase(schema)) 128 { 129 throw new InvalidResourceException("Attribute " + schema + 130 SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE + name + 131 " is not defined for resource " + getName()); 132 } 133 else 134 { 135 attributeDescriptor = AttributeDescriptor.createMultiValuedAttribute( 136 name, "value", AttributeDescriptor.DataType.STRING, null, 137 schema, false, false, false, null); 138 } 139 } 140 return attributeDescriptor; 141 } 142 143 /** 144 * Retrieves all the attribute descriptors of the provided schema defined 145 * in the resource. 146 * 147 * @param schema The name of the schema. 148 * @return All the attribute descriptors of the provided schema defined 149 * for this resource. 150 */ 151 public Collection<AttributeDescriptor> getAttributes(final String schema) 152 { 153 initAttributesCache(); 154 Map<String, AttributeDescriptor> map = 155 attributesCache.get(toLowerCase(schema)); 156 if(map != null) 157 { 158 return map.values(); 159 } 160 return null; 161 } 162 163 /** 164 * Retrieves the set of unique schemas for the attribute descriptors defined 165 * in the resource. 166 * 167 * @return The set of unique schemas for the attribute descriptors defined 168 * in the resource. 169 */ 170 public Set<String> getAttributeSchemas() 171 { 172 initAttributesCache(); 173 return attributesCache.keySet(); 174 } 175 176 /** 177 * Retrieve the name of the resource to be used in any external representation 178 * of the resource. 179 * 180 * @return Retrieve the name of the resource to be used in any external 181 * representation of the resource. It is never {@code null}. 182 */ 183 public String getName() 184 { 185 return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "name", 186 AttributeValueResolver.STRING_RESOLVER); 187 } 188 189 /** 190 * Sets the "strict mode" for this ResourceDescriptor. If strict mode is off, 191 * then a call to {@link #getAttribute(String, String)} where the requested 192 * attribute does not exist in the attributesCache will result in the method 193 * generating an AttributeDescriptor on the fly. If strict mode were on in 194 * this case, it would throw an exception because that attribute was not 195 * defined. 196 * 197 * @param strictMode a boolean indicating whether to use strict mode or not. 198 */ 199 public void setStrictMode(final boolean strictMode) 200 { 201 this.strictMode = strictMode; 202 } 203 204 /** 205 * Gets the "strict mode" setting for this ResourceDescriptor. If strict mode 206 * is off, then a call to {@link #getAttribute(String, String)} where the 207 * requested attribute does not exist in the attributesCache will result in 208 * the method generating an AttributeDescriptor on the fly. If strict mode 209 * were on in this case, it would throw an exception because that attribute 210 * was not defined. 211 * 212 * @return boolean indicating whether strict mode is enabled. 213 */ 214 public boolean isStrictMode() 215 { 216 return this.strictMode; 217 } 218 219 /** 220 * Sets the name of the resource to be used in any external representation 221 * of the resource. 222 * 223 * @param name The name of the resource to be used in any external 224 * representation of the resource. 225 * @return this ResourceDescriptor. 226 */ 227 private ResourceDescriptor setName(final String name) 228 { 229 try { 230 setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, 231 "name", AttributeValueResolver.STRING_RESOLVER, name); 232 } catch (InvalidResourceException e) { 233 // This should never happen as these are core attributes... 234 throw new RuntimeException(e); 235 } 236 return this; 237 } 238 239 /** 240 * Retrieves the list of all attribute descriptors defined in the resource. 241 * 242 * @return The list of attribute descriptors for the resource. It is never 243 * {@code null}. 244 */ 245 public Collection<AttributeDescriptor> getAttributes() 246 { 247 return getAttributeValues(SCIMConstants.SCHEMA_URI_CORE, 248 "attributes", AttributeDescriptor.ATTRIBUTE_DESCRIPTOR_RESOLVER); 249 } 250 251 /** 252 * Sets the list of attribute descriptors for the resource. 253 * 254 * @param attributes The list of attribute descriptors for the resource. 255 * @return this ResourceDescriptor. 256 */ 257 private ResourceDescriptor setAttributes( 258 final Collection<AttributeDescriptor> attributes) 259 { 260 try { 261 setAttributeValues(SCIMConstants.SCHEMA_URI_CORE, 262 "attributes", AttributeDescriptor.ATTRIBUTE_DESCRIPTOR_RESOLVER, 263 attributes); 264 } catch (InvalidResourceException e) { 265 // This should never happen as these are core attributes... 266 throw new RuntimeException(e); 267 } 268 return this; 269 } 270 271 /** 272 * Returns the resource's XML schema (namespace) name. 273 * 274 * @return The XML namespace name. 275 */ 276 public String getSchema() 277 { 278 return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "schema", 279 AttributeValueResolver.STRING_RESOLVER); 280 } 281 282 /** 283 * Sets the resource's XML schema (namespace) name. 284 * 285 * @param schema The XML namespace name. 286 * @return this ResourceDescriptor. 287 */ 288 private ResourceDescriptor setSchema(final String schema) 289 { 290 try { 291 setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, 292 "schema", AttributeValueResolver.STRING_RESOLVER, schema); 293 } catch (InvalidResourceException e) { 294 // This should never happen as these are core attributes... 295 throw new RuntimeException(e); 296 } 297 return this; 298 } 299 300 /** 301 * Retrieves the resource's human readable description. 302 * 303 * @return The resource's human readable description. 304 */ 305 public String getDescription() 306 { 307 return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, 308 "description", AttributeValueResolver.STRING_RESOLVER); 309 } 310 311 /** 312 * Sets the resource's human readable description. 313 * 314 * @param description The resource's human readable description. 315 * @return this ResourceDescriptor. 316 */ 317 private ResourceDescriptor setDescription(final String description) 318 { 319 try { 320 setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, 321 "description", AttributeValueResolver.STRING_RESOLVER, description); 322 } catch (InvalidResourceException e) { 323 // This should never happen as these are core attributes... 324 throw new RuntimeException(e); 325 } 326 return this; 327 } 328 329 /** 330 * Retrieves the Resource's HTTP addressable endpoint relative to the 331 * Base URL. 332 * 333 * @return The Resource's HTTP addressable endpoint relative to the Base URL. 334 */ 335 public String getEndpoint() 336 { 337 return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, 338 "endpoint", AttributeValueResolver.STRING_RESOLVER); 339 } 340 341 /** 342 * Sets the Resource's HTTP addressable endpoint relative to the 343 * Base URL. 344 * 345 * @param endpoint The Resource's HTTP addressable endpoint relative to 346 * the Base URL. 347 * @return this ResourceDescriptor. 348 */ 349 private ResourceDescriptor setEndpoint(final String endpoint) 350 { 351 try { 352 setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, 353 "endpoint", AttributeValueResolver.STRING_RESOLVER, endpoint); 354 } catch (InvalidResourceException e) { 355 // This should never happen as these are core attributes... 356 throw new RuntimeException(e); 357 } 358 return this; 359 } 360 361 /** 362 * Initializes the attributesCache if needed. 363 */ 364 private void initAttributesCache() 365 { 366 synchronized(this) 367 { 368 if(attributesCache == null) 369 { 370 attributesCache = new HashMap<String, 371 Map<String, AttributeDescriptor>>(); 372 for(AttributeDescriptor attributeDescriptor : getAttributes()) 373 { 374 final String lowerCaseSchema = 375 toLowerCase(attributeDescriptor.getSchema()); 376 Map<String, AttributeDescriptor> map = 377 attributesCache.get(lowerCaseSchema); 378 if(map == null) 379 { 380 map = new HashMap<String, AttributeDescriptor>(); 381 attributesCache.put(lowerCaseSchema, map); 382 } 383 map.put(toLowerCase(attributeDescriptor.getName()), 384 attributeDescriptor); 385 } 386 } 387 } 388 } 389 390 /** 391 * {@inheritDoc} 392 */ 393 @Override 394 public int hashCode() 395 { 396 int hashCode = 31; 397 hashCode += hashCode * toLowerCase(getSchema()).hashCode(); 398 hashCode += hashCode * toLowerCase(getName()).hashCode(); 399 return hashCode; 400 } 401 402 /** 403 * {@inheritDoc} 404 */ 405 @Override 406 public boolean equals(final Object obj) 407 { 408 if (this == obj) 409 { 410 return true; 411 } 412 413 if (!(obj instanceof ResourceDescriptor)) 414 { 415 return false; 416 } 417 418 final ResourceDescriptor that = (ResourceDescriptor)obj; 419 final String thisSchema = getSchema(); 420 final String thisName = getName(); 421 final String thatSchema = that.getSchema(); 422 final String thatName = that.getName(); 423 if (thisSchema == null && thatSchema == null) 424 { 425 return thisName.equalsIgnoreCase(thatName); 426 } 427 else 428 { 429 return thisSchema != null && thatSchema != null && 430 thisSchema.equalsIgnoreCase(thatSchema) && 431 thisName.equalsIgnoreCase(thatName); 432 } 433 } 434 435 @Override 436 public String toString() 437 { 438 return "ResourceDescriptor{" + 439 "name='" + getName() + '\'' + 440 ", description='" + getDescription() + 441 ", schema='" + getSchema() + '\'' + 442 ", endpoint='" + getEndpoint() + '\'' + 443 ", attributes=" + getAttributes() + 444 '}'; 445 } 446 447 /** 448 * Construct a new resource descriptor with the provided information. 449 * The resource attributes specified here should not include common core 450 * attributes (ie. id, externalId, meta) as these will be added automatically. 451 * 452 * @param name The addressable Resource endpoint name. 453 * @param description The Resource's human readable description. 454 * @param schema The Resource's associated schema URN 455 * @param endpoint The Resource's HTTP addressable endpoint relative 456 * to the Base URL. 457 * @param attributes Specifies the set of associated Resource attributes. 458 * @return The newly constructed resource descriptor. 459 */ 460 public static ResourceDescriptor create( 461 final String name, final String description, final String schema, 462 final String endpoint, final AttributeDescriptor... attributes) 463 { 464 ResourceDescriptor resourceDescriptor = 465 new ResourceDescriptor(CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR); 466 resourceDescriptor.setName(name); 467 resourceDescriptor.setDescription(description); 468 resourceDescriptor.setSchema(schema); 469 resourceDescriptor.setEndpoint(endpoint); 470 resourceDescriptor.setAttributes( 471 CoreSchema.addCommonResourceAttributes(attributes)); 472 473 return resourceDescriptor; 474 } 475}