001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2009 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS. 026 */ 027package org.opends.server.api; 028 029import static org.opends.messages.CoreMessages.*; 030import static com.forgerock.opendj.util.StaticUtils.toLowerCase; 031 032import java.util.AbstractMap.SimpleImmutableEntry; 033import java.util.Collection; 034import java.util.Collections; 035import java.util.Iterator; 036import java.util.LinkedHashMap; 037import java.util.LinkedHashSet; 038import java.util.List; 039import java.util.Map; 040import java.util.Map.Entry; 041import java.util.Set; 042import java.util.concurrent.ConcurrentHashMap; 043import java.util.concurrent.CopyOnWriteArrayList; 044 045import org.opends.server.core.DirectoryServer; 046import org.opends.server.types.Attribute; 047import org.opends.server.types.AttributeBuilder; 048import org.opends.server.types.AttributeType; 049import org.forgerock.opendj.ldap.ByteString; 050import org.opends.server.types.Attributes; 051import org.forgerock.opendj.ldap.ByteSequence; 052import org.forgerock.opendj.ldap.ByteSequenceReader; 053import org.forgerock.opendj.ldap.ByteStringBuilder; 054import org.opends.server.types.DirectoryException; 055import org.opends.server.types.ObjectClass; 056 057/** 058 * This class provides a utility for interacting with compressed representations 059 * of schema elements. The default implementation does not persist encoded 060 * attributes and object classes. 061 */ 062@org.opends.server.types.PublicAPI( 063 stability = org.opends.server.types.StabilityLevel.UNCOMMITTED, 064 mayInstantiate = false, 065 mayExtend = true, 066 mayInvoke = false) 067public class CompressedSchema 068{ 069 /** Maps attribute description to ID. */ 070 private final List<Entry<AttributeType, Set<String>>> adDecodeMap = new CopyOnWriteArrayList<>(); 071 /** Maps ID to attribute description. */ 072 private final Map<Entry<AttributeType, Set<String>>, Integer> adEncodeMap = new ConcurrentHashMap<>(); 073 /** The map between encoded representations and object class sets. */ 074 private final List<Map<ObjectClass, String>> ocDecodeMap = new CopyOnWriteArrayList<>(); 075 /** The map between object class sets and encoded representations. */ 076 private final Map<Map<ObjectClass, String>, Integer> ocEncodeMap = new ConcurrentHashMap<>(); 077 078 /** 079 * Decodes the contents of the provided array as an attribute at the current 080 * position. 081 * 082 * @param reader 083 * The byte string reader containing the encoded entry. 084 * @return The decoded attribute. 085 * @throws DirectoryException 086 * If the attribute could not be decoded properly for some reason. 087 */ 088 public final Attribute decodeAttribute(final ByteSequenceReader reader) 089 throws DirectoryException 090 { 091 // First decode the encoded attribute description id. 092 final int length = reader.readBERLength(); 093 final byte[] idBytes = new byte[length]; 094 reader.readBytes(idBytes); 095 final int id = decodeId(idBytes); 096 097 // Look up the attribute description. 098 Entry<AttributeType, Set<String>> ad = adDecodeMap.get(id); 099 if (ad == null) 100 { 101 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 102 ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(id)); 103 } 104 105 // Before returning the attribute, make sure that the attribute type is not 106 // stale. 107 AttributeType attrType = ad.getKey(); 108 Set<String> options = ad.getValue(); 109 if (attrType.isDirty()) 110 { 111 ad = loadAttribute(idBytes, attrType.getNameOrOID(), options); 112 attrType = ad.getKey(); 113 options = ad.getValue(); 114 } 115 116 // Determine the number of values for the attribute. 117 final int numValues = reader.readBERLength(); 118 119 // For the common case of a single value with no options, generate 120 // less garbage. 121 if (numValues == 1 && options.isEmpty()) 122 { 123 final int valueLength = reader.readBERLength(); 124 final ByteSequence valueBytes = reader.readByteSequence(valueLength); 125 return Attributes.create(attrType, valueBytes.toByteString()); 126 } 127 else 128 { 129 // Read the appropriate number of values. 130 final AttributeBuilder builder = new AttributeBuilder(attrType); 131 builder.setOptions(options); 132 for (int i = 0; i < numValues; i++) 133 { 134 final int valueLength = reader.readBERLength(); 135 final ByteSequence valueBytes = reader.readByteSequence(valueLength); 136 builder.add(valueBytes.toByteString()); 137 } 138 return builder.toAttribute(); 139 } 140 } 141 142 143 144 /** 145 * Decodes an object class set from the provided byte string. 146 * 147 * @param reader 148 * The byte string reader containing the object class set identifier. 149 * @return The decoded object class set. 150 * @throws DirectoryException 151 * If the provided byte string reader cannot be decoded as an object 152 * class set. 153 */ 154 public final Map<ObjectClass, String> decodeObjectClasses( 155 final ByteSequenceReader reader) throws DirectoryException 156 { 157 // First decode the encoded object class id. 158 final int length = reader.readBERLength(); 159 final byte[] idBytes = new byte[length]; 160 reader.readBytes(idBytes); 161 final int id = decodeId(idBytes); 162 163 // Look up the object classes. 164 final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id); 165 if (ocMap != null) 166 { 167 // Before returning the object classes, make sure that none of them are 168 // stale. 169 for (final ObjectClass oc : ocMap.keySet()) 170 { 171 if (oc.isDirty()) 172 { 173 // Found at least one object class which is dirty so refresh them. 174 return loadObjectClasses(idBytes, ocMap.values()); 175 } 176 } 177 return ocMap; 178 } 179 else 180 { 181 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 182 ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id)); 183 } 184 } 185 186 187 188 /** 189 * Encodes the information in the provided attribute to a byte array. 190 * 191 * @param builder 192 * The buffer to encode the attribute to. 193 * @param attribute 194 * The attribute to be encoded. 195 * @throws DirectoryException 196 * If a problem occurs while attempting to determine the appropriate 197 * identifier. 198 */ 199 public final void encodeAttribute(final ByteStringBuilder builder, 200 final Attribute attribute) throws DirectoryException 201 { 202 // Re-use or allocate a new ID. 203 final AttributeType type = attribute.getAttributeType(); 204 final Set<String> options = attribute.getOptions(); 205 final Entry<AttributeType, Set<String>> ad = new SimpleImmutableEntry<>(type, options); 206 207 // Use double checked locking to avoid lazy registration races. 208 Integer id = adEncodeMap.get(ad); 209 if (id == null) 210 { 211 synchronized (adEncodeMap) 212 { 213 id = adEncodeMap.get(ad); 214 if (id == null) 215 { 216 id = adDecodeMap.size(); 217 adDecodeMap.add(ad); 218 adEncodeMap.put(ad, id); 219 storeAttribute(encodeId(id), type.getNameOrOID(), options); 220 } 221 } 222 } 223 224 // Encode the attribute. 225 final byte[] idBytes = encodeId(id); 226 builder.appendBERLength(idBytes.length); 227 builder.appendBytes(idBytes); 228 builder.appendBERLength(attribute.size()); 229 for (final ByteString v : attribute) 230 { 231 builder.appendBERLength(v.length()); 232 builder.appendBytes(v); 233 } 234 } 235 236 237 238 /** 239 * Encodes the provided set of object classes to a byte array. If the same set 240 * had been previously encoded, then the cached value will be used. Otherwise, 241 * a new value will be created. 242 * 243 * @param builder 244 * The buffer to encode the object classes to. 245 * @param objectClasses 246 * The set of object classes for which to retrieve the corresponding 247 * byte array token. 248 * @throws DirectoryException 249 * If a problem occurs while attempting to determine the appropriate 250 * identifier. 251 */ 252 public final void encodeObjectClasses(final ByteStringBuilder builder, 253 final Map<ObjectClass, String> objectClasses) throws DirectoryException 254 { 255 // Re-use or allocate a new ID. 256 // Use double checked locking to avoid lazy registration races. 257 Integer id = ocEncodeMap.get(objectClasses); 258 if (id == null) 259 { 260 synchronized (ocEncodeMap) 261 { 262 id = ocEncodeMap.get(objectClasses); 263 if (id == null) 264 { 265 id = ocDecodeMap.size(); 266 ocDecodeMap.add(objectClasses); 267 ocEncodeMap.put(objectClasses, id); 268 storeObjectClasses(encodeId(id), objectClasses.values()); 269 } 270 } 271 } 272 273 // Encode the object classes. 274 final byte[] idBytes = encodeId(id); 275 builder.appendBERLength(idBytes.length); 276 builder.appendBytes(idBytes); 277 } 278 279 280 281 /** 282 * Returns a view of the encoded attributes in this compressed schema which 283 * can be used for saving the entire content to disk. The iterator returned by 284 * this method is not thread safe. 285 * 286 * @return A view of the encoded attributes in this compressed schema. 287 */ 288 protected final Iterable<Entry<byte[], 289 Entry<String, 290 Collection<String>>>> getAllAttributes() 291 { 292 return new Iterable<Entry<byte[], Entry<String, Collection<String>>>>() 293 { 294 295 @Override 296 public Iterator<Entry<byte[], 297 Entry<String, Collection<String>>>> iterator() 298 { 299 return new Iterator<Entry<byte[], Entry<String, Collection<String>>>>() 300 { 301 private int id = 0; 302 303 304 305 @Override 306 public boolean hasNext() 307 { 308 return id < adDecodeMap.size(); 309 } 310 311 312 313 @Override 314 public Entry<byte[], Entry<String, Collection<String>>> next() 315 { 316 final byte[] encodedAttribute = encodeId(id); 317 final Entry<AttributeType, Set<String>> ad = adDecodeMap.get(id++); 318 return new SimpleImmutableEntry<byte[], 319 Entry<String, Collection<String>>>( 320 encodedAttribute, 321 new SimpleImmutableEntry<String, Collection<String>>(ad 322 .getKey().getNameOrOID(), ad.getValue())); 323 } 324 325 326 327 @Override 328 public void remove() 329 { 330 throw new UnsupportedOperationException(); 331 } 332 }; 333 } 334 }; 335 } 336 337 338 339 /** 340 * Returns a view of the encoded object classes in this compressed schema 341 * which can be used for saving the entire content to disk. The iterator 342 * returned by this method is not thread safe. 343 * 344 * @return A view of the encoded object classes in this compressed schema. 345 */ 346 protected final Iterable<Entry<byte[], 347 Collection<String>>> getAllObjectClasses() 348 { 349 return new Iterable<Entry<byte[], Collection<String>>>() 350 { 351 352 @Override 353 public Iterator<Entry<byte[], Collection<String>>> iterator() 354 { 355 return new Iterator<Map.Entry<byte[], Collection<String>>>() 356 { 357 private int id = 0; 358 359 @Override 360 public boolean hasNext() 361 { 362 return id < ocDecodeMap.size(); 363 } 364 365 @Override 366 public Entry<byte[], Collection<String>> next() 367 { 368 final byte[] encodedObjectClasses = encodeId(id); 369 final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id++); 370 return new SimpleImmutableEntry<>(encodedObjectClasses, ocMap.values()); 371 } 372 373 @Override 374 public void remove() 375 { 376 throw new UnsupportedOperationException(); 377 } 378 }; 379 } 380 }; 381 } 382 383 /** 384 * Loads an encoded attribute into this compressed schema. This method may 385 * called by implementations during initialization when loading content from 386 * disk. 387 * 388 * @param encodedAttribute 389 * The encoded attribute description. 390 * @param attributeName 391 * The user provided attribute type name. 392 * @param attributeOptions 393 * The non-null but possibly empty set of attribute options. 394 * @return The attribute type description. 395 */ 396 protected final Entry<AttributeType, Set<String>> loadAttribute( 397 final byte[] encodedAttribute, final String attributeName, 398 final Collection<String> attributeOptions) 399 { 400 final AttributeType type = DirectoryServer.getAttributeTypeOrDefault(toLowerCase(attributeName)); 401 final Set<String> options = getOptions(attributeOptions); 402 final Entry<AttributeType, Set<String>> ad = new SimpleImmutableEntry<>(type, options); 403 final int id = decodeId(encodedAttribute); 404 synchronized (adEncodeMap) 405 { 406 adEncodeMap.put(ad, id); 407 if (id < adDecodeMap.size()) 408 { 409 adDecodeMap.set(id, ad); 410 } 411 else 412 { 413 // Grow the decode array. 414 while (id > adDecodeMap.size()) 415 { 416 adDecodeMap.add(null); 417 } 418 adDecodeMap.add(ad); 419 } 420 } 421 return ad; 422 } 423 424 private Set<String> getOptions(final Collection<String> attributeOptions) 425 { 426 switch (attributeOptions.size()) 427 { 428 case 0: 429 return Collections.emptySet(); 430 case 1: 431 return Collections.singleton(attributeOptions.iterator().next()); 432 default: 433 return new LinkedHashSet<>(attributeOptions); 434 } 435 } 436 437 /** 438 * Loads an encoded object class into this compressed schema. This method may 439 * called by implementations during initialization when loading content from 440 * disk. 441 * 442 * @param encodedObjectClasses 443 * The encoded object classes. 444 * @param objectClassNames 445 * The user provided set of object class names. 446 * @return The object class set. 447 */ 448 protected final Map<ObjectClass, String> loadObjectClasses( 449 final byte[] encodedObjectClasses, 450 final Collection<String> objectClassNames) 451 { 452 final LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<>(objectClassNames.size()); 453 for (final String name : objectClassNames) 454 { 455 final String lowerName = toLowerCase(name); 456 final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); 457 ocMap.put(oc, name); 458 } 459 final int id = decodeId(encodedObjectClasses); 460 synchronized (ocEncodeMap) 461 { 462 ocEncodeMap.put(ocMap, id); 463 if (id < ocDecodeMap.size()) 464 { 465 ocDecodeMap.set(id, ocMap); 466 } 467 else 468 { 469 // Grow the decode array. 470 while (id > ocDecodeMap.size()) 471 { 472 ocDecodeMap.add(null); 473 } 474 ocDecodeMap.add(ocMap); 475 } 476 } 477 return ocMap; 478 } 479 480 481 482 /** 483 * Persists the provided encoded attribute. The default implementation is to 484 * do nothing. Calls to this method are synchronized, so implementations can 485 * assume that this method is not being called by other threads. Note that 486 * this method is not thread-safe with respect to 487 * {@link #storeObjectClasses(byte[], Collection)}. 488 * 489 * @param encodedAttribute 490 * The encoded attribute description. 491 * @param attributeName 492 * The user provided attribute type name. 493 * @param attributeOptions 494 * The non-null but possibly empty set of attribute options. 495 * @throws DirectoryException 496 * If an error occurred while persisting the encoded attribute. 497 */ 498 protected void storeAttribute(final byte[] encodedAttribute, 499 final String attributeName, final Collection<String> attributeOptions) 500 throws DirectoryException 501 { 502 // Do nothing by default. 503 } 504 505 506 507 /** 508 * Persists the provided encoded object classes. The default implementation is 509 * to do nothing. Calls to this method are synchronized, so implementations 510 * can assume that this method is not being called by other threads. Note that 511 * this method is not thread-safe with respect to 512 * {@link #storeAttribute(byte[], String, Collection)}. 513 * 514 * @param encodedObjectClasses 515 * The encoded object classes. 516 * @param objectClassNames 517 * The user provided set of object class names. 518 * @throws DirectoryException 519 * If an error occurred while persisting the encoded object classes. 520 */ 521 protected void storeObjectClasses(final byte[] encodedObjectClasses, 522 final Collection<String> objectClassNames) throws DirectoryException 523 { 524 // Do nothing by default. 525 } 526 527 528 529 /** 530 * Decodes the provided encoded schema element ID. 531 * 532 * @param idBytes 533 * The encoded schema element ID. 534 * @return The schema element ID. 535 */ 536 private int decodeId(final byte[] idBytes) 537 { 538 int id = 0; 539 for (final byte b : idBytes) 540 { 541 id <<= 8; 542 id |= b & 0xFF; 543 } 544 return id - 1; // Subtract 1 to compensate for old behavior. 545 } 546 547 548 549 /** 550 * Encodes the provided schema element ID. 551 * 552 * @param id 553 * The schema element ID. 554 * @return The encoded schema element ID. 555 */ 556 private byte[] encodeId(final int id) 557 { 558 final int value = id + 1; // Add 1 to compensate for old behavior. 559 final byte[] idBytes; 560 if (value <= 0xFF) 561 { 562 idBytes = new byte[1]; 563 idBytes[0] = (byte) (value & 0xFF); 564 } 565 else if (value <= 0xFFFF) 566 { 567 idBytes = new byte[2]; 568 idBytes[0] = (byte) ((value >> 8) & 0xFF); 569 idBytes[1] = (byte) (value & 0xFF); 570 } 571 else if (value <= 0xFFFFFF) 572 { 573 idBytes = new byte[3]; 574 idBytes[0] = (byte) ((value >> 16) & 0xFF); 575 idBytes[1] = (byte) ((value >> 8) & 0xFF); 576 idBytes[2] = (byte) (value & 0xFF); 577 } 578 else 579 { 580 idBytes = new byte[4]; 581 idBytes[0] = (byte) ((value >> 24) & 0xFF); 582 idBytes[1] = (byte) ((value >> 16) & 0xFF); 583 idBytes[2] = (byte) ((value >> 8) & 0xFF); 584 idBytes[3] = (byte) (value & 0xFF); 585 } 586 return idBytes; 587 } 588}