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 java.util.Collection;
021    import java.util.Collections;
022    import java.util.HashMap;
023    import java.util.LinkedHashMap;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import static com.unboundid.scim.sdk.StaticUtils.toLowerCase;
029    
030    
031    
032    /**
033     * This class represents a Simple Cloud Identity Management (SCIM) object.
034     * A SCIM object may be composed of common schema attributes and a collection
035     * of attributes from one or more additional schema definitions.
036     * This class is not designed to be thread-safe.
037     */
038    public class SCIMObject
039    {
040    
041      /**
042       * The set of attributes in this object grouped by the URI of the schema to
043       * which they belong.
044       */
045      private final HashMap<String,LinkedHashMap<String,SCIMAttribute>> attributes;
046    
047    
048    
049      /**
050       * Create an empty SCIM object that initially has no attributes. The type of
051       * resource is not specified.
052       */
053      public SCIMObject()
054      {
055        this.attributes =
056            new HashMap<String, LinkedHashMap<String, SCIMAttribute>>();
057      }
058    
059    
060      /**
061       * Create a new copy of the provided SCIM object.
062       *
063       * @param scimObject The SCIMObject to copy.
064       */
065      public SCIMObject(final SCIMObject scimObject)
066      {
067        // Since SCIMAttribute is immutable, just copy the maps.
068        this.attributes =
069            new HashMap<String, LinkedHashMap<String, SCIMAttribute>>();
070        for(Map.Entry<String, LinkedHashMap<String, SCIMAttribute>> entry :
071            scimObject.attributes.entrySet())
072        {
073          this.attributes.put(entry.getKey(),
074              new LinkedHashMap<String, SCIMAttribute>(entry.getValue()));
075        }
076      }
077    
078    
079    
080      /**
081       * Retrieves the set of schemas currently contributing attributes to this
082       * object.
083       *
084       * @return  An immutable collection of the URIs of schemas currently
085       *          contributing attributes to this object.
086       */
087      public Set<String> getSchemas()
088      {
089        return Collections.unmodifiableSet(attributes.keySet());
090      }
091    
092    
093    
094      /**
095       * Determines whether this object contains any attributes in the specified
096       * schema.
097       *
098       * @param schema  The URI of the schema for which to make the determination.
099       *                It must not be {@code null}.
100       *
101       * @return  {@code true} if this object contains any attributes in the
102       *          specified schema, or {@code false} if not.
103       */
104      public boolean hasSchema(final String schema)
105      {
106        return attributes.containsKey(toLowerCase(schema));
107      }
108    
109    
110    
111      /**
112       * Retrieves the attribute with the specified name.
113       *
114       * @param schema  The URI of the schema containing the attribute to retrieve.
115       *
116       * @param name    The name of the attribute to retrieve. It must not be
117       *                {@code null}.
118       *
119       * @return  The requested attribute from this object, or {@code null} if the
120       *          specified attribute is not present in this object.
121       */
122      public SCIMAttribute getAttribute(final String schema, final String name)
123      {
124        final LinkedHashMap<String,SCIMAttribute> attrs =
125            attributes.get(toLowerCase(schema));
126    
127        if (attrs == null)
128        {
129          return null;
130        }
131        else
132        {
133          return attrs.get(toLowerCase(name));
134        }
135      }
136    
137    
138    
139      /**
140       * Retrieves the set of attributes in this object from the specified schema.
141       *
142       * @param schema  The URI of the schema whose attributes are to be retrieved.
143       *
144       * @return  An immutable collection of the attributes in this object from the
145       *          specified schema, or the empty collection if there are no such
146       *          attributes.
147       */
148      public Collection<SCIMAttribute> getAttributes(final String schema)
149      {
150        final LinkedHashMap<String, SCIMAttribute> attrs =
151            attributes.get(toLowerCase(schema));
152    
153        if (attrs == null)
154        {
155          return Collections.emptyList();
156        }
157        else
158        {
159          return Collections.unmodifiableCollection(attrs.values());
160        }
161      }
162    
163    
164    
165      /**
166       * Determines whether this object contains the specified attribute.
167       *
168       * @param schema  The URI of the schema containing the attribute.
169       * @param name    The name of the attribute for which to make the
170       *                determination. It must not be {@code null}.
171       *
172       * @return  {@code true} if this object contains the specified attribute, or
173       *          {@code false} if not.
174       */
175      public boolean hasAttribute(final String schema, final String name)
176      {
177        final LinkedHashMap<String, SCIMAttribute> attrs =
178            attributes.get(toLowerCase(schema));
179    
180        if (attrs == null)
181        {
182          return false;
183        }
184        else
185        {
186          return attrs.containsKey(toLowerCase(name));
187        }
188      }
189    
190    
191    
192      /**
193       * Adds the provided attribute to this object. If this object already contains
194       * an attribute with the same name from the same schema, then the provided
195       * attribute will not be added.
196       *
197       * @param attribute  The attribute to be added. It must not be {@code null}.
198       *
199       * @return  {@code true} if the object was updated, or {@code false} if the
200       *          object already contained an attribute with the same name.
201       */
202      public boolean addAttribute(final SCIMAttribute attribute)
203      {
204        final String lowerCaseSchema = toLowerCase(attribute.getSchema());
205        final String lowerCaseName = toLowerCase(attribute.getName());
206    
207        LinkedHashMap<String,SCIMAttribute> attrs = attributes.get(lowerCaseSchema);
208        if (attrs == null)
209        {
210          attrs = new LinkedHashMap<String, SCIMAttribute>();
211          attrs.put(lowerCaseName, attribute);
212          attributes.put(lowerCaseSchema, attrs);
213          return true;
214        }
215        else
216        {
217          if (attrs.containsKey(lowerCaseName))
218          {
219            return false;
220          }
221          else
222          {
223            attrs.put(lowerCaseName, attribute);
224            return true;
225          }
226        }
227      }
228    
229    
230    
231      /**
232       * Adds the provided attribute to this object, replacing any existing
233       * attribute with the same name.
234       *
235       * @param attribute  The attribute to be added. It must not be {@code null}.
236       */
237      public void setAttribute(final SCIMAttribute attribute)
238      {
239        final String lowerCaseSchema = toLowerCase(attribute.getSchema());
240        final String lowerCaseName = toLowerCase(attribute.getName());
241    
242        LinkedHashMap<String,SCIMAttribute> attrs = attributes.get(lowerCaseSchema);
243        if (attrs == null)
244        {
245          attrs = new LinkedHashMap<String, SCIMAttribute>();
246          attrs.put(lowerCaseName, attribute);
247          attributes.put(lowerCaseSchema, attrs);
248        }
249        else
250        {
251          attrs.put(lowerCaseName, attribute);
252        }
253      }
254    
255    
256    
257      /**
258       * Removes the specified attribute from this object.
259       *
260       * @param schema  The URI of the schema to which the attribute belongs.
261       * @param name    The name of the attribute to remove. It must not be
262       *                {@code null}.
263       *
264       * @return  {@code true} if the attribute was removed from the object, or
265       *          {@code false} if it was not present.
266       */
267      public boolean removeAttribute(final String schema, final String name)
268      {
269        final String lowerCaseSchema = toLowerCase(schema);
270        LinkedHashMap<String,SCIMAttribute> attrs = attributes.get(lowerCaseSchema);
271        if (attrs == null)
272        {
273          return false;
274        }
275        else
276        {
277          final boolean removed = attrs.remove(toLowerCase(name)) != null;
278          if (removed && attrs.isEmpty())
279          {
280            attributes.remove(lowerCaseSchema);
281          }
282          return removed;
283        }
284      }
285    
286    
287    
288      /**
289       * Determine whether this object matches the provided filter parameters.
290       *
291       * @param filter  The filter parameters to compare against the object.
292       *
293       * @return  {@code true} if this object matches the provided filter, and
294       *          {@code false} otherwise.
295       */
296      public boolean matchesFilter(final SCIMFilter filter)
297      {
298        final SCIMFilterType type = filter.getFilterType();
299        final List<SCIMFilter> components = filter.getFilterComponents();
300    
301        switch(type)
302        {
303          case AND:
304            for(SCIMFilter component : components)
305            {
306              if(!matchesFilter(component))
307              {
308                return false;
309              }
310            }
311            return true;
312          case OR:
313            for(SCIMFilter component : components)
314            {
315              if(matchesFilter(component))
316              {
317                return true;
318              }
319            }
320            return false;
321        }
322    
323        final String schema = filter.getFilterAttribute().getAttributeSchema();
324        final String attributeName = filter.getFilterAttribute().getAttributeName();
325    
326        final SCIMAttribute attribute = getAttribute(schema, attributeName);
327        if (attribute == null)
328        {
329          return false;
330        }
331    
332        return attribute.matchesFilter(filter);
333      }
334    
335    
336      /**
337       * {@inheritDoc}
338       */
339      @Override
340      public boolean equals(final Object o) {
341        if (this == o) {
342          return true;
343        }
344        if (o == null || getClass() != o.getClass()) {
345          return false;
346        }
347    
348        SCIMObject that = (SCIMObject) o;
349    
350        if (!attributes.equals(that.attributes)) {
351          return false;
352        }
353    
354        return true;
355      }
356    
357    
358      /**
359       * {@inheritDoc}
360       */
361      @Override
362      public int hashCode() {
363        return attributes.hashCode();
364      }
365    
366    
367      /**
368       * {@inheritDoc}
369       */
370      @Override
371      public String toString() {
372        return "SCIMObject{" +
373          "attributes=" + attributes +
374          '}';
375      }
376    }