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 null for
044   *                           other requests.
045   * @param queryAttributes    The attributes from the 'attributes' or
046   *                           'excludedAttributes' query parameter.
047   * @param excluded           {@code true} if the queryAttributes came from
048   *                           the excludedAttributes query parameter.
049   */
050  public ScimResourceTrimmer(final ResourceTypeDefinition resourceType,
051                             final Set<Path> requestAttributes,
052                             final Set<Path> queryAttributes,
053                             final boolean excluded)
054  {
055    this.resourceType      = resourceType;
056    this.requestAttributes = requestAttributes;
057    this.queryAttributes   = queryAttributes;
058    this.excluded          = excluded;
059  }
060
061
062
063  /**
064   * {@inheritDoc}
065   */
066  @Override
067  public boolean shouldReturn(final Path path)
068  {
069    AttributeDefinition attributeDefinition =
070        resourceType.getAttributeDefinition(path);
071    AttributeDefinition.Returned returned = attributeDefinition == null ?
072        AttributeDefinition.Returned.DEFAULT :
073        attributeDefinition.getReturned();
074
075    switch(returned)
076    {
077      case ALWAYS:
078        return true;
079      case NEVER:
080        return false;
081      case REQUEST:
082        // Return only if it was one of the request attributes or if there are
083        // no request attributes, then only if it was one of the override query
084        // attributes.
085        return pathContains(requestAttributes, path) ||
086               (requestAttributes.isEmpty() && !excluded &&
087                pathContains(queryAttributes, path));
088      default:
089        // Return if it is not one of the excluded query attributes and no
090        // override query attributes are provided. If override query attributes
091        // are provided, only return if it is one of them.
092        if(excluded)
093        {
094          return !pathContains(queryAttributes, path);
095        }
096        else
097        {
098          return queryAttributes.isEmpty() ||
099                 pathContains(queryAttributes, path);
100        }
101    }
102  }
103
104  private boolean pathContains(final Set<Path> paths, final Path path)
105  {
106    // Exact path match
107    if (paths.contains(path))
108    {
109      return true;
110    }
111
112    // See if a sub-attribute of the given path is included in the list
113    // ie. include name if name.givenName is in the list.
114    for (Path p : paths)
115    {
116      if(p.size() > path.size() && path.equals(p.subPath(path.size())))
117      {
118        return true;
119      }
120    }
121
122    // See if the parent attribute of the given path is included in the list
123    // ie. include name.{anything} if name is in the list.
124    for (Path p = path; p.size() > 0; p = p.subPath(p.size() - 1))
125    {
126      if (paths.contains(p))
127      {
128        return true;
129      }
130    }
131
132    return false;
133  }
134}