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