001/* 002 * Copyright 2011-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.scim.sdk; 019 020import com.unboundid.scim.schema.AttributeDescriptor; 021import com.unboundid.scim.schema.ResourceDescriptor; 022 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032 033 034/** 035 * This class represents a list of query attributes taken from the attributes 036 * query parameter. e.g. attributes=name.formatted,userName 037 */ 038public class SCIMQueryAttributes 039{ 040 /** 041 * Indicates whether all attributes and sub-attributes are requested. 042 */ 043 private final boolean allAttributesRequested; 044 045 /** 046 * The set of attributes and sub-attributes explicitly requested. 047 */ 048 private final Map<AttributeDescriptor,Set<AttributeDescriptor>> descriptors; 049 050 051 /** 052 * Create a new instance of query attributes from their string representation. 053 * 054 * @param resourceDescriptor The resource descriptor for the SCIM endpoint. 055 * @param attributes The attributes query parameter specifying the set of 056 * attributes or sub-attributes requested, or null if 057 * all attributes and sub-attributes are requested. The 058 * attributes must be qualified by their 059 * schema URI if they are not in the core schema. 060 * 061 * @throws InvalidResourceException If one of the specified attributes does 062 * not exist. 063 */ 064 public SCIMQueryAttributes(final ResourceDescriptor resourceDescriptor, 065 final String attributes) 066 throws InvalidResourceException 067 { 068 descriptors = 069 new HashMap<AttributeDescriptor, Set<AttributeDescriptor>>(); 070 071 if (attributes == null) 072 { 073 allAttributesRequested = true; 074 } 075 else { 076 allAttributesRequested = false; 077 078 List<String> attributeList; 079 if (attributes.isEmpty()) 080 { 081 attributeList = Collections.emptyList(); 082 } 083 else 084 { 085 attributeList = Arrays.asList(attributes.split(",")); 086 } 087 initializeDescriptors(resourceDescriptor, attributeList); 088 } 089 } 090 091 092 /** 093 * Create a new instance of query attributes from their string representation. 094 * 095 * @param resourceDescriptor The resource descriptor for the SCIM endpoint. 096 * @param attributes The attributes query parameter specifying the set of 097 * attributes or sub-attributes requested, or null if 098 * all attributes and sub-attributes are requested. The 099 * attributes must be qualified by their 100 * schema URI if they are not in the core schema. 101 * 102 * @throws InvalidResourceException If one of the specified attributes does 103 * not exist. 104 */ 105 public SCIMQueryAttributes(final List<String> attributes, 106 final ResourceDescriptor resourceDescriptor) 107 throws InvalidResourceException 108 { 109 descriptors = 110 new HashMap<AttributeDescriptor, Set<AttributeDescriptor>>(); 111 112 if (attributes == null) 113 { 114 allAttributesRequested = true; 115 } 116 else { 117 allAttributesRequested = false; 118 initializeDescriptors(resourceDescriptor, attributes); 119 } 120 } 121 122 123 /** 124 * Create a new set of query attributes from the provided information. 125 * 126 * @param descriptors The set of attributes and sub-attributes 127 * explicitly requested, or {@code null} if all 128 * attributes are requested. 129 */ 130 public SCIMQueryAttributes( 131 final Map<AttributeDescriptor,Set<AttributeDescriptor>> descriptors) 132 { 133 this.allAttributesRequested = (descriptors == null); 134 this.descriptors = descriptors; 135 } 136 137 138 139 /** 140 * Determine whether all attributes and sub-attributes are requested by 141 * these query attributes. 142 * 143 * @return {@code true} if all attributes and sub-attributes are requested, 144 * and {@code false} otherwise. 145 */ 146 public boolean allAttributesRequested() 147 { 148 return allAttributesRequested; 149 } 150 151 152 153 /** 154 * Determine whether the specified attribute is requested by these query 155 * attributes. 156 * 157 * @param attributeDescriptor The attribute for which to make the 158 * determination. 159 * 160 * @return {@code true} if the specified attribute is requested, or false 161 * otherwise. 162 */ 163 public boolean isAttributeRequested( 164 final AttributeDescriptor attributeDescriptor) 165 { 166 return allAttributesRequested() || 167 descriptors.containsKey(attributeDescriptor); 168 } 169 170 171 172 /** 173 * Returns the map of requested attributes and sub-attributes. 174 * 175 * @return an unmodifiable map of the requested attributes. 176 */ 177 public Map<AttributeDescriptor, Set<AttributeDescriptor>> getDescriptors() 178 { 179 return Collections.unmodifiableMap(descriptors); 180 } 181 182 183 184 /** 185 * Pare down a SCIM object to its requested attributes. 186 * 187 * @param scimObject The SCIM object to be pared down. 188 * 189 * @return The pared down SCIM object. 190 */ 191 public SCIMObject pareObject(final SCIMObject scimObject) 192 { 193 if (allAttributesRequested()) 194 { 195 return scimObject; 196 } 197 198 final SCIMObject paredObject = new SCIMObject(); 199 for (final Map.Entry<AttributeDescriptor,Set<AttributeDescriptor>> entry : 200 descriptors.entrySet()) 201 { 202 final AttributeDescriptor attributeDescriptor = entry.getKey(); 203 204 final SCIMAttribute a = 205 scimObject.getAttribute(attributeDescriptor.getSchema(), 206 attributeDescriptor.getName()); 207 if (a != null) 208 { 209 final SCIMAttribute paredAttribute = pareAttribute(a); 210 if (paredAttribute != null) 211 { 212 paredObject.addAttribute(paredAttribute); 213 } 214 } 215 } 216 217 return paredObject; 218 } 219 220 221 222 /** 223 * Pare down an attribute to its requested sub-attributes. 224 * 225 * @param attribute The attribute to be pared down. 226 * 227 * @return The pared down attribute, or {@code null} if the attribute 228 * should not be included at all. 229 */ 230 public SCIMAttribute pareAttribute(final SCIMAttribute attribute) 231 { 232 final AttributeDescriptor descriptor = attribute.getAttributeDescriptor(); 233 234 if (allAttributesRequested() || descriptor.getSubAttributes() == null) 235 { 236 return attribute; 237 } 238 239 final Set<AttributeDescriptor> subDescriptors = descriptors.get(descriptor); 240 if (subDescriptors == null) 241 { 242 return null; 243 } 244 245 if (subDescriptors.isEmpty()) 246 { 247 return attribute; 248 } 249 250 if (attribute.getAttributeDescriptor().isMultiValued()) 251 { 252 final ArrayList<SCIMAttributeValue> values = 253 new ArrayList<SCIMAttributeValue>(); 254 255 for (final SCIMAttributeValue v : attribute.getValues()) 256 { 257 final ArrayList<SCIMAttribute> subAttributes = 258 new ArrayList<SCIMAttribute>(); 259 for (final AttributeDescriptor d : subDescriptors) 260 { 261 final SCIMAttribute subAttribute = v.getAttribute(d.getName()); 262 if (subAttribute != null) 263 { 264 subAttributes.add(subAttribute); 265 } 266 } 267 values.add(SCIMAttributeValue.createComplexValue(subAttributes)); 268 } 269 270 return SCIMAttribute.create( 271 descriptor, values.toArray(new SCIMAttributeValue[values.size()])); 272 } 273 else 274 { 275 final ArrayList<SCIMAttribute> subAttributes = 276 new ArrayList<SCIMAttribute>(); 277 for (final AttributeDescriptor d : subDescriptors) 278 { 279 final SCIMAttribute subAttribute = 280 attribute.getValue().getAttribute(d.getName()); 281 if (subAttribute != null) 282 { 283 subAttributes.add(subAttribute); 284 } 285 } 286 return SCIMAttribute.create(descriptor, 287 SCIMAttributeValue.createComplexValue(subAttributes)); 288 } 289 } 290 291 292 293 /** 294 * Return query attributes formed by merging these query attributes with the 295 * provided query attributes. 296 * 297 * @param that The query attributes to be merged with these query attributes 298 * to form new query attributes. 299 * 300 * @return The merged query attributes. 301 * 302 * @throws InvalidResourceException If the query attributes could not be 303 * merged. 304 */ 305 public SCIMQueryAttributes merge(final SCIMQueryAttributes that) 306 throws InvalidResourceException 307 { 308 if (this.allAttributesRequested || that.allAttributesRequested) 309 { 310 return new SCIMQueryAttributes(null); 311 } 312 313 final Map<AttributeDescriptor,Set<AttributeDescriptor>> merged = 314 new HashMap<AttributeDescriptor, Set<AttributeDescriptor>>( 315 this.descriptors); 316 317 for (final Map.Entry<AttributeDescriptor,Set<AttributeDescriptor>> e : 318 that.descriptors.entrySet()) 319 { 320 final AttributeDescriptor attributeDescriptor = e.getKey(); 321 final Set<AttributeDescriptor> thatSet = e.getValue(); 322 323 Set<AttributeDescriptor> thisSet = merged.get(attributeDescriptor); 324 if (thisSet == null) 325 { 326 merged.put(attributeDescriptor, thatSet); 327 } 328 else 329 { 330 if (!thisSet.isEmpty()) 331 { 332 if (thatSet.isEmpty()) 333 { 334 thisSet.clear(); 335 } 336 else 337 { 338 thisSet.addAll(thatSet); 339 } 340 } 341 } 342 } 343 344 return new SCIMQueryAttributes(merged); 345 } 346 347 348 349 /** 350 * {@inheritDoc} 351 */ 352 @Override 353 public String toString() 354 { 355 final StringBuilder sb = new StringBuilder(); 356 sb.append("SCIMQueryAttributes"); 357 sb.append("{allAttributesRequested=").append(allAttributesRequested); 358 sb.append(", descriptors=").append(descriptors); 359 sb.append('}'); 360 return sb.toString(); 361 } 362 363 364 /** 365 * Common code to initialize attribute descriptors for all requested 366 * attributes. 367 * @param resourceDescriptor The resource descriptor for the SCIM endpoint. 368 * @param attributes List of requested attributes. 369 * @throws InvalidResourceException If one of the specified attributes does 370 * not exist. 371 */ 372 private void initializeDescriptors( 373 final ResourceDescriptor resourceDescriptor, 374 final List<String> attributes) throws InvalidResourceException 375 { 376 for (final String a : attributes) 377 { 378 final AttributePath path; 379 if (resourceDescriptor.getSchema().equalsIgnoreCase( 380 "urn:unboundid:schemas:scim:ldap:1.0")) 381 { 382 path = AttributePath.parse(a, resourceDescriptor.getSchema()); 383 } 384 else 385 { 386 path = AttributePath.parse(a); 387 } 388 389 final AttributeDescriptor attributeDescriptor = 390 resourceDescriptor.getAttribute(path.getAttributeSchema(), 391 path.getAttributeName()); 392 393 Set<AttributeDescriptor> subAttributes = 394 descriptors.get(attributeDescriptor); 395 if (subAttributes == null) 396 { 397 subAttributes = new HashSet<AttributeDescriptor>(); 398 if (path.getSubAttributeName() != null) 399 { 400 subAttributes.add( 401 attributeDescriptor.getSubAttribute( 402 path.getSubAttributeName())); 403 } 404 descriptors.put(attributeDescriptor, subAttributes); 405 } 406 else 407 { 408 if (!subAttributes.isEmpty()) 409 { 410 if (path.getSubAttributeName() != null) 411 { 412 subAttributes.add( 413 attributeDescriptor.getSubAttribute( 414 path.getSubAttributeName())); 415 } 416 else 417 { 418 subAttributes.clear(); 419 } 420 } 421 } 422 } 423 424 final AttributeDescriptor id = 425 resourceDescriptor.getAttribute(SCIMConstants.SCHEMA_URI_CORE, "id"); 426 if (!descriptors.containsKey(id)) 427 { 428 descriptors.put(id, new HashSet<AttributeDescriptor>()); 429 } 430 431 final AttributeDescriptor meta = 432 resourceDescriptor.getAttribute(SCIMConstants.SCHEMA_URI_CORE, 433 "meta"); 434 if (!descriptors.containsKey(meta)) 435 { 436 descriptors.put(meta, new HashSet<AttributeDescriptor>()); 437 } 438 } 439}