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