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.schema.AttributeDescriptor;
021import com.unboundid.scim.schema.ResourceDescriptor;
022
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029
030
031
032/**
033 * This class represents a list of query attributes taken from the attributes
034 * query parameter. e.g. attributes=name.formatted,userName
035 */
036public class SCIMQueryAttributes
037{
038  /**
039   * Indicates whether all attributes and sub-attributes are requested.
040   */
041  private final boolean allAttributesRequested;
042
043  /**
044   * The set of attributes and sub-attributes explicitly requested.
045   */
046  private final Map<AttributeDescriptor,Set<AttributeDescriptor>> descriptors;
047
048
049
050  /**
051   * Create a new instance of query attributes from their string representation.
052   *
053   * @param resourceDescriptor  The resource descriptor for the SCIM endpoint.
054   * @param attributes     The attributes query parameter specifying the set of
055   *                       attributes or sub-attributes requested, or null if
056   *                       all attributes and sub-attributes are requested. The
057   *                       attributes must be qualified by their
058   *                       schema URI if they are not in the core schema.
059   *
060   * @throws InvalidResourceException  If one of the specified attributes does
061   *                                   not exist.
062   */
063  public SCIMQueryAttributes(final ResourceDescriptor resourceDescriptor,
064                             final String attributes)
065      throws InvalidResourceException
066  {
067    descriptors =
068        new HashMap<AttributeDescriptor, Set<AttributeDescriptor>>();
069
070    if (attributes == null)
071    {
072      allAttributesRequested = true;
073    }
074    else
075    {
076      allAttributesRequested = false;
077      if (!attributes.isEmpty())
078      {
079        final String[] paths = attributes.split(",");
080        if (paths.length > 0)
081        {
082          for (final String a : paths)
083          {
084            final AttributePath path;
085            if (resourceDescriptor.getSchema().equalsIgnoreCase(
086                    "urn:unboundid:schemas:scim:ldap:1.0"))
087            {
088              path = AttributePath.parse(a, resourceDescriptor.getSchema());
089            }
090            else
091            {
092              path = AttributePath.parse(a);
093            }
094
095            final AttributeDescriptor attributeDescriptor =
096                resourceDescriptor.getAttribute(path.getAttributeSchema(),
097                                                path.getAttributeName());
098
099            Set<AttributeDescriptor> subAttributes =
100                descriptors.get(attributeDescriptor);
101            if (subAttributes == null)
102            {
103              subAttributes = new HashSet<AttributeDescriptor>();
104              if (path.getSubAttributeName() != null)
105              {
106                subAttributes.add(
107                    attributeDescriptor.getSubAttribute(
108                        path.getSubAttributeName()));
109              }
110              descriptors.put(attributeDescriptor, subAttributes);
111            }
112            else
113            {
114              if (!subAttributes.isEmpty())
115              {
116                if (path.getSubAttributeName() != null)
117                {
118                  subAttributes.add(
119                      attributeDescriptor.getSubAttribute(
120                          path.getSubAttributeName()));
121                }
122                else
123                {
124                  subAttributes.clear();
125                }
126              }
127            }
128          }
129        }
130      }
131
132      final AttributeDescriptor id =
133          resourceDescriptor.getAttribute(SCIMConstants.SCHEMA_URI_CORE, "id");
134      if (!descriptors.containsKey(id))
135      {
136        descriptors.put(id, new HashSet<AttributeDescriptor>());
137      }
138
139      final AttributeDescriptor meta =
140          resourceDescriptor.getAttribute(SCIMConstants.SCHEMA_URI_CORE,
141                                          "meta");
142      if (!descriptors.containsKey(meta))
143      {
144        descriptors.put(meta, new HashSet<AttributeDescriptor>());
145      }
146    }
147  }
148
149
150
151  /**
152   * Create a new set of query attributes from the provided information.
153   *
154   * @param descriptors         The set of attributes and sub-attributes
155   *                            explicitly requested, or {@code null} if all
156   *                            attributes are requested.
157   */
158  public SCIMQueryAttributes(
159      final Map<AttributeDescriptor,Set<AttributeDescriptor>> descriptors)
160  {
161    this.allAttributesRequested = (descriptors == null);
162    this.descriptors = descriptors;
163  }
164
165
166
167  /**
168   * Determine whether all attributes and sub-attributes are requested by
169   * these query attributes.
170   *
171   * @return  {@code true} if all attributes and sub-attributes are requested,
172   *          and {@code false} otherwise.
173   */
174  public boolean allAttributesRequested()
175  {
176    return allAttributesRequested;
177  }
178
179
180
181  /**
182   * Determine whether the specified attribute is requested by these query
183   * attributes.
184   *
185   * @param attributeDescriptor  The attribute for which to make the
186   *                             determination.
187   *
188   * @return  {@code true} if the specified attribute is requested, or false
189   *          otherwise.
190   */
191  public boolean isAttributeRequested(
192      final AttributeDescriptor attributeDescriptor)
193  {
194    return allAttributesRequested() ||
195           descriptors.containsKey(attributeDescriptor);
196  }
197
198
199
200  /**
201   * Returns the map of requested attributes and sub-attributes.
202   *
203   * @return an unmodifiable map of the requested attributes.
204   */
205  public Map<AttributeDescriptor, Set<AttributeDescriptor>> getDescriptors()
206  {
207    return Collections.unmodifiableMap(descriptors);
208  }
209
210
211
212  /**
213   * Pare down a SCIM object to its requested attributes.
214   *
215   * @param scimObject  The SCIM object to be pared down.
216   *
217   * @return  The pared down SCIM object.
218   */
219  public SCIMObject pareObject(final SCIMObject scimObject)
220  {
221    if (allAttributesRequested())
222    {
223      return scimObject;
224    }
225
226    final SCIMObject paredObject = new SCIMObject();
227    for (final Map.Entry<AttributeDescriptor,Set<AttributeDescriptor>> entry :
228        descriptors.entrySet())
229    {
230      final AttributeDescriptor attributeDescriptor = entry.getKey();
231
232      final SCIMAttribute a =
233          scimObject.getAttribute(attributeDescriptor.getSchema(),
234                                  attributeDescriptor.getName());
235      if (a != null)
236      {
237        final SCIMAttribute paredAttribute = pareAttribute(a);
238        if (paredAttribute != null)
239        {
240          paredObject.addAttribute(paredAttribute);
241        }
242      }
243    }
244
245    return paredObject;
246  }
247
248
249
250  /**
251   * Pare down an attribute to its requested sub-attributes.
252   *
253   * @param attribute  The attribute to be pared down.
254   *
255   * @return  The pared down attribute, or {@code null} if the attribute
256   *          should not be included at all.
257   */
258  public SCIMAttribute pareAttribute(final SCIMAttribute attribute)
259  {
260    final AttributeDescriptor descriptor = attribute.getAttributeDescriptor();
261
262    if (allAttributesRequested() || descriptor.getSubAttributes() == null)
263    {
264      return attribute;
265    }
266
267    final Set<AttributeDescriptor> subDescriptors = descriptors.get(descriptor);
268    if (subDescriptors == null)
269    {
270      return null;
271    }
272
273    if (subDescriptors.isEmpty())
274    {
275      return attribute;
276    }
277
278    if (attribute.getAttributeDescriptor().isMultiValued())
279    {
280      final ArrayList<SCIMAttributeValue> values =
281          new ArrayList<SCIMAttributeValue>();
282
283      for (final SCIMAttributeValue v : attribute.getValues())
284      {
285        final ArrayList<SCIMAttribute> subAttributes =
286            new ArrayList<SCIMAttribute>();
287        for (final AttributeDescriptor d : subDescriptors)
288        {
289          final SCIMAttribute subAttribute = v.getAttribute(d.getName());
290          if (subAttribute != null)
291          {
292            subAttributes.add(subAttribute);
293          }
294        }
295        values.add(SCIMAttributeValue.createComplexValue(subAttributes));
296      }
297
298      return SCIMAttribute.create(
299          descriptor, values.toArray(new SCIMAttributeValue[values.size()]));
300    }
301    else
302    {
303      final ArrayList<SCIMAttribute> subAttributes =
304          new ArrayList<SCIMAttribute>();
305      for (final AttributeDescriptor d : subDescriptors)
306      {
307        final SCIMAttribute subAttribute =
308            attribute.getValue().getAttribute(d.getName());
309        if (subAttribute != null)
310        {
311          subAttributes.add(subAttribute);
312        }
313      }
314      return SCIMAttribute.create(descriptor,
315          SCIMAttributeValue.createComplexValue(subAttributes));
316    }
317  }
318
319
320
321  /**
322   * Return query attributes formed by merging these query attributes with the
323   * provided query attributes.
324   *
325   * @param that  The query attributes to be merged with these query attributes
326   *              to form new query attributes.
327   *
328   * @return  The merged query attributes.
329   *
330   * @throws InvalidResourceException  If the query attributes could not be
331   *                                   merged.
332   */
333  public SCIMQueryAttributes merge(final SCIMQueryAttributes that)
334      throws InvalidResourceException
335  {
336    if (this.allAttributesRequested || that.allAttributesRequested)
337    {
338      return new SCIMQueryAttributes(null);
339    }
340
341    final Map<AttributeDescriptor,Set<AttributeDescriptor>> merged =
342        new HashMap<AttributeDescriptor, Set<AttributeDescriptor>>(
343            this.descriptors);
344
345    for (final Map.Entry<AttributeDescriptor,Set<AttributeDescriptor>> e :
346        that.descriptors.entrySet())
347    {
348      final AttributeDescriptor attributeDescriptor = e.getKey();
349      final Set<AttributeDescriptor> thatSet = e.getValue();
350
351      Set<AttributeDescriptor> thisSet = merged.get(attributeDescriptor);
352      if (thisSet == null)
353      {
354        merged.put(attributeDescriptor, thatSet);
355      }
356      else
357      {
358        if (!thisSet.isEmpty())
359        {
360          if (thatSet.isEmpty())
361          {
362            thisSet.clear();
363          }
364          else
365          {
366            thisSet.addAll(thatSet);
367          }
368        }
369      }
370    }
371
372    return new SCIMQueryAttributes(merged);
373  }
374
375
376
377  /**
378   * {@inheritDoc}
379   */
380  @Override
381  public String toString()
382  {
383    final StringBuilder sb = new StringBuilder();
384    sb.append("SCIMQueryAttributes");
385    sb.append("{allAttributesRequested=").append(allAttributesRequested);
386    sb.append(", descriptors=").append(descriptors);
387    sb.append('}');
388    return sb.toString();
389  }
390}