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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.backends.jeb; 028 029 030 031 import java.util.ArrayList; 032 import java.util.LinkedHashMap; 033 import java.util.LinkedHashSet; 034 import java.util.Map; 035 import java.util.concurrent.ConcurrentHashMap; 036 import java.util.concurrent.atomic.AtomicInteger; 037 038 import com.sleepycat.je.Cursor; 039 import com.sleepycat.je.Database; 040 import com.sleepycat.je.DatabaseConfig; 041 import com.sleepycat.je.DatabaseEntry; 042 import com.sleepycat.je.DatabaseException; 043 import com.sleepycat.je.DeadlockException; 044 import com.sleepycat.je.Environment; 045 import com.sleepycat.je.LockMode; 046 import com.sleepycat.je.OperationStatus; 047 048 import org.opends.messages.Message; 049 import org.opends.server.api.CompressedSchema; 050 import org.opends.server.core.DirectoryServer; 051 import org.opends.server.loggers.debug.DebugTracer; 052 import org.opends.server.protocols.asn1.ASN1Element; 053 import org.opends.server.protocols.asn1.ASN1Exception; 054 import org.opends.server.protocols.asn1.ASN1OctetString; 055 import org.opends.server.protocols.asn1.ASN1Sequence; 056 import org.opends.server.types.Attribute; 057 import org.opends.server.types.AttributeType; 058 import org.opends.server.types.AttributeValue; 059 import org.opends.server.types.ByteArray; 060 import org.opends.server.types.DebugLogLevel; 061 import org.opends.server.types.DirectoryException; 062 import org.opends.server.types.ObjectClass; 063 064 import static org.opends.server.config.ConfigConstants.*; 065 import static org.opends.server.loggers.debug.DebugLogger.*; 066 import static org.opends.messages.JebMessages.*; 067 import static org.opends.server.util.StaticUtils.*; 068 069 070 071 /** 072 * This class provides a compressed schema implementation whose definitions are 073 * stored in a Berkeley DB JE database. 074 */ 075 public final class JECompressedSchema 076 extends CompressedSchema 077 { 078 /** 079 * The tracer object for the debug logger. 080 */ 081 private static final DebugTracer TRACER = getTracer(); 082 083 084 085 /** 086 * The name of the database used to store compressed attribute description 087 * definitions. 088 */ 089 public static final String DB_NAME_AD = "compressed_attributes"; 090 091 092 093 /** 094 * The name of the database used to store compressed object class set 095 * definitions. 096 */ 097 public static final String DB_NAME_OC = "compressed_object_classes"; 098 099 100 101 // The counter used for attribute descriptions. 102 private AtomicInteger adCounter; 103 104 // The counter used for object class sets. 105 private AtomicInteger ocCounter; 106 107 // The map between encoded representations and attribute types. 108 private ConcurrentHashMap<ByteArray,AttributeType> atDecodeMap; 109 110 // The map between encoded representations and attribute options. 111 private ConcurrentHashMap<ByteArray,LinkedHashSet<String>> aoDecodeMap; 112 113 // The map between encoded representations and object class sets. 114 private ConcurrentHashMap<ByteArray,Map<ObjectClass,String>> ocDecodeMap; 115 116 // The map between attribute descriptions and their encoded 117 // representations. 118 private ConcurrentHashMap<AttributeType, 119 ConcurrentHashMap<LinkedHashSet<String>,ByteArray>> adEncodeMap; 120 121 // The map between object class sets and encoded representations. 122 private ConcurrentHashMap<Map<ObjectClass,String>,ByteArray> ocEncodeMap; 123 124 // The compressed attribute description schema database. 125 private Database adDatabase; 126 127 // The compresesd object class set schema database. 128 private Database ocDatabase; 129 130 // The environment in which the databases are held. 131 private Environment environment; 132 133 134 135 /** 136 * Creates a new instance of this JE compressed schema manager. 137 * 138 * @param environment A reference to the database environment in which the 139 * databases will be held. 140 * 141 * @throws DatabaseException If a problem occurs while loading the 142 * compressed schema definitions from the 143 * database. 144 */ 145 public JECompressedSchema(Environment environment) 146 throws DatabaseException 147 { 148 this.environment = environment; 149 150 atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>(); 151 aoDecodeMap = new ConcurrentHashMap<ByteArray,LinkedHashSet<String>>(); 152 ocDecodeMap = new ConcurrentHashMap<ByteArray,Map<ObjectClass,String>>(); 153 adEncodeMap = 154 new ConcurrentHashMap<AttributeType, 155 ConcurrentHashMap<LinkedHashSet<String>,ByteArray>>(); 156 ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>,ByteArray>(); 157 158 adCounter = new AtomicInteger(1); 159 ocCounter = new AtomicInteger(1); 160 161 load(); 162 } 163 164 165 166 /** 167 * Loads the compressed schema information from the database. 168 * 169 * @throws DatabaseException If a problem occurs while loading the 170 * definitions from the database. 171 */ 172 private void load() 173 throws DatabaseException 174 { 175 DatabaseConfig dbConfig = new DatabaseConfig(); 176 177 if(environment.getConfig().getReadOnly()) 178 { 179 dbConfig.setReadOnly(true); 180 dbConfig.setAllowCreate(false); 181 dbConfig.setTransactional(false); 182 } 183 else if(!environment.getConfig().getTransactional()) 184 { 185 dbConfig.setAllowCreate(true); 186 dbConfig.setTransactional(false); 187 dbConfig.setDeferredWrite(true); 188 } 189 else 190 { 191 dbConfig.setAllowCreate(true); 192 dbConfig.setTransactional(true); 193 } 194 195 adDatabase = environment.openDatabase(null, DB_NAME_AD, dbConfig); 196 ocDatabase = environment.openDatabase(null, DB_NAME_OC, dbConfig); 197 198 // Cursor through the object class database and load the object class set 199 // definitions. At the same time, figure out the highest token value and 200 // initialize the object class counter to one greater than that. 201 Cursor ocCursor = ocDatabase.openCursor(null, null); 202 int highestToken = 0; 203 204 try 205 { 206 DatabaseEntry keyEntry = new DatabaseEntry(); 207 DatabaseEntry valueEntry = new DatabaseEntry(); 208 OperationStatus status = ocCursor.getFirst(keyEntry, valueEntry, 209 LockMode.READ_UNCOMMITTED); 210 while (status == OperationStatus.SUCCESS) 211 { 212 ByteArray token = new ByteArray(keyEntry.getData()); 213 highestToken = Math.max(highestToken, decodeInt(token.array())); 214 215 ArrayList<ASN1Element> elements = 216 ASN1Sequence.decodeAsSequence(valueEntry.getData()).elements(); 217 LinkedHashMap<ObjectClass,String> ocMap = 218 new LinkedHashMap<ObjectClass,String>(elements.size()); 219 for (int i=0; i < elements.size(); i++) 220 { 221 ASN1OctetString os = elements.get(i).decodeAsOctetString(); 222 String ocName = os.stringValue(); 223 String lowerName = toLowerCase(ocName); 224 ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); 225 ocMap.put(oc, ocName); 226 } 227 228 ocEncodeMap.put(ocMap, token); 229 ocDecodeMap.put(token, ocMap); 230 231 status = ocCursor.getNext(keyEntry, valueEntry, 232 LockMode.READ_UNCOMMITTED); 233 } 234 } 235 catch (ASN1Exception ae) 236 { 237 if (debugEnabled()) 238 { 239 TRACER.debugCaught(DebugLogLevel.ERROR, ae); 240 } 241 242 Message m = 243 ERR_JEB_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(ae.getMessage()); 244 throw new DatabaseException(m.toString(), ae); 245 } 246 finally 247 { 248 ocCursor.close(); 249 } 250 251 ocCounter.set(highestToken+1); 252 253 254 // Cursor through the attribute description database and load the attribute 255 // set definitions. 256 Cursor adCursor = adDatabase.openCursor(null, null); 257 highestToken = 0; 258 259 try 260 { 261 DatabaseEntry keyEntry = new DatabaseEntry(); 262 DatabaseEntry valueEntry = new DatabaseEntry(); 263 OperationStatus status = adCursor.getFirst(keyEntry, valueEntry, 264 LockMode.READ_UNCOMMITTED); 265 while (status == OperationStatus.SUCCESS) 266 { 267 ByteArray token = new ByteArray(keyEntry.getData()); 268 highestToken = Math.max(highestToken, decodeInt(token.array())); 269 270 ArrayList<ASN1Element> elements = 271 ASN1Sequence.decodeAsSequence(valueEntry.getData()).elements(); 272 273 ASN1OctetString os = elements.get(0).decodeAsOctetString(); 274 String attrName = os.stringValue(); 275 String lowerName = toLowerCase(attrName); 276 AttributeType attrType = 277 DirectoryServer.getAttributeType(lowerName, true); 278 279 LinkedHashSet<String> options = 280 new LinkedHashSet<String>(elements.size()-1); 281 for (int i=1; i < elements.size(); i++) 282 { 283 os = elements.get(i).decodeAsOctetString(); 284 options.add(os.stringValue()); 285 } 286 287 atDecodeMap.put(token, attrType); 288 aoDecodeMap.put(token, options); 289 290 ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map = 291 adEncodeMap.get(attrType); 292 if (map == null) 293 { 294 map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1); 295 map.put(options, token); 296 adEncodeMap.put(attrType, map); 297 } 298 else 299 { 300 map.put(options, token); 301 } 302 303 status = adCursor.getNext(keyEntry, valueEntry, 304 LockMode.READ_UNCOMMITTED); 305 } 306 } 307 catch (ASN1Exception ae) 308 { 309 if (debugEnabled()) 310 { 311 TRACER.debugCaught(DebugLogLevel.ERROR, ae); 312 } 313 314 Message m = 315 ERR_JEB_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(ae.getMessage()); 316 throw new DatabaseException(m.toString(), ae); 317 } 318 finally 319 { 320 adCursor.close(); 321 } 322 323 adCounter.set(highestToken+1); 324 } 325 326 327 328 /** 329 * Closes the databases and releases any resources held by this compressed 330 * schema manager. 331 */ 332 public void close() 333 { 334 try 335 { 336 adDatabase.sync(); 337 } catch (Exception e) {} 338 339 try 340 { 341 adDatabase.close(); 342 } catch (Exception e) {} 343 344 try 345 { 346 ocDatabase.sync(); 347 } catch (Exception e) {} 348 349 try 350 { 351 ocDatabase.close(); 352 } catch (Exception e) {} 353 354 adDatabase = null; 355 ocDatabase = null; 356 environment = null; 357 atDecodeMap = null; 358 aoDecodeMap = null; 359 ocDecodeMap = null; 360 adEncodeMap = null; 361 ocEncodeMap = null; 362 adCounter = null; 363 ocCounter = null; 364 } 365 366 367 368 /** 369 * {@inheritDoc} 370 */ 371 @Override() 372 public byte[] encodeObjectClasses(Map<ObjectClass,String> objectClasses) 373 throws DirectoryException 374 { 375 ByteArray encodedClasses = ocEncodeMap.get(objectClasses); 376 if (encodedClasses == null) 377 { 378 synchronized (ocEncodeMap) 379 { 380 int setValue = ocCounter.getAndIncrement(); 381 byte[] tokenArray = encodeInt(setValue); 382 383 ArrayList<ASN1Element> elements = 384 new ArrayList<ASN1Element>(objectClasses.size()); 385 for (String ocName : objectClasses.values()) 386 { 387 elements.add(new ASN1OctetString(ocName)); 388 } 389 390 byte[] encodedOCs = new ASN1Sequence(elements).encode(); 391 store(ocDatabase, tokenArray, encodedOCs); 392 393 encodedClasses = new ByteArray(tokenArray); 394 ocEncodeMap.put(objectClasses, encodedClasses); 395 ocDecodeMap.put(encodedClasses, objectClasses); 396 397 return tokenArray; 398 } 399 } 400 else 401 { 402 return encodedClasses.array(); 403 } 404 } 405 406 407 408 /** 409 * {@inheritDoc} 410 */ 411 @Override() 412 public Map<ObjectClass,String> decodeObjectClasses( 413 byte[] encodedObjectClasses) 414 throws DirectoryException 415 { 416 ByteArray byteArray = new ByteArray(encodedObjectClasses); 417 Map<ObjectClass,String> ocMap = ocDecodeMap.get(byteArray); 418 if (ocMap == null) 419 { 420 Message message = ERR_JEB_COMPSCHEMA_UNKNOWN_OC_TOKEN.get( 421 bytesToHex(encodedObjectClasses)); 422 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 423 message); 424 } 425 else 426 { 427 return ocMap; 428 } 429 } 430 431 432 433 /** 434 * {@inheritDoc} 435 */ 436 @Override() 437 public byte[] encodeAttribute(Attribute attribute) 438 throws DirectoryException 439 { 440 AttributeType type = attribute.getAttributeType(); 441 LinkedHashSet<String> options = attribute.getOptions(); 442 443 ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map = 444 adEncodeMap.get(type); 445 if (map == null) 446 { 447 byte[] tokenArray; 448 synchronized (adEncodeMap) 449 { 450 map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1); 451 452 int intValue = adCounter.getAndIncrement(); 453 tokenArray = encodeInt(intValue); 454 ByteArray byteArray = new ByteArray(tokenArray); 455 map.put(options,byteArray); 456 457 ArrayList<ASN1Element> elements = 458 new ArrayList<ASN1Element>(options.size()+1); 459 elements.add(new ASN1OctetString(attribute.getName())); 460 for (String option : options) 461 { 462 elements.add(new ASN1OctetString(option)); 463 } 464 byte[] encodedValue = new ASN1Sequence(elements).encode(); 465 store(adDatabase, tokenArray, encodedValue); 466 467 adEncodeMap.put(type, map); 468 atDecodeMap.put(byteArray, type); 469 aoDecodeMap.put(byteArray, options); 470 } 471 472 return encodeAttribute(tokenArray, attribute); 473 } 474 else 475 { 476 ByteArray byteArray = map.get(options); 477 if (byteArray == null) 478 { 479 byte[] tokenArray; 480 synchronized (map) 481 { 482 int intValue = adCounter.getAndIncrement(); 483 tokenArray = encodeInt(intValue); 484 byteArray = new ByteArray(tokenArray); 485 map.put(options,byteArray); 486 487 ArrayList<ASN1Element> elements = 488 new ArrayList<ASN1Element>(options.size()+1); 489 elements.add(new ASN1OctetString(attribute.getName())); 490 for (String option : options) 491 { 492 elements.add(new ASN1OctetString(option)); 493 } 494 byte[] encodedValue = new ASN1Sequence(elements).encode(); 495 store(adDatabase, tokenArray, encodedValue); 496 497 atDecodeMap.put(byteArray, type); 498 aoDecodeMap.put(byteArray, options); 499 } 500 501 return encodeAttribute(tokenArray, attribute); 502 } 503 else 504 { 505 return encodeAttribute(byteArray.array(), attribute); 506 } 507 } 508 } 509 510 511 512 /** 513 * Encodes the information in the provided attribute to a byte 514 * array. 515 * 516 * @param adArray The byte array that is a placeholder for the 517 * attribute type and set of options. 518 * @param attribute The attribute to be encoded. 519 * 520 * @return An encoded representation of the provided attribute. 521 */ 522 private byte[] encodeAttribute(byte[] adArray, Attribute attribute) 523 { 524 LinkedHashSet<AttributeValue> values = attribute.getValues(); 525 int totalValuesLength = 0; 526 byte[][] subArrays = new byte[values.size()*2][]; 527 int pos = 0; 528 for (AttributeValue v : values) 529 { 530 byte[] vBytes = v.getValueBytes(); 531 byte[] lBytes = ASN1Element.encodeLength(vBytes.length); 532 533 subArrays[pos++] = lBytes; 534 subArrays[pos++] = vBytes; 535 536 totalValuesLength += lBytes.length + vBytes.length; 537 } 538 539 byte[] adArrayLength = ASN1Element.encodeLength(adArray.length); 540 byte[] countBytes = ASN1Element.encodeLength(values.size()); 541 int totalLength = adArrayLength.length + adArray.length + 542 countBytes.length + totalValuesLength; 543 byte[] array = new byte[totalLength]; 544 545 System.arraycopy(adArrayLength, 0, array, 0, 546 adArrayLength.length); 547 pos = adArrayLength.length; 548 System.arraycopy(adArray, 0, array, pos, adArray.length); 549 pos += adArray.length; 550 System.arraycopy(countBytes, 0, array, pos, countBytes.length); 551 pos += countBytes.length; 552 553 for (int i=0; i < subArrays.length; i++) 554 { 555 System.arraycopy(subArrays[i], 0, array, pos, 556 subArrays[i].length); 557 pos += subArrays[i].length; 558 } 559 560 return array; 561 } 562 563 564 565 /** 566 * {@inheritDoc} 567 */ 568 @Override() 569 public Attribute decodeAttribute(byte[] encodedEntry, int startPos, 570 int length) 571 throws DirectoryException 572 { 573 // Figure out how many bytes are in the token that is the placeholder for 574 // the attribute description. 575 int pos = startPos; 576 int adArrayLength = encodedEntry[pos] & 0x7F; 577 if (adArrayLength != encodedEntry[pos++]) 578 { 579 int numLengthBytes = adArrayLength; 580 adArrayLength = 0; 581 for (int i=0; i < numLengthBytes; i++, pos++) 582 { 583 adArrayLength = (adArrayLength << 8) | (encodedEntry[pos] & 0xFF); 584 } 585 } 586 587 588 // Get the attribute description token and make sure it resolves to an 589 // attribute type and option set. 590 ByteArray adArray = new ByteArray(new byte[adArrayLength]); 591 System.arraycopy(encodedEntry, pos, adArray.array(), 0, adArrayLength); 592 pos += adArrayLength; 593 AttributeType attrType = atDecodeMap.get(adArray); 594 LinkedHashSet<String> options = aoDecodeMap.get(adArray); 595 if ((attrType == null) || (options == null)) 596 { 597 Message message = ERR_JEB_COMPSCHEMA_UNRECOGNIZED_AD_TOKEN.get( 598 bytesToHex(adArray.array())); 599 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 600 message); 601 } 602 603 604 // Determine the number of values for the attribute. 605 int numValues = encodedEntry[pos] & 0x7F; 606 if (numValues != encodedEntry[pos++]) 607 { 608 int numValuesBytes = numValues; 609 numValues = 0; 610 for (int i=0; i < numValuesBytes; i++, pos++) 611 { 612 numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF); 613 } 614 } 615 616 617 // Read the appropriate number of values. 618 LinkedHashSet<AttributeValue> values = 619 new LinkedHashSet<AttributeValue>(numValues); 620 for (int i=0; i < numValues; i++) 621 { 622 int valueLength = encodedEntry[pos] & 0x7F; 623 if (valueLength != encodedEntry[pos++]) 624 { 625 int valueLengthBytes = valueLength; 626 valueLength = 0; 627 for (int j=0; j < valueLengthBytes; j++, pos++) 628 { 629 valueLength = (valueLength << 8) | (encodedEntry[pos] & 0xFF); 630 } 631 } 632 633 byte[] valueBytes = new byte[valueLength]; 634 System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength); 635 pos += valueLength; 636 values.add(new AttributeValue(attrType, new ASN1OctetString(valueBytes))); 637 } 638 639 return new Attribute(attrType, attrType.getPrimaryName(), options, values); 640 } 641 642 643 644 /** 645 * Stores the provided key-value pair in the specified database container. 646 * 647 * @param database The database in which to store the information. 648 * @param keyBytes The byte array containing the key to store. 649 * @param valueBytes The byte array containing the value to store. 650 * 651 * @throws DirectoryException If a problem occurs while attempting to store 652 * the data. 653 */ 654 private void store(Database database, byte[] keyBytes, byte[] valueBytes) 655 throws DirectoryException 656 { 657 boolean successful = false; 658 DatabaseEntry keyEntry = new DatabaseEntry(keyBytes); 659 DatabaseEntry valueEntry = new DatabaseEntry(valueBytes); 660 661 for (int i=0; i < 3; i++) 662 { 663 try 664 { 665 OperationStatus status = database.putNoOverwrite(null, keyEntry, 666 valueEntry); 667 if (status == OperationStatus.SUCCESS) 668 { 669 successful = true; 670 break; 671 } 672 else 673 { 674 Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get( 675 status.toString()); 676 throw new DirectoryException( 677 DirectoryServer.getServerErrorResultCode(), m); 678 } 679 } 680 catch (DeadlockException de) 681 { 682 continue; 683 } 684 catch (DatabaseException de) 685 { 686 Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_EX.get(de.getMessage()); 687 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 688 m, de); 689 } 690 } 691 692 if (! successful) 693 { 694 Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get(); 695 throw new DirectoryException( 696 DirectoryServer.getServerErrorResultCode(), m); 697 } 698 } 699 700 701 702 703 /** 704 * Encodes the provided int value to a byte array. 705 * 706 * @param intValue The int value to be encoded. 707 * 708 * @return The byte array containing the encoded int value. 709 */ 710 private byte[] encodeInt(int intValue) 711 { 712 byte[] array; 713 if (intValue <= 0xFF) 714 { 715 array = new byte[1]; 716 array[0] = (byte) (intValue & 0xFF); 717 } 718 else if (intValue <= 0xFFFF) 719 { 720 array = new byte[2]; 721 array[0] = (byte) ((intValue >> 8) & 0xFF); 722 array[1] = (byte) (intValue & 0xFF); 723 } 724 else if (intValue <= 0xFFFFFF) 725 { 726 array = new byte[3]; 727 array[0] = (byte) ((intValue >> 16) & 0xFF); 728 array[1] = (byte) ((intValue >> 8) & 0xFF); 729 array[2] = (byte) (intValue & 0xFF); 730 } 731 else 732 { 733 array = new byte[4]; 734 array[0] = (byte) ((intValue >> 24) & 0xFF); 735 array[1] = (byte) ((intValue >> 16) & 0xFF); 736 array[2] = (byte) ((intValue >> 8) & 0xFF); 737 array[3] = (byte) (intValue & 0xFF); 738 } 739 740 return array; 741 } 742 743 744 745 /** 746 * Decodes the contents of the provided byte array as an int. 747 * 748 * @param byteArray The byte array containing the data to decode. 749 * 750 * @return The decoded int value. 751 */ 752 private int decodeInt(byte[] byteArray) 753 { 754 int intValue = 0; 755 756 for (byte b : byteArray) 757 { 758 intValue <<= 8; 759 intValue |= (b & 0xFF); 760 } 761 762 return intValue; 763 } 764 } 765