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 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.backends.jeb; 028 import org.opends.messages.Message; 029 030 import com.sleepycat.je.*; 031 import org.opends.server.loggers.debug.DebugTracer; 032 import static org.opends.server.loggers.debug.DebugLogger.getTracer; 033 import static org.opends.server.loggers.ErrorLogger.*; 034 import org.opends.server.types.*; 035 import org.opends.server.admin.std.server.LocalDBVLVIndexCfg; 036 import org.opends.server.admin.server.ConfigurationChangeListener; 037 import org.opends.server.core.DirectoryServer; 038 import org.opends.server.core.SearchOperation; 039 import static org.opends.messages.JebMessages. 040 NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD; 041 import static org.opends.messages.JebMessages. 042 ERR_ENTRYIDSORTER_NEGATIVE_START_POS; 043 import static org.opends.messages.JebMessages. 044 ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR; 045 import static org.opends.messages.JebMessages. 046 ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER; 047 048 049 import static org.opends.server.loggers.debug.DebugLogger.*; 050 import org.opends.server.util.StaticUtils; 051 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; 052 import org.opends.server.api.OrderingMatchingRule; 053 import org.opends.server.config.ConfigException; 054 import org.opends.server.protocols.asn1.ASN1Element; 055 import org.opends.server.protocols.asn1.ASN1OctetString; 056 import org.opends.server.protocols.ldap.LDAPResultCode; 057 import org.opends.server.controls.VLVRequestControl; 058 import org.opends.server.controls.VLVResponseControl; 059 import org.opends.server.controls.ServerSideSortRequestControl; 060 061 import java.util.*; 062 import java.util.concurrent.atomic.AtomicInteger; 063 064 /** 065 * This class represents a VLV index. Each database record is a sorted list 066 * of entry IDs followed by sets of attribute values used to sort the entries. 067 * The entire set of entry IDs are broken up into sorted subsets to decrease 068 * the number of database retrivals needed for a range lookup. The records are 069 * keyed by the last entry's first sort attribute value. The list of entries 070 * in a particular database record maintains the property where the first sort 071 * attribute value is bigger then the previous key but smaller or equal 072 * to its own key. 073 */ 074 public class VLVIndex extends DatabaseContainer 075 implements ConfigurationChangeListener<LocalDBVLVIndexCfg> 076 { 077 /** 078 * The tracer object for the debug logger. 079 */ 080 private static final DebugTracer TRACER = getTracer(); 081 082 /** 083 * The comparator for vlvIndex keys. 084 */ 085 public VLVKeyComparator comparator; 086 087 /** 088 * The limit on the number of entry IDs that may be indexed by one key. 089 */ 090 private int sortedSetCapacity = 4000; 091 092 /** 093 * The cached count of entries in this index. 094 */ 095 private AtomicInteger count; 096 097 private State state; 098 099 /** 100 * A flag to indicate if this vlvIndex should be trusted to be consistent 101 * with the entries database. 102 */ 103 private boolean trusted = false; 104 105 /** 106 * A flag to indicate if a rebuild process is running on this vlvIndex. 107 */ 108 private boolean rebuildRunning = false; 109 110 /** 111 * The VLV vlvIndex configuration. 112 */ 113 private LocalDBVLVIndexCfg config; 114 115 private ID2Entry id2entry; 116 117 private DN baseDN; 118 119 private SearchFilter filter; 120 121 private SearchScope scope; 122 123 /** 124 * The SortOrder in use by this VLV index to sort the entries. 125 */ 126 public SortOrder sortOrder; 127 128 129 /** 130 * Create a new VLV vlvIndex object. 131 * 132 * @param config The VLV index config object to use for this VLV 133 * index. 134 * @param state The state database to persist vlvIndex state info. 135 * @param env The JE Environemnt 136 * @param entryContainer The database entryContainer holding this vlvIndex. 137 * @throws com.sleepycat.je.DatabaseException 138 * If an error occurs in the JE database. 139 * @throws ConfigException if a error occurs while reading the VLV index 140 * configuration 141 */ 142 public VLVIndex(LocalDBVLVIndexCfg config, State state, Environment env, 143 EntryContainer entryContainer) 144 throws DatabaseException, ConfigException 145 { 146 super(entryContainer.getDatabasePrefix()+"_vlv."+config.getName(), 147 env, entryContainer); 148 149 this.config = config; 150 this.baseDN = config.getBaseDN(); 151 this.scope = SearchScope.valueOf(config.getScope().name()); 152 this.sortedSetCapacity = config.getMaxBlockSize(); 153 this.id2entry = entryContainer.getID2Entry(); 154 155 try 156 { 157 this.filter = 158 SearchFilter.createFilterFromString(config.getFilter()); 159 } 160 catch(Exception e) 161 { 162 Message msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get( 163 config.getFilter(), name, stackTraceToSingleLineString(e)); 164 throw new ConfigException(msg); 165 } 166 167 String[] sortAttrs = config.getSortOrder().split(" "); 168 SortKey[] sortKeys = new SortKey[sortAttrs.length]; 169 OrderingMatchingRule[] orderingRules = 170 new OrderingMatchingRule[sortAttrs.length]; 171 boolean[] ascending = new boolean[sortAttrs.length]; 172 for(int i = 0; i < sortAttrs.length; i++) 173 { 174 try 175 { 176 if(sortAttrs[i].startsWith("-")) 177 { 178 ascending[i] = false; 179 sortAttrs[i] = sortAttrs[i].substring(1); 180 } 181 else 182 { 183 ascending[i] = true; 184 if(sortAttrs[i].startsWith("+")) 185 { 186 sortAttrs[i] = sortAttrs[i].substring(1); 187 } 188 } 189 } 190 catch(Exception e) 191 { 192 Message msg = 193 ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( 194 String.valueOf(sortKeys[i]), name); 195 throw new ConfigException(msg); 196 } 197 198 AttributeType attrType = 199 DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase()); 200 if(attrType == null) 201 { 202 Message msg = 203 ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortAttrs[i], name); 204 throw new ConfigException(msg); 205 } 206 sortKeys[i] = new SortKey(attrType, ascending[i]); 207 orderingRules[i] = attrType.getOrderingMatchingRule(); 208 } 209 210 this.sortOrder = new SortOrder(sortKeys); 211 this.comparator = new VLVKeyComparator(orderingRules, ascending); 212 213 DatabaseConfig dbNodupsConfig = new DatabaseConfig(); 214 215 if(env.getConfig().getReadOnly()) 216 { 217 dbNodupsConfig.setReadOnly(true); 218 dbNodupsConfig.setAllowCreate(false); 219 dbNodupsConfig.setTransactional(false); 220 } 221 else if(!env.getConfig().getTransactional()) 222 { 223 dbNodupsConfig.setAllowCreate(true); 224 dbNodupsConfig.setTransactional(false); 225 dbNodupsConfig.setDeferredWrite(true); 226 } 227 else 228 { 229 dbNodupsConfig.setAllowCreate(true); 230 dbNodupsConfig.setTransactional(true); 231 } 232 233 this.dbConfig = dbNodupsConfig; 234 this.dbConfig.setOverrideBtreeComparator(true); 235 this.dbConfig.setBtreeComparator(this.comparator); 236 237 this.state = state; 238 239 this.trusted = state.getIndexTrustState(null, this); 240 if(!trusted && entryContainer.getHighestEntryID().equals(new EntryID(0))) 241 { 242 // If there are no entries in the entry container then there 243 // is no reason why this vlvIndex can't be upgraded to trusted. 244 setTrusted(null, true); 245 } 246 247 // Issue warning if this vlvIndex is not trusted 248 if(!trusted) 249 { 250 logError(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(name)); 251 } 252 253 this.count = new AtomicInteger(0); 254 this.config.addChangeListener(this); 255 } 256 257 /** 258 * {@inheritDoc} 259 */ 260 public void open() throws DatabaseException 261 { 262 super.open(); 263 264 DatabaseEntry key = new DatabaseEntry(); 265 OperationStatus status; 266 LockMode lockMode = LockMode.RMW; 267 DatabaseEntry data = new DatabaseEntry(); 268 269 Cursor cursor = openCursor(null, CursorConfig.READ_COMMITTED); 270 271 try 272 { 273 status = cursor.getFirst(key, data,lockMode); 274 while(status == OperationStatus.SUCCESS) 275 { 276 count.getAndAdd(SortValuesSet.getEncodedSize(data.getData(), 0)); 277 status = cursor.getNext(key, data, lockMode); 278 } 279 } 280 finally 281 { 282 cursor.close(); 283 } 284 } 285 286 /** 287 * Close the VLV index. 288 * 289 * @throws DatabaseException if a JE database error occurs while 290 * closing the index. 291 */ 292 public void close() throws DatabaseException 293 { 294 super.close(); 295 this.config.removeChangeListener(this); 296 } 297 298 /** 299 * Update the vlvIndex for a new entry. 300 * 301 * @param txn A database transaction, or null if none is required. 302 * @param entryID The entry ID. 303 * @param entry The entry to be indexed. 304 * @return True if the entry ID for the entry are added. False if 305 * the entry ID already exists. 306 * @throws DatabaseException If an error occurs in the JE database. 307 * @throws org.opends.server.types.DirectoryException If a Directory Server 308 * error occurs. 309 * @throws JebException If an error occurs in the JE backend. 310 */ 311 public boolean addEntry(Transaction txn, EntryID entryID, Entry entry) 312 throws DatabaseException, DirectoryException, JebException 313 { 314 DN entryDN = entry.getDN(); 315 if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) 316 { 317 return insertValues(txn, entryID.longValue(), entry); 318 } 319 return false; 320 } 321 322 /** 323 * Update the vlvIndex for a new entry. 324 * 325 * @param buffer The index buffer to buffer the changes. 326 * @param entryID The entry ID. 327 * @param entry The entry to be indexed. 328 * @return True if the entry ID for the entry are added. False if 329 * the entry ID already exists. 330 * @throws DirectoryException If a Directory Server 331 * error occurs. 332 */ 333 public boolean addEntry(IndexBuffer buffer, EntryID entryID, Entry entry) 334 throws DirectoryException 335 { 336 DN entryDN = entry.getDN(); 337 if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) 338 { 339 SortValues sortValues = new SortValues(entryID, entry, sortOrder); 340 341 IndexBuffer.BufferedVLVValues bufferedValues = 342 buffer.getVLVIndex(this); 343 if(bufferedValues == null) 344 { 345 bufferedValues = new IndexBuffer.BufferedVLVValues(); 346 buffer.putBufferedVLVIndex(this, bufferedValues); 347 } 348 349 if(bufferedValues.deletedValues != null && 350 bufferedValues.deletedValues.contains(sortValues)) 351 { 352 bufferedValues.deletedValues.remove(sortValues); 353 return true; 354 } 355 356 TreeSet<SortValues> bufferedAddedValues = bufferedValues.addedValues; 357 if(bufferedAddedValues == null) 358 { 359 bufferedAddedValues = new TreeSet<SortValues>(); 360 bufferedValues.addedValues = bufferedAddedValues; 361 } 362 bufferedAddedValues.add(sortValues); 363 return true; 364 } 365 return false; 366 } 367 368 369 /** 370 * Update the vlvIndex for a deleted entry. 371 * 372 * @param txn The database transaction to be used for the deletions 373 * @param entryID The entry ID 374 * @param entry The contents of the deleted entry. 375 * @return True if the entry was successfully removed from this VLV index 376 * or False otherwise. 377 * @throws DatabaseException If an error occurs in the JE database. 378 * @throws DirectoryException If a Directory Server error occurs. 379 * @throws JebException If an error occurs in the JE backend. 380 */ 381 public boolean removeEntry(Transaction txn, EntryID entryID, Entry entry) 382 throws DatabaseException, DirectoryException, JebException 383 { 384 DN entryDN = entry.getDN(); 385 if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) 386 { 387 return removeValues(txn, entryID.longValue(), entry); 388 } 389 return false; 390 } 391 392 /** 393 * Update the vlvIndex for a deleted entry. 394 * 395 * @param buffer The database transaction to be used for the deletions 396 * @param entryID The entry ID 397 * @param entry The contents of the deleted entry. 398 * @return True if the entry was successfully removed from this VLV index 399 * or False otherwise. 400 * @throws DirectoryException If a Directory Server error occurs. 401 */ 402 public boolean removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry) 403 throws DirectoryException 404 { 405 DN entryDN = entry.getDN(); 406 if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) 407 { 408 SortValues sortValues = new SortValues(entryID, entry, sortOrder); 409 410 IndexBuffer.BufferedVLVValues bufferedValues = 411 buffer.getVLVIndex(this); 412 if(bufferedValues == null) 413 { 414 bufferedValues = new IndexBuffer.BufferedVLVValues(); 415 buffer.putBufferedVLVIndex(this, bufferedValues); 416 } 417 418 if(bufferedValues.addedValues != null && 419 bufferedValues.addedValues.contains(sortValues)) 420 { 421 bufferedValues.addedValues.remove(sortValues); 422 return true; 423 } 424 425 TreeSet<SortValues> bufferedDeletedValues = bufferedValues.deletedValues; 426 if(bufferedDeletedValues == null) 427 { 428 bufferedDeletedValues = new TreeSet<SortValues>(); 429 bufferedValues.deletedValues = bufferedDeletedValues; 430 } 431 bufferedDeletedValues.add(sortValues); 432 return true; 433 } 434 return false; 435 436 } 437 438 /** 439 * Update the vlvIndex to reflect a sequence of modifications in a Modify 440 * operation. 441 * 442 * @param txn The JE transaction to use for database updates. 443 * @param entryID The ID of the entry that was modified. 444 * @param oldEntry The entry before the modifications were applied. 445 * @param newEntry The entry after the modifications were applied. 446 * @param mods The sequence of modifications in the Modify operation. 447 * @return True if the modification was successfully processed or False 448 * otherwise. 449 * @throws JebException If an error occurs during an operation on a 450 * JE database. 451 * @throws DatabaseException If an error occurs during an operation on a 452 * JE database. 453 * @throws DirectoryException If a Directory Server error occurs. 454 */ 455 public boolean modifyEntry(Transaction txn, 456 EntryID entryID, 457 Entry oldEntry, 458 Entry newEntry, 459 List<Modification> mods) 460 throws DatabaseException, DirectoryException, JebException 461 { 462 DN oldEntryDN = oldEntry.getDN(); 463 DN newEntryDN = newEntry.getDN(); 464 if(oldEntryDN.matchesBaseAndScope(baseDN, scope) && 465 filter.matchesEntry(oldEntry)) 466 { 467 if(newEntryDN.matchesBaseAndScope(baseDN, scope) && 468 filter.matchesEntry(newEntry)) 469 { 470 // The entry should still be indexed. See if any sorted attributes are 471 // changed. 472 boolean sortAttributeModified = false; 473 SortKey[] sortKeys = sortOrder.getSortKeys(); 474 for(SortKey sortKey : sortKeys) 475 { 476 AttributeType attributeType = sortKey.getAttributeType(); 477 Iterable<AttributeType> subTypes = 478 DirectoryServer.getSchema().getSubTypes(attributeType); 479 for(Modification mod : mods) 480 { 481 AttributeType modAttrType = mod.getAttribute().getAttributeType(); 482 if(modAttrType.equals(attributeType)) 483 { 484 sortAttributeModified = true; 485 break; 486 } 487 for(AttributeType subType : subTypes) 488 { 489 if(modAttrType.equals(subType)) 490 { 491 sortAttributeModified = true; 492 break; 493 } 494 } 495 } 496 if(sortAttributeModified) 497 { 498 break; 499 } 500 } 501 if(sortAttributeModified) 502 { 503 boolean success; 504 // Sorted attributes have changed. Reindex the entry; 505 success = removeValues(txn, entryID.longValue(), oldEntry); 506 success &= insertValues(txn, entryID.longValue(), newEntry); 507 return success; 508 } 509 } 510 else 511 { 512 // The modifications caused the new entry to be unindexed. Remove from 513 // vlvIndex. 514 return removeValues(txn, entryID.longValue(), oldEntry); 515 } 516 } 517 else 518 { 519 if(newEntryDN.matchesBaseAndScope(baseDN, scope) && 520 filter.matchesEntry(newEntry)) 521 { 522 // The modifications caused the new entry to be indexed. Add to 523 // vlvIndex. 524 return insertValues(txn, entryID.longValue(), newEntry); 525 } 526 } 527 528 // The modifications does not affect this vlvIndex 529 return true; 530 } 531 532 /** 533 * Update the vlvIndex to reflect a sequence of modifications in a Modify 534 * operation. 535 * 536 * @param buffer The database transaction to be used for the deletions 537 * @param entryID The ID of the entry that was modified. 538 * @param oldEntry The entry before the modifications were applied. 539 * @param newEntry The entry after the modifications were applied. 540 * @param mods The sequence of modifications in the Modify operation. 541 * @return True if the modification was successfully processed or False 542 * otherwise. 543 * @throws JebException If an error occurs during an operation on a 544 * JE database. 545 * @throws DatabaseException If an error occurs during an operation on a 546 * JE database. 547 * @throws DirectoryException If a Directory Server error occurs. 548 */ 549 public boolean modifyEntry(IndexBuffer buffer, 550 EntryID entryID, 551 Entry oldEntry, 552 Entry newEntry, 553 List<Modification> mods) 554 throws DatabaseException, DirectoryException, JebException 555 { 556 DN oldEntryDN = oldEntry.getDN(); 557 DN newEntryDN = newEntry.getDN(); 558 if(oldEntryDN.matchesBaseAndScope(baseDN, scope) && 559 filter.matchesEntry(oldEntry)) 560 { 561 if(newEntryDN.matchesBaseAndScope(baseDN, scope) && 562 filter.matchesEntry(newEntry)) 563 { 564 // The entry should still be indexed. See if any sorted attributes are 565 // changed. 566 boolean sortAttributeModified = false; 567 SortKey[] sortKeys = sortOrder.getSortKeys(); 568 for(SortKey sortKey : sortKeys) 569 { 570 AttributeType attributeType = sortKey.getAttributeType(); 571 Iterable<AttributeType> subTypes = 572 DirectoryServer.getSchema().getSubTypes(attributeType); 573 for(Modification mod : mods) 574 { 575 AttributeType modAttrType = mod.getAttribute().getAttributeType(); 576 if(modAttrType.equals(attributeType)) 577 { 578 sortAttributeModified = true; 579 break; 580 } 581 for(AttributeType subType : subTypes) 582 { 583 if(modAttrType.equals(subType)) 584 { 585 sortAttributeModified = true; 586 break; 587 } 588 } 589 } 590 if(sortAttributeModified) 591 { 592 break; 593 } 594 } 595 if(sortAttributeModified) 596 { 597 boolean success; 598 // Sorted attributes have changed. Reindex the entry; 599 success = removeEntry(buffer, entryID, oldEntry); 600 success &= addEntry(buffer, entryID, newEntry); 601 return success; 602 } 603 } 604 else 605 { 606 // The modifications caused the new entry to be unindexed. Remove from 607 // vlvIndex. 608 return removeEntry(buffer, entryID, oldEntry); 609 } 610 } 611 else 612 { 613 if(newEntryDN.matchesBaseAndScope(baseDN, scope) && 614 filter.matchesEntry(newEntry)) 615 { 616 // The modifications caused the new entry to be indexed. Add to 617 // vlvIndex. 618 return addEntry(buffer, entryID, newEntry); 619 } 620 } 621 622 // The modifications does not affect this vlvIndex 623 return true; 624 } 625 626 /** 627 * Put a sort values set in this VLV index. 628 * 629 * @param txn The transaction to use when retriving the set or NULL if it is 630 * not required. 631 * @param sortValuesSet The SortValuesSet to put. 632 * @return True if the sortValuesSet was put successfully or False otherwise. 633 * @throws JebException If an error occurs during an operation on a 634 * JE database. 635 * @throws DatabaseException If an error occurs during an operation on a 636 * JE database. 637 * @throws DirectoryException If a Directory Server error occurs. 638 */ 639 public boolean putSortValuesSet(Transaction txn, SortValuesSet sortValuesSet) 640 throws JebException, DatabaseException, DirectoryException 641 { 642 DatabaseEntry key = new DatabaseEntry(); 643 DatabaseEntry data = new DatabaseEntry(); 644 645 byte[] after = sortValuesSet.toDatabase(); 646 key.setData(sortValuesSet.getKeyBytes()); 647 data.setData(after); 648 return put(txn, key, data) == OperationStatus.SUCCESS; 649 } 650 651 /** 652 * Get a sorted values set that should contain the entry with the given 653 * information. 654 * 655 * @param txn The transaction to use when retriving the set or NULL if it is 656 * not required. 657 * @param entryID The entry ID to use. 658 * @param values The values to use. 659 * @return The SortValuesSet that should contain the entry with the given 660 * information. 661 * @throws DatabaseException If an error occurs during an operation on a 662 * JE database. 663 * @throws DirectoryException If a Directory Server error occurs. 664 */ 665 public SortValuesSet getSortValuesSet(Transaction txn, long entryID, 666 AttributeValue[] values) 667 throws DatabaseException, DirectoryException 668 { 669 SortValuesSet sortValuesSet = null; 670 DatabaseEntry key = new DatabaseEntry(); 671 OperationStatus status; 672 LockMode lockMode = LockMode.DEFAULT; 673 DatabaseEntry data = new DatabaseEntry(); 674 675 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 676 677 try 678 { 679 key.setData(encodeKey(entryID, values)); 680 status = cursor.getSearchKeyRange(key, data,lockMode); 681 682 if(status != OperationStatus.SUCCESS) 683 { 684 // There are no records in the database 685 if(debugEnabled()) 686 { 687 TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " + 688 "Creating unbound set.", config.getName()); 689 } 690 sortValuesSet = new SortValuesSet(this); 691 } 692 else 693 { 694 if(debugEnabled()) 695 { 696 StringBuilder searchKeyHex = new StringBuilder(); 697 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4); 698 StringBuilder foundKeyHex = new StringBuilder(); 699 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4); 700 TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " + 701 "%s\nSearch Key:%s\nFound Key:%s\n", 702 config.getName(), 703 searchKeyHex, 704 foundKeyHex); 705 } 706 sortValuesSet = new SortValuesSet(key.getData(), data.getData(), 707 this); 708 } 709 } 710 finally 711 { 712 cursor.close(); 713 } 714 715 return sortValuesSet; 716 } 717 718 /** 719 * Search for entries matching the entry ID and attribute values and 720 * return its entry ID. 721 * 722 * @param txn The JE transaction to use for database updates. 723 * @param entryID The entry ID to search for. 724 * @param values The values to search for. 725 * @return The index of the entry ID matching the values or -1 if its not 726 * found. 727 * @throws DatabaseException If an error occurs during an operation on a 728 * JE database. 729 * @throws JebException If an error occurs during an operation on a 730 * JE database. 731 * @throws DirectoryException If a Directory Server error occurs. 732 */ 733 public boolean containsValues(Transaction txn, long entryID, 734 AttributeValue[] values) 735 throws JebException, DatabaseException, DirectoryException 736 { 737 SortValuesSet valuesSet = getSortValuesSet(txn, entryID, values); 738 int pos = valuesSet.binarySearch(entryID, values); 739 if(pos < 0) 740 { 741 return false; 742 } 743 return true; 744 } 745 746 private boolean insertValues(Transaction txn, long entryID, Entry entry) 747 throws JebException, DatabaseException, DirectoryException 748 { 749 SortValuesSet sortValuesSet; 750 AttributeValue[] values = getSortValues(entry); 751 DatabaseEntry key = new DatabaseEntry(); 752 OperationStatus status; 753 LockMode lockMode = LockMode.RMW; 754 DatabaseEntry data = new DatabaseEntry(); 755 boolean success = true; 756 757 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 758 759 try 760 { 761 key.setData(encodeKey(entryID, values)); 762 status = cursor.getSearchKeyRange(key, data,lockMode); 763 } 764 finally 765 { 766 cursor.close(); 767 } 768 769 if(status != OperationStatus.SUCCESS) 770 { 771 // There are no records in the database 772 if(debugEnabled()) 773 { 774 TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " + 775 "Creating unbound set.", config.getName()); 776 } 777 sortValuesSet = new SortValuesSet(this); 778 key.setData(new byte[0]); 779 } 780 else 781 { 782 if(debugEnabled()) 783 { 784 StringBuilder searchKeyHex = new StringBuilder(); 785 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4); 786 StringBuilder foundKeyHex = new StringBuilder(); 787 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4); 788 TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " + 789 "%s\nSearch Key:%s\nFound Key:%s\n", 790 config.getName(), 791 searchKeyHex, 792 foundKeyHex); 793 } 794 sortValuesSet = new SortValuesSet(key.getData(), data.getData(), 795 this); 796 } 797 798 799 800 801 success = sortValuesSet.add(entryID, values); 802 803 int newSize = sortValuesSet.size(); 804 if(newSize >= sortedSetCapacity) 805 { 806 SortValuesSet splitSortValuesSet = sortValuesSet.split(newSize / 2); 807 byte[] splitAfter = splitSortValuesSet.toDatabase(); 808 key.setData(splitSortValuesSet.getKeyBytes()); 809 data.setData(splitAfter); 810 put(txn, key, data); 811 byte[] after = sortValuesSet.toDatabase(); 812 key.setData(sortValuesSet.getKeyBytes()); 813 data.setData(after); 814 put(txn, key, data); 815 816 if(debugEnabled()) 817 { 818 TRACER.debugInfo("SortValuesSet with key %s has reached" + 819 " the entry size of %d. Spliting into two sets with " + 820 " keys %s and %s.", splitSortValuesSet.getKeySortValues(), 821 newSize, sortValuesSet.getKeySortValues(), 822 splitSortValuesSet.getKeySortValues()); 823 } 824 } 825 else 826 { 827 byte[] after = sortValuesSet.toDatabase(); 828 data.setData(after); 829 put(txn, key, data); 830 // TODO: What about phantoms? 831 } 832 833 if(success) 834 { 835 count.getAndIncrement(); 836 } 837 838 return success; 839 } 840 841 private boolean removeValues(Transaction txn, long entryID, Entry entry) 842 throws JebException, DatabaseException, DirectoryException 843 { 844 SortValuesSet sortValuesSet; 845 AttributeValue[] values = getSortValues(entry); 846 DatabaseEntry key = new DatabaseEntry(); 847 OperationStatus status; 848 LockMode lockMode = LockMode.RMW; 849 DatabaseEntry data = new DatabaseEntry(); 850 851 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 852 853 try 854 { 855 key.setData(encodeKey(entryID, values)); 856 status = cursor.getSearchKeyRange(key, data,lockMode); 857 } 858 finally 859 { 860 cursor.close(); 861 } 862 863 if(status == OperationStatus.SUCCESS) 864 { 865 if(debugEnabled()) 866 { 867 StringBuilder searchKeyHex = new StringBuilder(); 868 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4); 869 StringBuilder foundKeyHex = new StringBuilder(); 870 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4); 871 TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " + 872 "%s\nSearch Key:%s\nFound Key:%s\n", 873 config.getName(), 874 searchKeyHex, 875 foundKeyHex); 876 } 877 sortValuesSet = new SortValuesSet(key.getData(), data.getData(), 878 this); 879 boolean success = sortValuesSet.remove(entryID, values); 880 byte[] after = sortValuesSet.toDatabase(); 881 882 if(after == null) 883 { 884 delete(txn, key); 885 } 886 else 887 { 888 data.setData(after); 889 put(txn, key, data); 890 } 891 892 if(success) 893 { 894 count.getAndDecrement(); 895 } 896 897 return success; 898 } 899 else 900 { 901 return false; 902 } 903 } 904 905 /** 906 * Update the vlvIndex with the specified values to add and delete. 907 * 908 * @param txn A database transaction, or null if none is required. 909 * @param addedValues The values to add to the VLV index. 910 * @param deletedValues The values to delete from the VLV index. 911 * @throws DatabaseException If an error occurs in the JE database. 912 * @throws DirectoryException If a Directory Server 913 * error occurs. 914 * @throws JebException If an error occurs in the JE backend. 915 */ 916 public void updateIndex(Transaction txn, 917 TreeSet<SortValues> addedValues, 918 TreeSet<SortValues> deletedValues) 919 throws DirectoryException, DatabaseException, JebException 920 { 921 // Handle cases where nothing is changed early to avoid 922 // DB access. 923 if((addedValues == null || addedValues.size() == 0) && 924 (deletedValues == null || deletedValues.size() == 0)) 925 { 926 return; 927 } 928 929 DatabaseEntry key = new DatabaseEntry(); 930 OperationStatus status; 931 LockMode lockMode = LockMode.RMW; 932 DatabaseEntry data = new DatabaseEntry(); 933 SortValuesSet sortValuesSet; 934 Iterator<SortValues> aValues = null; 935 Iterator<SortValues> dValues = null; 936 SortValues av = null; 937 SortValues dv = null; 938 939 if(addedValues != null) 940 { 941 aValues = addedValues.iterator(); 942 av = aValues.next(); 943 } 944 if(deletedValues != null) 945 { 946 dValues = deletedValues.iterator(); 947 dv = dValues.next(); 948 } 949 950 while(true) 951 { 952 if(av != null) 953 { 954 if(dv != null) 955 { 956 // Start from the smallest values from either set. 957 if(av.compareTo(dv) < 0) 958 { 959 key.setData(encodeKey(av.getEntryID(), av.getValues())); 960 } 961 else 962 { 963 key.setData(encodeKey(dv.getEntryID(), dv.getValues())); 964 } 965 } 966 else 967 { 968 key.setData(encodeKey(av.getEntryID(), av.getValues())); 969 } 970 } 971 else if(dv != null) 972 { 973 key.setData(encodeKey(dv.getEntryID(), dv.getValues())); 974 } 975 else 976 { 977 break; 978 } 979 980 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 981 982 try 983 { 984 status = cursor.getSearchKeyRange(key, data,lockMode); 985 } 986 finally 987 { 988 cursor.close(); 989 } 990 991 if(status != OperationStatus.SUCCESS) 992 { 993 // There are no records in the database 994 if(debugEnabled()) 995 { 996 TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " + 997 "Creating unbound set.", config.getName()); 998 } 999 sortValuesSet = new SortValuesSet(this); 1000 key.setData(new byte[0]); 1001 } 1002 else 1003 { 1004 if(debugEnabled()) 1005 { 1006 StringBuilder searchKeyHex = new StringBuilder(); 1007 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4); 1008 StringBuilder foundKeyHex = new StringBuilder(); 1009 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4); 1010 TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " + 1011 "%s\nSearch Key:%s\nFound Key:%s\n", 1012 config.getName(), 1013 searchKeyHex, 1014 foundKeyHex); 1015 } 1016 sortValuesSet = new SortValuesSet(key.getData(), data.getData(), 1017 this); 1018 } 1019 1020 int oldSize = sortValuesSet.size(); 1021 if(key.getData().length == 0) 1022 { 1023 // This is the last unbounded set. 1024 while(av != null) 1025 { 1026 sortValuesSet.add(av.getEntryID(), av.getValues()); 1027 aValues.remove(); 1028 if(aValues.hasNext()) 1029 { 1030 av = aValues.next(); 1031 } 1032 else 1033 { 1034 av = null; 1035 } 1036 } 1037 1038 while(dv != null) 1039 { 1040 sortValuesSet.remove(dv.getEntryID(), dv.getValues()); 1041 dValues.remove(); 1042 if(dValues.hasNext()) 1043 { 1044 dv = dValues.next(); 1045 } 1046 else 1047 { 1048 dv = null; 1049 } 1050 } 1051 } 1052 else 1053 { 1054 SortValues maxValues = decodeKey(sortValuesSet.getKeyBytes()); 1055 1056 while(av != null && av.compareTo(maxValues) <= 0) 1057 { 1058 sortValuesSet.add(av.getEntryID(), av.getValues()); 1059 aValues.remove(); 1060 if(aValues.hasNext()) 1061 { 1062 av = aValues.next(); 1063 } 1064 else 1065 { 1066 av = null; 1067 } 1068 } 1069 1070 while(dv != null && dv.compareTo(maxValues) <= 0) 1071 { 1072 sortValuesSet.remove(dv.getEntryID(), dv.getValues()); 1073 dValues.remove(); 1074 if(dValues.hasNext()) 1075 { 1076 dv = dValues.next(); 1077 } 1078 else 1079 { 1080 dv = null; 1081 } 1082 } 1083 } 1084 1085 int newSize = sortValuesSet.size(); 1086 if(newSize >= sortedSetCapacity) 1087 { 1088 SortValuesSet splitSortValuesSet = sortValuesSet.split(newSize / 2); 1089 byte[] splitAfter = splitSortValuesSet.toDatabase(); 1090 key.setData(splitSortValuesSet.getKeyBytes()); 1091 data.setData(splitAfter); 1092 put(txn, key, data); 1093 byte[] after = sortValuesSet.toDatabase(); 1094 key.setData(sortValuesSet.getKeyBytes()); 1095 data.setData(after); 1096 put(txn, key, data); 1097 1098 if(debugEnabled()) 1099 { 1100 TRACER.debugInfo("SortValuesSet with key %s has reached" + 1101 " the entry size of %d. Spliting into two sets with " + 1102 " keys %s and %s.", splitSortValuesSet.getKeySortValues(), 1103 newSize, sortValuesSet.getKeySortValues(), 1104 splitSortValuesSet.getKeySortValues()); 1105 } 1106 } 1107 else if(newSize == 0) 1108 { 1109 delete(txn, key); 1110 } 1111 else 1112 { 1113 byte[] after = sortValuesSet.toDatabase(); 1114 data.setData(after); 1115 put(txn, key, data); 1116 } 1117 1118 count.getAndAdd(newSize - oldSize); 1119 } 1120 } 1121 1122 /** 1123 * Evaluate a search with sort control using this VLV index. 1124 * 1125 * @param txn The transaction to used when reading the index or NULL if it is 1126 * not required. 1127 * @param searchOperation The search operation to evaluate. 1128 * @param sortControl The sort request control to evaluate. 1129 * @param vlvRequest The VLV request control to evaluate or NULL if VLV is not 1130 * requested. 1131 * @param debugBuilder If not null, a diagnostic string will be written 1132 * which will help determine how this index contributed 1133 * to this search. 1134 * @return The sorted EntryIDSet containing the entry IDs that match the 1135 * search criteria. 1136 * @throws DirectoryException If a Directory Server error occurs. 1137 * @throws DatabaseException If an error occurs in the JE database. 1138 * @throws JebException If an error occurs in the JE database. 1139 */ 1140 public EntryIDSet evaluate(Transaction txn, 1141 SearchOperation searchOperation, 1142 ServerSideSortRequestControl sortControl, 1143 VLVRequestControl vlvRequest, 1144 StringBuilder debugBuilder) 1145 throws DirectoryException, DatabaseException, JebException 1146 { 1147 if(!trusted || rebuildRunning) 1148 { 1149 return null; 1150 } 1151 if(!searchOperation.getBaseDN().equals(baseDN)) 1152 { 1153 return null; 1154 } 1155 if(!searchOperation.getScope().equals(scope)) 1156 { 1157 return null; 1158 } 1159 if(!searchOperation.getFilter().equals(filter)) 1160 { 1161 return null; 1162 } 1163 if(!sortControl.getSortOrder().equals(this.sortOrder)) 1164 { 1165 return null; 1166 } 1167 1168 if (debugBuilder != null) 1169 { 1170 debugBuilder.append("vlv="); 1171 debugBuilder.append("[INDEX:"); 1172 debugBuilder.append(name.replace(entryContainer.getDatabasePrefix() + "_", 1173 "")); 1174 debugBuilder.append("]"); 1175 } 1176 1177 long[] selectedIDs = new long[0]; 1178 if(vlvRequest != null) 1179 { 1180 int currentCount = count.get(); 1181 int beforeCount = vlvRequest.getBeforeCount(); 1182 int afterCount = vlvRequest.getAfterCount(); 1183 1184 if (vlvRequest.getTargetType() == VLVRequestControl.TYPE_TARGET_BYOFFSET) 1185 { 1186 int targetOffset = vlvRequest.getOffset(); 1187 if (targetOffset < 0) 1188 { 1189 // The client specified a negative target offset. This should never 1190 // be allowed. 1191 searchOperation.addResponseControl( 1192 new VLVResponseControl(targetOffset, currentCount, 1193 LDAPResultCode.OFFSET_RANGE_ERROR)); 1194 1195 Message message = ERR_ENTRYIDSORTER_NEGATIVE_START_POS.get(); 1196 throw new DirectoryException(ResultCode.VIRTUAL_LIST_VIEW_ERROR, 1197 message); 1198 } 1199 else if (targetOffset == 0) 1200 { 1201 // This is an easy mistake to make, since VLV offsets start at 1 1202 // instead of 0. We'll assume the client meant to use 1. 1203 targetOffset = 1; 1204 } 1205 int listOffset = targetOffset - 1; // VLV offsets start at 1, not 0. 1206 int startPos = listOffset - beforeCount; 1207 if (startPos < 0) 1208 { 1209 // This can happen if beforeCount >= offset, and in this case we'll 1210 // just adjust the start position to ignore the range of beforeCount 1211 // that doesn't exist. 1212 startPos = 0; 1213 beforeCount = listOffset; 1214 } 1215 else if(startPos >= currentCount) 1216 { 1217 // The start position is beyond the end of the list. In this case, 1218 // we'll assume that the start position was one greater than the 1219 // size of the list and will only return the beforeCount entries. 1220 // The start position is beyond the end of the list. In this case, 1221 // we'll assume that the start position was one greater than the 1222 // size of the list and will only return the beforeCount entries. 1223 targetOffset = currentCount + 1; 1224 listOffset = currentCount; 1225 startPos = listOffset - beforeCount; 1226 afterCount = 0; 1227 } 1228 1229 int count = 1 + beforeCount + afterCount; 1230 selectedIDs = new long[count]; 1231 1232 DatabaseEntry key = new DatabaseEntry(); 1233 OperationStatus status; 1234 LockMode lockMode = LockMode.DEFAULT; 1235 DatabaseEntry data = new DatabaseEntry(); 1236 1237 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 1238 1239 try 1240 { 1241 //Locate the set that contains the target entry. 1242 int cursorCount = 0; 1243 int selectedPos = 0; 1244 status = cursor.getFirst(key, data,lockMode); 1245 while(status == OperationStatus.SUCCESS) 1246 { 1247 if(debugEnabled()) 1248 { 1249 StringBuilder searchKeyHex = new StringBuilder(); 1250 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 1251 4); 1252 StringBuilder foundKeyHex = new StringBuilder(); 1253 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 1254 4); 1255 TRACER.debugVerbose("Retrieved a sort values set in VLV " + 1256 "vlvIndex %s\nSearch Key:%s\nFound Key:%s\n", 1257 config.getName(), 1258 searchKeyHex, 1259 foundKeyHex); 1260 } 1261 long[] IDs = SortValuesSet.getEncodedIDs(data.getData(), 0); 1262 for(int i = startPos + selectedPos - cursorCount; 1263 i < IDs.length && selectedPos < count; 1264 i++, selectedPos++) 1265 { 1266 selectedIDs[selectedPos] = IDs[i]; 1267 } 1268 cursorCount += IDs.length; 1269 status = cursor.getNext(key, data,lockMode); 1270 } 1271 1272 if (selectedPos < count) 1273 { 1274 // We don't have enough entries in the set to meet the requested 1275 // page size, so we'll need to shorten the array. 1276 long[] newIDArray = new long[selectedPos]; 1277 System.arraycopy(selectedIDs, 0, newIDArray, 0, selectedPos); 1278 selectedIDs = newIDArray; 1279 } 1280 1281 searchOperation.addResponseControl( 1282 new VLVResponseControl(targetOffset, currentCount, 1283 LDAPResultCode.SUCCESS)); 1284 1285 if(debugBuilder != null) 1286 { 1287 debugBuilder.append("[COUNT:"); 1288 debugBuilder.append(cursorCount); 1289 debugBuilder.append("]"); 1290 } 1291 } 1292 finally 1293 { 1294 cursor.close(); 1295 } 1296 } 1297 else 1298 { 1299 int targetOffset = 0; 1300 int includedBeforeCount = 0; 1301 int includedAfterCount = 0; 1302 LinkedList<EntryID> idList = new LinkedList<EntryID>(); 1303 DatabaseEntry key = new DatabaseEntry(); 1304 OperationStatus status; 1305 LockMode lockMode = LockMode.DEFAULT; 1306 DatabaseEntry data = new DatabaseEntry(); 1307 1308 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 1309 1310 try 1311 { 1312 byte[] vBytes = vlvRequest.getGreaterThanOrEqualAssertion().value(); 1313 byte[] vLength = ASN1Element.encodeLength(vBytes.length); 1314 byte[] keyBytes = new byte[vBytes.length + vLength.length]; 1315 System.arraycopy(vLength, 0, keyBytes, 0, vLength.length); 1316 System.arraycopy(vBytes, 0, keyBytes, vLength.length, vBytes.length); 1317 1318 key.setData(keyBytes); 1319 status = cursor.getSearchKeyRange(key, data, lockMode); 1320 if(status == OperationStatus.SUCCESS) 1321 { 1322 if(debugEnabled()) 1323 { 1324 StringBuilder searchKeyHex = new StringBuilder(); 1325 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 1326 4); 1327 StringBuilder foundKeyHex = new StringBuilder(); 1328 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 1329 4); 1330 TRACER.debugVerbose("Retrieved a sort values set in VLV " + 1331 "vlvIndex %s\nSearch Key:%s\nFound Key:%s\n", 1332 config.getName(), 1333 searchKeyHex, 1334 foundKeyHex); 1335 } 1336 SortValuesSet sortValuesSet = 1337 new SortValuesSet(key.getData(), data.getData(), this); 1338 AttributeValue[] assertionValue = new AttributeValue[1]; 1339 assertionValue[0] = 1340 new AttributeValue( 1341 sortOrder.getSortKeys()[0].getAttributeType(), 1342 vlvRequest.getGreaterThanOrEqualAssertion()); 1343 1344 int adjustedTargetOffset = 1345 sortValuesSet.binarySearch(-1, assertionValue); 1346 if(adjustedTargetOffset < 0) 1347 { 1348 // For a negative return value r, the vlvIndex -(r+1) gives the 1349 // array index of the ID that is greater then the assertion value. 1350 adjustedTargetOffset = -(adjustedTargetOffset+1); 1351 } 1352 1353 targetOffset = adjustedTargetOffset; 1354 1355 // Iterate through all the sort values sets before this one to find 1356 // the target offset in the index. 1357 int lastOffset = adjustedTargetOffset - 1; 1358 long[] lastIDs = sortValuesSet.getEntryIDs(); 1359 while(true) 1360 { 1361 for(int i = lastOffset; 1362 i >= 0 && includedBeforeCount < beforeCount; i--) 1363 { 1364 idList.addFirst(new EntryID(lastIDs[i])); 1365 includedBeforeCount++; 1366 } 1367 1368 status = cursor.getPrev(key, data, lockMode); 1369 1370 if(status != OperationStatus.SUCCESS) 1371 { 1372 break; 1373 } 1374 1375 if(includedBeforeCount < beforeCount) 1376 { 1377 lastIDs = 1378 SortValuesSet.getEncodedIDs(data.getData(), 0); 1379 lastOffset = lastIDs.length - 1; 1380 targetOffset += lastIDs.length; 1381 } 1382 else 1383 { 1384 targetOffset += SortValuesSet.getEncodedSize(data.getData(), 0); 1385 } 1386 } 1387 1388 1389 // Set the cursor back to the position of the target entry set 1390 key.setData(sortValuesSet.getKeyBytes()); 1391 cursor.getSearchKey(key, data, lockMode); 1392 1393 // Add the target and after count entries if the target was found. 1394 lastOffset = adjustedTargetOffset; 1395 lastIDs = sortValuesSet.getEntryIDs(); 1396 int afterIDCount = 0; 1397 while(true) 1398 { 1399 for(int i = lastOffset; 1400 i < lastIDs.length && includedAfterCount < afterCount + 1; 1401 i++) 1402 { 1403 idList.addLast(new EntryID(lastIDs[i])); 1404 includedAfterCount++; 1405 } 1406 1407 if(includedAfterCount >= afterCount + 1) 1408 { 1409 break; 1410 } 1411 1412 status = cursor.getNext(key, data, lockMode); 1413 1414 if(status != OperationStatus.SUCCESS) 1415 { 1416 break; 1417 } 1418 1419 lastIDs = 1420 SortValuesSet.getEncodedIDs(data.getData(), 0); 1421 lastOffset = 0; 1422 afterIDCount += lastIDs.length; 1423 } 1424 1425 selectedIDs = new long[idList.size()]; 1426 Iterator<EntryID> idIterator = idList.iterator(); 1427 for (int i=0; i < selectedIDs.length; i++) 1428 { 1429 selectedIDs[i] = idIterator.next().longValue(); 1430 } 1431 1432 searchOperation.addResponseControl( 1433 new VLVResponseControl(targetOffset + 1, currentCount, 1434 LDAPResultCode.SUCCESS)); 1435 1436 if(debugBuilder != null) 1437 { 1438 debugBuilder.append("[COUNT:"); 1439 debugBuilder.append(targetOffset + afterIDCount + 1); 1440 debugBuilder.append("]"); 1441 } 1442 } 1443 } 1444 finally 1445 { 1446 cursor.close(); 1447 } 1448 } 1449 } 1450 else 1451 { 1452 LinkedList<long[]> idSets = new LinkedList<long[]>(); 1453 int currentCount = 0; 1454 DatabaseEntry key = new DatabaseEntry(); 1455 OperationStatus status; 1456 LockMode lockMode = LockMode.RMW; 1457 DatabaseEntry data = new DatabaseEntry(); 1458 1459 Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); 1460 1461 try 1462 { 1463 status = cursor.getFirst(key, data, lockMode); 1464 while(status == OperationStatus.SUCCESS) 1465 { 1466 if(debugEnabled()) 1467 { 1468 StringBuilder searchKeyHex = new StringBuilder(); 1469 StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4); 1470 StringBuilder foundKeyHex = new StringBuilder(); 1471 StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4); 1472 TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " + 1473 "%s\nSearch Key:%s\nFound Key:%s\n", 1474 config.getName(), 1475 searchKeyHex, 1476 foundKeyHex); 1477 } 1478 long[] ids = SortValuesSet.getEncodedIDs(data.getData(), 0); 1479 idSets.add(ids); 1480 currentCount += ids.length; 1481 status = cursor.getNext(key, data, lockMode); 1482 } 1483 } 1484 finally 1485 { 1486 cursor.close(); 1487 } 1488 1489 selectedIDs = new long[currentCount]; 1490 int pos = 0; 1491 for(long[] id : idSets) 1492 { 1493 System.arraycopy(id, 0, selectedIDs, pos, id.length); 1494 pos += id.length; 1495 } 1496 1497 if(debugBuilder != null) 1498 { 1499 debugBuilder.append("[COUNT:"); 1500 debugBuilder.append(currentCount); 1501 debugBuilder.append("]"); 1502 } 1503 } 1504 return new EntryIDSet(selectedIDs, 0, selectedIDs.length); 1505 } 1506 1507 /** 1508 * Set the vlvIndex trust state. 1509 * @param txn A database transaction, or null if none is required. 1510 * @param trusted True if this vlvIndex should be trusted or false 1511 * otherwise. 1512 * @throws DatabaseException If an error occurs in the JE database. 1513 */ 1514 public synchronized void setTrusted(Transaction txn, boolean trusted) 1515 throws DatabaseException 1516 { 1517 this.trusted = trusted; 1518 state.putIndexTrustState(txn, this, trusted); 1519 } 1520 1521 /** 1522 * Set the rebuild status of this vlvIndex. 1523 * @param rebuildRunning True if a rebuild process on this vlvIndex 1524 * is running or False otherwise. 1525 */ 1526 public synchronized void setRebuildStatus(boolean rebuildRunning) 1527 { 1528 this.rebuildRunning = rebuildRunning; 1529 } 1530 1531 /** 1532 * Gets the values to sort on from the entry. 1533 * 1534 * @param entry The entry to get the values from. 1535 * @return The attribute values to sort on. 1536 */ 1537 AttributeValue[] getSortValues(Entry entry) 1538 { 1539 SortKey[] sortKeys = sortOrder.getSortKeys(); 1540 AttributeValue[] values = new AttributeValue[sortKeys.length]; 1541 for (int i=0; i < sortKeys.length; i++) 1542 { 1543 SortKey sortKey = sortKeys[i]; 1544 AttributeType attrType = sortKey.getAttributeType(); 1545 List<Attribute> attrList = entry.getAttribute(attrType); 1546 if (attrList != null) 1547 { 1548 AttributeValue sortValue = null; 1549 1550 // There may be multiple versions of this attribute in the target entry 1551 // (e.g., with different sets of options), and it may also be a 1552 // multivalued attribute. In that case, we need to find the value that 1553 // is the best match for the corresponding sort key (i.e., for sorting 1554 // in ascending order, we want to find the lowest value; for sorting in 1555 // descending order, we want to find the highest value). This is 1556 // handled by the SortKey.compareValues method. 1557 for (Attribute a : attrList) 1558 { 1559 for (AttributeValue v : a.getValues()) 1560 { 1561 if (sortValue == null) 1562 { 1563 sortValue = v; 1564 } 1565 else if (sortKey.compareValues(v, sortValue) < 0) 1566 { 1567 sortValue = v; 1568 } 1569 } 1570 } 1571 1572 values[i] = sortValue; 1573 } 1574 } 1575 return values; 1576 } 1577 1578 /** 1579 * Encode a VLV database key with the given information. 1580 * 1581 * @param entryID The entry ID to encode. 1582 * @param values The values to encode. 1583 * @return The encoded bytes. 1584 * @throws DirectoryException If a Directory Server error occurs. 1585 */ 1586 byte[] encodeKey(long entryID, AttributeValue[] values) 1587 throws DirectoryException 1588 { 1589 int totalValueBytes = 0; 1590 LinkedList<byte[]> valueBytes = new LinkedList<byte[]>(); 1591 for (AttributeValue v : values) 1592 { 1593 byte[] vBytes; 1594 if(v == null) 1595 { 1596 vBytes = new byte[0]; 1597 } 1598 else 1599 { 1600 vBytes = v.getNormalizedValueBytes(); 1601 } 1602 byte[] vLength = ASN1Element.encodeLength(vBytes.length); 1603 valueBytes.add(vLength); 1604 valueBytes.add(vBytes); 1605 totalValueBytes += vLength.length + vBytes.length; 1606 } 1607 1608 byte[] entryIDBytes = 1609 JebFormat.entryIDToDatabase(entryID); 1610 byte[] attrBytes = new byte[entryIDBytes.length + totalValueBytes]; 1611 1612 int pos = 0; 1613 for (byte[] b : valueBytes) 1614 { 1615 System.arraycopy(b, 0, attrBytes, pos, b.length); 1616 pos += b.length; 1617 } 1618 1619 System.arraycopy(entryIDBytes, 0, attrBytes, pos, entryIDBytes.length); 1620 1621 return attrBytes; 1622 } 1623 1624 /** 1625 * Decode a VLV database key. 1626 * 1627 * @param keyBytes The byte array to decode. 1628 * @return The sort values represented by the key bytes. 1629 * @throws DirectoryException If a Directory Server error occurs. 1630 */ 1631 SortValues decodeKey(byte[] keyBytes) 1632 throws DirectoryException 1633 { 1634 if(keyBytes == null || keyBytes.length == 0) 1635 { 1636 return null; 1637 } 1638 1639 AttributeValue[] attributeValues = 1640 new AttributeValue[sortOrder.getSortKeys().length]; 1641 int vBytesPos = 0; 1642 1643 for(int i = 0; i < attributeValues.length; i++) 1644 { 1645 int valueLength = keyBytes[vBytesPos] & 0x7F; 1646 if (valueLength != keyBytes[vBytesPos++]) 1647 { 1648 int valueLengthBytes = valueLength; 1649 valueLength = 0; 1650 for (int j=0; j < valueLengthBytes; j++, vBytesPos++) 1651 { 1652 valueLength = (valueLength << 8) | (keyBytes[vBytesPos] & 0xFF); 1653 } 1654 } 1655 1656 if(valueLength == 0) 1657 { 1658 attributeValues[i] = null; 1659 } 1660 else 1661 { 1662 byte[] valueBytes = new byte[valueLength]; 1663 System.arraycopy(keyBytes, vBytesPos, valueBytes, 0, valueLength); 1664 attributeValues[i] = 1665 new AttributeValue(sortOrder.getSortKeys()[i].getAttributeType(), 1666 new ASN1OctetString(valueBytes)); 1667 } 1668 1669 vBytesPos += valueLength; 1670 } 1671 1672 // FIXME: Should pos+offset method for decoding IDs be added to 1673 // JebFormat? 1674 long v = 0; 1675 for (int i = vBytesPos; i < keyBytes.length; i++) 1676 { 1677 v <<= 8; 1678 v |= (keyBytes[i] & 0xFF); 1679 } 1680 1681 return new SortValues(new EntryID(v), attributeValues, sortOrder); 1682 } 1683 1684 /** 1685 * Get the sorted set capacity configured for this VLV index. 1686 * 1687 * @return The sorted set capacity. 1688 */ 1689 public int getSortedSetCapacity() 1690 { 1691 return sortedSetCapacity; 1692 } 1693 1694 /** 1695 * Indicates if the given entry should belong in this VLV index. 1696 * 1697 * @param entry The entry to check. 1698 * @return True if the given entry should belong in this VLV index or False 1699 * otherwise. 1700 * @throws DirectoryException If a Directory Server error occurs. 1701 */ 1702 public boolean shouldInclude(Entry entry) throws DirectoryException 1703 { 1704 DN entryDN = entry.getDN(); 1705 if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) 1706 { 1707 return true; 1708 } 1709 return false; 1710 } 1711 1712 /** 1713 * {@inheritDoc} 1714 */ 1715 public synchronized boolean isConfigurationChangeAcceptable( 1716 LocalDBVLVIndexCfg cfg, 1717 List<Message> unacceptableReasons) 1718 { 1719 try 1720 { 1721 this.filter = 1722 SearchFilter.createFilterFromString(config.getFilter()); 1723 } 1724 catch(Exception e) 1725 { 1726 Message msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get( 1727 config.getFilter(), name, 1728 stackTraceToSingleLineString(e)); 1729 unacceptableReasons.add(msg); 1730 return false; 1731 } 1732 1733 String[] sortAttrs = config.getSortOrder().split(" "); 1734 SortKey[] sortKeys = new SortKey[sortAttrs.length]; 1735 OrderingMatchingRule[] orderingRules = 1736 new OrderingMatchingRule[sortAttrs.length]; 1737 boolean[] ascending = new boolean[sortAttrs.length]; 1738 for(int i = 0; i < sortAttrs.length; i++) 1739 { 1740 try 1741 { 1742 if(sortAttrs[i].startsWith("-")) 1743 { 1744 ascending[i] = false; 1745 sortAttrs[i] = sortAttrs[i].substring(1); 1746 } 1747 else 1748 { 1749 ascending[i] = true; 1750 if(sortAttrs[i].startsWith("+")) 1751 { 1752 sortAttrs[i] = sortAttrs[i].substring(1); 1753 } 1754 } 1755 } 1756 catch(Exception e) 1757 { 1758 Message msg = 1759 ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( 1760 String.valueOf(sortKeys[i]), name); 1761 unacceptableReasons.add(msg); 1762 return false; 1763 } 1764 1765 AttributeType attrType = 1766 DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase()); 1767 if(attrType == null) 1768 { 1769 Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( 1770 sortAttrs[i], name); 1771 unacceptableReasons.add(msg); 1772 return false; 1773 } 1774 sortKeys[i] = new SortKey(attrType, ascending[i]); 1775 orderingRules[i] = attrType.getOrderingMatchingRule(); 1776 } 1777 1778 return true; 1779 } 1780 1781 /** 1782 * {@inheritDoc} 1783 */ 1784 public synchronized ConfigChangeResult applyConfigurationChange( 1785 LocalDBVLVIndexCfg cfg) 1786 { 1787 ResultCode resultCode = ResultCode.SUCCESS; 1788 boolean adminActionRequired = false; 1789 ArrayList<Message> messages = new ArrayList<Message>(); 1790 1791 // Update base DN only if changed.. 1792 if(!config.getBaseDN().equals(cfg.getBaseDN())) 1793 { 1794 this.baseDN = cfg.getBaseDN(); 1795 adminActionRequired = true; 1796 } 1797 1798 // Update scope only if changed. 1799 if(!config.getScope().equals(cfg.getScope())) 1800 { 1801 this.scope = SearchScope.valueOf(cfg.getScope().name()); 1802 adminActionRequired = true; 1803 } 1804 1805 // Update sort set capacity only if changed. 1806 if(config.getMaxBlockSize() != 1807 cfg.getMaxBlockSize()) 1808 { 1809 this.sortedSetCapacity = cfg.getMaxBlockSize(); 1810 1811 // Require admin action only if the new capacity is larger. Otherwise, 1812 // we will lazyly update the sorted sets. 1813 if(config.getMaxBlockSize() < 1814 cfg.getMaxBlockSize()) 1815 { 1816 adminActionRequired = true; 1817 } 1818 } 1819 1820 // Update the filter only if changed. 1821 if(!config.getFilter().equals(cfg.getFilter())) 1822 { 1823 try 1824 { 1825 this.filter = 1826 SearchFilter.createFilterFromString(cfg.getFilter()); 1827 adminActionRequired = true; 1828 } 1829 catch(Exception e) 1830 { 1831 Message msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get( 1832 config.getFilter(), name, 1833 stackTraceToSingleLineString(e)); 1834 messages.add(msg); 1835 if(resultCode == ResultCode.SUCCESS) 1836 { 1837 resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX; 1838 } 1839 } 1840 } 1841 1842 // Update the sort order only if changed. 1843 if(!config.getSortOrder().equals( 1844 cfg.getMaxBlockSize())) 1845 { 1846 String[] sortAttrs = cfg.getSortOrder().split(" "); 1847 SortKey[] sortKeys = new SortKey[sortAttrs.length]; 1848 OrderingMatchingRule[] orderingRules = 1849 new OrderingMatchingRule[sortAttrs.length]; 1850 boolean[] ascending = new boolean[sortAttrs.length]; 1851 for(int i = 0; i < sortAttrs.length; i++) 1852 { 1853 try 1854 { 1855 if(sortAttrs[i].startsWith("-")) 1856 { 1857 ascending[i] = false; 1858 sortAttrs[i] = sortAttrs[i].substring(1); 1859 } 1860 else 1861 { 1862 ascending[i] = true; 1863 if(sortAttrs[i].startsWith("+")) 1864 { 1865 sortAttrs[i] = sortAttrs[i].substring(1); 1866 } 1867 } 1868 } 1869 catch(Exception e) 1870 { 1871 Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( 1872 String.valueOf(String.valueOf(sortKeys[i])), name); 1873 messages.add(msg); 1874 if(resultCode == ResultCode.SUCCESS) 1875 { 1876 resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX; 1877 } 1878 } 1879 1880 AttributeType attrType = 1881 DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase()); 1882 if(attrType == null) 1883 { 1884 Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( 1885 String.valueOf(String.valueOf(sortKeys[i])), name); 1886 messages.add(msg); 1887 if(resultCode == ResultCode.SUCCESS) 1888 { 1889 resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX; 1890 } 1891 } 1892 sortKeys[i] = new SortKey(attrType, ascending[i]); 1893 orderingRules[i] = attrType.getOrderingMatchingRule(); 1894 } 1895 1896 this.sortOrder = new SortOrder(sortKeys); 1897 this.comparator = new VLVKeyComparator(orderingRules, ascending); 1898 1899 // We have to close the database and open it using the new comparator. 1900 entryContainer.exclusiveLock.lock(); 1901 try 1902 { 1903 this.close(); 1904 this.dbConfig.setBtreeComparator(this.comparator); 1905 this.open(); 1906 } 1907 catch(DatabaseException de) 1908 { 1909 messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(de))); 1910 if(resultCode == ResultCode.SUCCESS) 1911 { 1912 resultCode = DirectoryServer.getServerErrorResultCode(); 1913 } 1914 } 1915 finally 1916 { 1917 entryContainer.exclusiveLock.unlock(); 1918 } 1919 1920 adminActionRequired = true; 1921 } 1922 1923 1924 if(adminActionRequired) 1925 { 1926 trusted = false; 1927 Message message = NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(name); 1928 messages.add(message); 1929 try 1930 { 1931 state.putIndexTrustState(null, this, false); 1932 } 1933 catch(DatabaseException de) 1934 { 1935 messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(de))); 1936 if(resultCode == ResultCode.SUCCESS) 1937 { 1938 resultCode = DirectoryServer.getServerErrorResultCode(); 1939 } 1940 } 1941 } 1942 1943 this.config = cfg; 1944 return new ConfigChangeResult(resultCode, adminActionRequired, messages); 1945 } 1946 }