001/* 002 * Copyright 2015-2019 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.ObjectNode; 022import com.unboundid.scim2.common.Path; 023import com.unboundid.scim2.common.ScimResource; 024import com.unboundid.scim2.common.exceptions.ScimException; 025import com.unboundid.scim2.common.messages.SortOrder; 026import com.unboundid.scim2.common.types.AttributeDefinition; 027import com.unboundid.scim2.common.utils.Debug; 028import com.unboundid.scim2.common.utils.JsonUtils; 029 030import java.util.Comparator; 031import java.util.Iterator; 032import java.util.List; 033 034/** 035 * A comparator implementation that could be used to compare POJOs representing 036 * SCIM resources using the SCIM sorting parameters. 037 */ 038public class ResourceComparator<T extends ScimResource> 039 implements Comparator<T> 040{ 041 private final Path sortBy; 042 private final SortOrder sortOrder; 043 private final ResourceTypeDefinition resourceType; 044 045 /** 046 * Create a new ScimComparator that will sort in ascending order. 047 * 048 * @param sortBy The path to the attribute to sort by. 049 * @param resourceType The resource type definition containing the schemas or 050 * {@code null} to compare using case insensitive matching 051 * for string values. 052 */ 053 public ResourceComparator(final Path sortBy, 054 final ResourceTypeDefinition resourceType) 055 { 056 this(sortBy, SortOrder.ASCENDING, resourceType); 057 } 058 059 /** 060 * Create a new ScimComparator. 061 * 062 * @param sortBy The path to the attribute to sort by. 063 * @param sortOrder The sort order. 064 * @param resourceType The resource type definition containing the schemas or 065 * {@code null} to compare using case insensitive matching 066 * for string values. 067 */ 068 public ResourceComparator(final Path sortBy, final SortOrder sortOrder, 069 final ResourceTypeDefinition resourceType) 070 { 071 this.sortBy = sortBy; 072 this.sortOrder = sortOrder == null ? SortOrder.ASCENDING : sortOrder; 073 this.resourceType = resourceType; 074 } 075 076 /** 077 * {@inheritDoc} 078 */ 079 public int compare(final T o1, final T o2) 080 { 081 ObjectNode n1 = o1.asGenericScimResource().getObjectNode(); 082 ObjectNode n2 = o2.asGenericScimResource().getObjectNode(); 083 084 JsonNode v1 = null; 085 JsonNode v2 = null; 086 087 try 088 { 089 List<JsonNode> v1s = JsonUtils.findMatchingPaths(sortBy, n1); 090 if(!v1s.isEmpty()) 091 { 092 // Always just use the primary or first value of the first found node. 093 v1 = getPrimaryOrFirst(v1s.get(0)); 094 } 095 } 096 catch (ScimException e) 097 { 098 Debug.debugException(e); 099 } 100 101 try 102 { 103 List<JsonNode> v2s = JsonUtils.findMatchingPaths(sortBy, n2); 104 if(!v2s.isEmpty()) 105 { 106 // Always just use the primary or first value of the first found node. 107 v2 = getPrimaryOrFirst(v2s.get(0)); 108 } 109 } 110 catch (ScimException e) 111 { 112 Debug.debugException(e); 113 } 114 115 if(v1 == null && v2 == null) 116 { 117 return 0; 118 } 119 // or all attribute types, if there is no data for the specified "sortBy" 120 // value they are sorted via the "sortOrder" parameter; i.e., they are 121 // ordered last if ascending and first if descending. 122 else if(v1 == null) 123 { 124 return sortOrder == SortOrder.ASCENDING ? 1 : -1; 125 } 126 else if(v2 == null) 127 { 128 return sortOrder == SortOrder.ASCENDING ? -1 : 1; 129 } 130 else 131 { 132 AttributeDefinition attributeDefinition = 133 resourceType == null ? null : 134 resourceType.getAttributeDefinition(sortBy); 135 return sortOrder == SortOrder.ASCENDING ? 136 JsonUtils.compareTo(v1, v2, attributeDefinition) : 137 JsonUtils.compareTo(v2, v1, attributeDefinition); 138 } 139 } 140 141 /** 142 * Retrieve the value of a complex multi-valued attribute that is marked as 143 * primary or the first value in the list. If the provided node is not an 144 * array node, then just return the provided node. 145 * 146 * @param node The JsonNode to retrieve from. 147 * @return The primary or first value or {@code null} if the provided array 148 * node is empty. 149 */ 150 private JsonNode getPrimaryOrFirst(final JsonNode node) 151 { 152 // if it's a multi-valued attribute (see Section 2.4 153 // [I-D.ietf - scim - core - schema]), if any, or else the first value in 154 // the list, if any. 155 156 if(!node.isArray()) 157 { 158 return node; 159 } 160 161 if(node.size() == 0) 162 { 163 return null; 164 } 165 166 Iterator<JsonNode> i = node.elements(); 167 while(i.hasNext()) 168 { 169 JsonNode value = i.next(); 170 JsonNode primary = value.get("primary"); 171 if(primary != null && primary.booleanValue()) 172 { 173 return value; 174 } 175 } 176 return node.get(0); 177 } 178 179}