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}