001/*
002 * Copyright 2015-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.scim2.server.utils;
019
020import com.unboundid.scim2.common.Path;
021import com.unboundid.scim2.common.types.AttributeDefinition;
022
023import java.util.Set;
024
025
026
027/**
028 * A resource trimmer implementing the SCIM standard for returning attributes.
029 */
030public class ScimResourceTrimmer extends ResourceTrimmer
031{
032  private final ResourceTypeDefinition resourceType;
033  private final Set<Path> requestAttributes;
034  private final Set<Path> queryAttributes;
035  private final boolean excluded;
036
037
038
039  /**
040   * Create a new SCIMResourceTrimmer.
041   * @param resourceType       The resource type definition for resources to
042   *                           trim.
043   * @param requestAttributes  The attributes in the request object or
044   *                           {@code null} for
045   *                           other requests.
046   * @param queryAttributes    The attributes from the 'attributes' or
047   *                           'excludedAttributes' query parameter.
048   * @param excluded           {@code true} if the queryAttributes came from
049   *                           the excludedAttributes query parameter.
050   */
051  public ScimResourceTrimmer(final ResourceTypeDefinition resourceType,
052                             final Set<Path> requestAttributes,
053                             final Set<Path> queryAttributes,
054                             final boolean excluded)
055  {
056    this.resourceType      = resourceType;
057    this.requestAttributes = requestAttributes;
058    this.queryAttributes   = queryAttributes;
059    this.excluded          = excluded;
060  }
061
062
063
064  /**
065   * {@inheritDoc}
066   */
067  @Override
068  public boolean shouldReturn(final Path path)
069  {
070    AttributeDefinition attributeDefinition =
071        resourceType.getAttributeDefinition(path);
072    AttributeDefinition.Returned returned = attributeDefinition == null ?
073        AttributeDefinition.Returned.DEFAULT :
074        attributeDefinition.getReturned();
075
076    switch(returned)
077    {
078      case ALWAYS:
079        return true;
080      case NEVER:
081        return false;
082      case REQUEST:
083        // Return only if it was one of the request attributes or if there are
084        // no request attributes, then only if it was one of the override query
085        // attributes.
086        return pathContains(requestAttributes, path) ||
087               (requestAttributes.isEmpty() && !excluded &&
088                pathContains(queryAttributes, path));
089      default:
090        // Return if it is not one of the excluded query attributes and no
091        // override query attributes are provided. If override query attributes
092        // are provided, only return if it is one of them.
093        if(excluded)
094        {
095          return !pathContains(queryAttributes, path);
096        }
097        else
098        {
099          return queryAttributes.isEmpty() ||
100                 pathContains(queryAttributes, path);
101        }
102    }
103  }
104
105  private boolean pathContains(final Set<Path> paths, final Path path)
106  {
107    // Exact path match
108    if (paths.contains(path))
109    {
110      return true;
111    }
112
113    // See if a sub-attribute of the given path is included in the list
114    // ie. include name if name.givenName is in the list.
115    for (Path p : paths)
116    {
117      if(p.size() > path.size() && path.equals(p.subPath(path.size())))
118      {
119        return true;
120      }
121    }
122
123    // See if the parent attribute of the given path is included in the list
124    // ie. include name.{anything} if name is in the list.
125    for (Path p = path; p.size() > 0; p = p.subPath(p.size() - 1))
126    {
127      if (paths.contains(p))
128      {
129        return true;
130      }
131    }
132
133    return false;
134  }
135}