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