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.fasterxml.jackson.databind.JsonNode;
021import com.fasterxml.jackson.databind.node.ArrayNode;
022import com.fasterxml.jackson.databind.node.ObjectNode;
023import com.unboundid.scim2.common.Path;
024import com.unboundid.scim2.common.utils.JsonUtils;
025import com.unboundid.scim2.common.utils.SchemaUtils;
026
027import java.util.Iterator;
028import java.util.Map;
029
030
031
032/**
033 * An abstract class which may be implemented to trim resources down to
034 * selected attributes.
035 */
036public abstract class ResourceTrimmer
037{
038  /**
039   * Trim attributes of the object node to return.
040   *
041   * @param objectNode The object node to return.
042   * @return The trimmed object node ready to return to the client.
043   */
044  public ObjectNode trimObjectNode(final ObjectNode objectNode)
045  {
046    return trimObjectNode(objectNode, Path.root());
047  }
048
049  /**
050   * Trim attributes of an inner object node to return.
051   *
052   * @param objectNode The object node to return.
053   * @param parentPath  The parent path of attributes in the object.
054   * @return The trimmed object node ready to return to the client.
055   */
056  private ObjectNode trimObjectNode(final ObjectNode objectNode,
057                                    final Path parentPath)
058  {
059    ObjectNode objectToReturn = JsonUtils.getJsonNodeFactory().objectNode();
060    Iterator<Map.Entry<String, JsonNode>> i = objectNode.fields();
061    while(i.hasNext())
062    {
063      Map.Entry<String, JsonNode> field = i.next();
064      final Path path;
065      if (parentPath.isRoot() && parentPath.getSchemaUrn() == null &&
066          SchemaUtils.isUrn(field.getKey()))
067      {
068        path = Path.root(field.getKey());
069      }
070      else
071      {
072        path = parentPath.attribute(field.getKey());
073      }
074
075      if(path.isRoot() || shouldReturn(path))
076      {
077        if (field.getValue().isArray())
078        {
079          ArrayNode trimmedNode = trimArrayNode(
080              (ArrayNode) field.getValue(), path);
081          if(trimmedNode.size() > 0)
082          {
083            objectToReturn.set(field.getKey(), trimmedNode);
084          }
085        }
086        else if (field.getValue().isObject())
087        {
088          ObjectNode trimmedNode = trimObjectNode(
089              (ObjectNode) field.getValue(), path);
090          if(trimmedNode.size() > 0)
091          {
092            objectToReturn.set(field.getKey(), trimmedNode);
093          }
094        }
095        else
096        {
097          objectToReturn.set(field.getKey(), field.getValue());
098        }
099      }
100    }
101    return objectToReturn;
102  }
103
104  /**
105   * Trim attributes of the values in the array node to return.
106   *
107   * @param arrayNode The array node to return.
108   * @param parentPath  The parent path of attributes in the array.
109   * @return The trimmed object node ready to return to the client.
110   */
111  protected ArrayNode trimArrayNode(final ArrayNode arrayNode,
112                                    final Path parentPath)
113  {
114    ArrayNode arrayToReturn = JsonUtils.getJsonNodeFactory().arrayNode();
115    for(JsonNode value : arrayNode)
116    {
117      if(value.isArray())
118      {
119        ArrayNode trimmedNode = trimArrayNode((ArrayNode) value, parentPath);
120        if(trimmedNode.size() > 0)
121        {
122          arrayToReturn.add(trimmedNode);
123        }
124      }
125      else if(value.isObject())
126      {
127        ObjectNode trimmedNode = trimObjectNode(
128            (ObjectNode) value, parentPath);
129        if(trimmedNode.size() > 0)
130        {
131          arrayToReturn.add(trimmedNode);
132        }
133      }
134      else
135      {
136        arrayToReturn.add(value);
137      }
138    }
139    return arrayToReturn;
140  }
141
142  /**
143   * Determine if the attribute specified by the path should be returned.
144   *
145   * @param path The path for the attribute.
146   * @return {@code true} to return the attribute or {@code false} to remove the
147   * attribute from the returned resource..
148   */
149  public abstract boolean shouldReturn(final Path path);
150}