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