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 029 030 import com.sleepycat.je.*; 031 032 import org.opends.messages.Message; 033 import org.opends.server.core.DirectoryServer; 034 import org.opends.server.core.SearchOperation; 035 import org.opends.server.loggers.debug.DebugTracer; 036 import org.opends.server.protocols.asn1.ASN1OctetString; 037 import org.opends.server.types.Attribute; 038 import org.opends.server.types.AttributeType; 039 import org.opends.server.types.AttributeValue; 040 import org.opends.server.types.ConditionResult; 041 import org.opends.server.types.DebugLogLevel; 042 import org.opends.server.types.DirectoryException; 043 import org.opends.server.types.DN; 044 import org.opends.server.types.Entry; 045 import org.opends.server.types.LDAPURL; 046 import org.opends.server.types.Modification; 047 import org.opends.server.types.ResultCode; 048 import org.opends.server.types.SearchResultReference; 049 import org.opends.server.types.SearchScope; 050 import org.opends.server.util.StaticUtils; 051 052 import java.io.UnsupportedEncodingException; 053 import java.util.ArrayList; 054 import java.util.Comparator; 055 import java.util.LinkedHashSet; 056 import java.util.List; 057 import java.util.Set; 058 059 import static org.opends.server.util.ServerConstants.ATTR_REFERRAL_URL; 060 import static org.opends.server.loggers.debug.DebugLogger.*; 061 import static org.opends.messages.JebMessages. 062 NOTE_JEB_REFERRAL_RESULT_MESSAGE; 063 /** 064 * This class represents the referral database which contains URIs from referral 065 * entries. The key is the DN of the referral entry and the value is that of a 066 * labeled URI in the ref attribute for that entry. Duplicate keys are permitted 067 * since a referral entry can contain multiple values of the ref attribute. Key 068 * order is the same as in the DN database so that all referrals in a subtree 069 * can be retrieved by cursoring through a range of the records. 070 */ 071 public class DN2URI extends DatabaseContainer 072 { 073 /** 074 * The tracer object for the debug logger. 075 */ 076 private static final DebugTracer TRACER = getTracer(); 077 078 /** 079 * The key comparator used for the DN database. 080 */ 081 private Comparator<byte[]> dn2uriComparator; 082 083 084 /** 085 * The standard attribute type that is used to specify the set of referral 086 * URLs in a referral entry. 087 */ 088 private final AttributeType referralType = 089 DirectoryServer.getAttributeType(ATTR_REFERRAL_URL); 090 091 /** 092 * A flag that indicates whether there are any referrals contained in this 093 * database. It should only be set to {@code false} when it is known that 094 * there are no referrals. 095 */ 096 private volatile ConditionResult containsReferrals = 097 ConditionResult.UNDEFINED; 098 099 100 /** 101 * Create a new object representing a referral database in a given 102 * entryContainer. 103 * 104 * @param name The name of the referral database. 105 * @param env The JE environment. 106 * @param entryContainer The entryContainer of the DN database. 107 * @throws DatabaseException If an error occurs in the JE database. 108 */ 109 DN2URI(String name, Environment env, 110 EntryContainer entryContainer) 111 throws DatabaseException 112 { 113 super(name, env, entryContainer); 114 115 dn2uriComparator = new EntryContainer.KeyReverseComparator(); 116 DatabaseConfig dn2uriConfig = new DatabaseConfig(); 117 118 if(env.getConfig().getReadOnly()) 119 { 120 dn2uriConfig.setReadOnly(true); 121 dn2uriConfig.setSortedDuplicates(true); 122 dn2uriConfig.setAllowCreate(false); 123 dn2uriConfig.setTransactional(false); 124 } 125 else if(!env.getConfig().getTransactional()) 126 { 127 dn2uriConfig.setSortedDuplicates(true); 128 dn2uriConfig.setAllowCreate(true); 129 dn2uriConfig.setTransactional(false); 130 dn2uriConfig.setDeferredWrite(true); 131 } 132 else 133 { 134 dn2uriConfig.setSortedDuplicates(true); 135 dn2uriConfig.setAllowCreate(true); 136 dn2uriConfig.setTransactional(true); 137 } 138 this.dbConfig = dn2uriConfig; 139 this.dbConfig.setBtreeComparator(dn2uriComparator.getClass()); 140 } 141 142 /** 143 * Insert a URI value in the referral database. 144 * 145 * @param txn A database transaction used for the update, or null if none is 146 * required. 147 * @param dn The DN of the referral entry. 148 * @param labeledURI The labeled URI value of the ref attribute. 149 * @return true if the record was inserted, false if it was not. 150 * @throws DatabaseException If an error occurs in the JE database. 151 */ 152 public boolean insert(Transaction txn, DN dn, String labeledURI) 153 throws DatabaseException 154 { 155 byte[] normDN = StaticUtils.getBytes(dn.toNormalizedString()); 156 byte[] URIBytes = StaticUtils.getBytes(labeledURI); 157 DatabaseEntry key = new DatabaseEntry(normDN); 158 DatabaseEntry data = new DatabaseEntry(URIBytes); 159 OperationStatus status; 160 161 // The JE insert method does not permit duplicate keys so we must use the 162 // put method. 163 status = put(txn, key, data); 164 if (status != OperationStatus.SUCCESS) 165 { 166 return false; 167 } 168 containsReferrals = ConditionResult.TRUE; 169 return true; 170 } 171 172 /** 173 * Delete URI values for a given referral entry from the referral database. 174 * 175 * @param txn A database transaction used for the update, or null if none is 176 * required. 177 * @param dn The DN of the referral entry for which URI values are to be 178 * deleted. 179 * @return true if the values were deleted, false if not. 180 * @throws DatabaseException If an error occurs in the JE database. 181 */ 182 public boolean delete(Transaction txn, DN dn) 183 throws DatabaseException 184 { 185 byte[] normDN = StaticUtils.getBytes(dn.toNormalizedString()); 186 DatabaseEntry key = new DatabaseEntry(normDN); 187 OperationStatus status; 188 189 status = delete(txn, key); 190 if (status != OperationStatus.SUCCESS) 191 { 192 return false; 193 } 194 containsReferrals = containsReferrals(txn); 195 return true; 196 } 197 198 /** 199 * Delete a single URI value from the referral database. 200 * @param txn A database transaction used for the update, or null if none is 201 * required. 202 * @param dn The DN of the referral entry. 203 * @param labeledURI The URI value to be deleted. 204 * @return true if the value was deleted, false if not. 205 * @throws DatabaseException If an error occurs in the JE database. 206 */ 207 public boolean delete(Transaction txn, DN dn, String labeledURI) 208 throws DatabaseException 209 { 210 CursorConfig cursorConfig = null; 211 byte[] normDN = StaticUtils.getBytes(dn.toNormalizedString()); 212 byte[] URIBytes = StaticUtils.getBytes(labeledURI); 213 DatabaseEntry key = new DatabaseEntry(normDN); 214 DatabaseEntry data = new DatabaseEntry(URIBytes); 215 OperationStatus status; 216 217 Cursor cursor = openCursor(txn, cursorConfig); 218 try 219 { 220 status = cursor.getSearchBoth(key, data, null); 221 if (status == OperationStatus.SUCCESS) 222 { 223 status = cursor.delete(); 224 } 225 } 226 finally 227 { 228 cursor.close(); 229 } 230 231 if (status != OperationStatus.SUCCESS) 232 { 233 return false; 234 } 235 containsReferrals = containsReferrals(txn); 236 return true; 237 } 238 239 /** 240 * Indicates whether the underlying database contains any referrals. 241 * 242 * @param txn The transaction to use when making the determination. 243 * 244 * @return {@code true} if it is believed that the underlying database may 245 * contain at least one referral, or {@code false} if it is certain 246 * that it doesn't. 247 */ 248 private ConditionResult containsReferrals(Transaction txn) 249 { 250 try 251 { 252 Cursor cursor = openCursor(txn, null); 253 DatabaseEntry key = new DatabaseEntry(); 254 DatabaseEntry data = new DatabaseEntry(); 255 256 OperationStatus status = cursor.getFirst(key, data, null); 257 cursor.close(); 258 259 if (status == OperationStatus.SUCCESS) 260 { 261 return ConditionResult.TRUE; 262 } 263 else if (status == OperationStatus.NOTFOUND) 264 { 265 return ConditionResult.FALSE; 266 } 267 else 268 { 269 return ConditionResult.UNDEFINED; 270 } 271 } 272 catch (Exception e) 273 { 274 if (debugEnabled()) 275 { 276 TRACER.debugCaught(DebugLogLevel.ERROR, e); 277 } 278 279 return ConditionResult.UNDEFINED; 280 } 281 } 282 283 /** 284 * Update the referral database for an entry that has been modified. Does 285 * not do anything unless the entry before the modification or the entry after 286 * the modification is a referral entry. 287 * 288 * @param txn A database transaction used for the update, or null if none is 289 * required. 290 * @param before The entry before the modifications have been applied. 291 * @param after The entry after the modifications have been applied. 292 * @param mods The sequence of modifications made to the entry. 293 * @throws DatabaseException If an error occurs in the JE database. 294 */ 295 public void modifyEntry(Transaction txn, Entry before, Entry after, 296 List<Modification> mods) 297 throws DatabaseException 298 { 299 DN entryDN = before.getDN(); 300 for (Modification mod : mods) 301 { 302 Attribute modAttr = mod.getAttribute(); 303 AttributeType modAttrType = modAttr.getAttributeType(); 304 if (modAttrType.equals(referralType)) 305 { 306 Attribute a = mod.getAttribute(); 307 switch (mod.getModificationType()) 308 { 309 case ADD: 310 if (a != null) 311 { 312 for (AttributeValue v : a.getValues()) 313 { 314 insert(txn, entryDN, v.getStringValue()); 315 } 316 } 317 break; 318 319 case DELETE: 320 if (a == null || !a.hasValue()) 321 { 322 delete(txn, entryDN); 323 } 324 else 325 { 326 for (AttributeValue v : a.getValues()) 327 { 328 delete(txn, entryDN, v.getStringValue()); 329 } 330 } 331 break; 332 333 case INCREMENT: 334 // Nonsensical. 335 break; 336 337 case REPLACE: 338 delete(txn, entryDN); 339 if (a != null) 340 { 341 for (AttributeValue v : a.getValues()) 342 { 343 insert(txn, entryDN, v.getStringValue()); 344 } 345 } 346 break; 347 } 348 } 349 } 350 } 351 352 /** 353 * Update the referral database for an entry that has been replaced. Does 354 * not do anything unless the entry before it was replaced or the entry after 355 * it was replaced is a referral entry. 356 * 357 * @param txn A database transaction used for the update, or null if none is 358 * required. 359 * @param before The entry before it was replaced. 360 * @param after The entry after it was replaced. 361 * @throws DatabaseException If an error occurs in the JE database. 362 */ 363 public void replaceEntry(Transaction txn, Entry before, Entry after) 364 throws DatabaseException 365 { 366 deleteEntry(txn, before); 367 addEntry(txn, after); 368 } 369 370 /** 371 * Update the referral database for a new entry. Does nothing if the entry 372 * is not a referral entry. 373 * @param txn A database transaction used for the update, or null if none is 374 * required. 375 * @param entry The entry to be added. 376 * @return True if the entry was added successfully or False otherwise. 377 * @throws DatabaseException If an error occurs in the JE database. 378 */ 379 public boolean addEntry(Transaction txn, Entry entry) 380 throws DatabaseException 381 { 382 boolean success = true; 383 Set<String> labeledURIs = entry.getReferralURLs(); 384 if (labeledURIs != null) 385 { 386 DN dn = entry.getDN(); 387 for (String labeledURI : labeledURIs) 388 { 389 if(!insert(txn, dn, labeledURI)) 390 { 391 success = false; 392 } 393 } 394 } 395 return success; 396 } 397 398 /** 399 * Update the referral database for a deleted entry. Does nothing if the entry 400 * was not a referral entry. 401 * @param txn A database transaction used for the update, or null if none is 402 * required. 403 * @param entry The entry to be deleted. 404 * @throws DatabaseException If an error occurs in the JE database. 405 */ 406 public void deleteEntry(Transaction txn, Entry entry) 407 throws DatabaseException 408 { 409 Set<String> labeledURIs = entry.getReferralURLs(); 410 if (labeledURIs != null) 411 { 412 delete(txn, entry.getDN()); 413 } 414 } 415 416 /** 417 * Checks whether the target of an operation is a referral entry and throws 418 * a Directory referral exception if it is. 419 * @param entry The target entry of the operation, or the base entry of a 420 * search operation. 421 * @param searchScope The scope of the search operation, or null if the 422 * operation is not a search operation. 423 * @throws DirectoryException If a referral is found at or above the target 424 * DN. The referral URLs will be set appropriately for the references found 425 * in the referral entry. 426 */ 427 public void checkTargetForReferral(Entry entry, SearchScope searchScope) 428 throws DirectoryException 429 { 430 Set<String> referralURLs = entry.getReferralURLs(); 431 if (referralURLs != null) 432 { 433 throwReferralException(entry.getDN(), entry.getDN(), referralURLs, 434 searchScope); 435 } 436 } 437 438 /** 439 * Throws a Directory referral exception for the case where a referral entry 440 * exists at or above the target DN of an operation. 441 * @param targetDN The target DN of the operation, or the base object of a 442 * search operation. 443 * @param referralDN The DN of the referral entry. 444 * @param labeledURIs The set of labeled URIs in the referral entry. 445 * @param searchScope The scope of the search operation, or null if the 446 * operation is not a search operation. 447 * @throws DirectoryException If a referral is found at or above the target 448 * DN. The referral URLs will be set appropriately for the references found 449 * in the referral entry. 450 */ 451 public void throwReferralException(DN targetDN, DN referralDN, 452 Set<String> labeledURIs, 453 SearchScope searchScope) 454 throws DirectoryException 455 { 456 ArrayList<String> URIList = new ArrayList<String>(labeledURIs.size()); 457 for (String labeledURI : labeledURIs) 458 { 459 // Remove the label part of the labeled URI if there is a label. 460 String uri = labeledURI; 461 int i = labeledURI.indexOf(' '); 462 if (i != -1) 463 { 464 uri = labeledURI.substring(0, i); 465 } 466 467 try 468 { 469 LDAPURL ldapurl = LDAPURL.decode(uri, false); 470 471 if (ldapurl.getScheme().equalsIgnoreCase("ldap")) 472 { 473 DN urlBaseDN = targetDN; 474 if (!referralDN.equals(ldapurl.getBaseDN())) 475 { 476 urlBaseDN = 477 EntryContainer.modDN(targetDN, 478 referralDN.getNumComponents(), 479 ldapurl.getBaseDN()); 480 } 481 ldapurl.setBaseDN(urlBaseDN); 482 if (searchScope == null) 483 { 484 // RFC 3296, 5.2. Target Object Considerations: 485 // In cases where the URI to be returned is a LDAP URL, the server 486 // SHOULD trim any present scope, filter, or attribute list from the 487 // URI before returning it. Critical extensions MUST NOT be trimmed 488 // or modified. 489 StringBuilder builder = new StringBuilder(uri.length()); 490 ldapurl.toString(builder, true); 491 uri = builder.toString(); 492 } 493 else 494 { 495 // RFC 3296, 5.3. Base Object Considerations: 496 // In cases where the URI to be returned is a LDAP URL, the server 497 // MUST provide an explicit scope specifier from the LDAP URL prior 498 // to returning it. 499 ldapurl.getAttributes().clear(); 500 ldapurl.setScope(searchScope); 501 ldapurl.setFilter(null); 502 uri = ldapurl.toString(); 503 } 504 } 505 } 506 catch (DirectoryException e) 507 { 508 if (debugEnabled()) 509 { 510 TRACER.debugCaught(DebugLogLevel.ERROR, e); 511 } 512 // Return the non-LDAP URI as is. 513 } 514 515 URIList.add(uri); 516 } 517 518 // Throw a directory referral exception containing the URIs. 519 Message msg = 520 NOTE_JEB_REFERRAL_RESULT_MESSAGE.get(String.valueOf(referralDN)); 521 throw new DirectoryException( 522 ResultCode.REFERRAL, msg, referralDN, URIList, null); 523 } 524 525 /** 526 * Process referral entries that are above the target DN of an operation. 527 * @param targetDN The target DN of the operation, or the base object of a 528 * search operation. 529 * @param searchScope The scope of the search operation, or null if the 530 * operation is not a search operation. 531 * @throws DirectoryException If a referral is found at or above the target 532 * DN. The referral URLs will be set appropriately for the references found 533 * in the referral entry. 534 */ 535 public void targetEntryReferrals(DN targetDN, SearchScope searchScope) 536 throws DirectoryException 537 { 538 if (containsReferrals == ConditionResult.UNDEFINED) 539 { 540 containsReferrals = containsReferrals(null); 541 } 542 543 if (containsReferrals == ConditionResult.FALSE) 544 { 545 return; 546 } 547 548 Transaction txn = null; 549 CursorConfig cursorConfig = null; 550 551 try 552 { 553 Cursor cursor = openCursor(txn, cursorConfig); 554 try 555 { 556 DatabaseEntry key = new DatabaseEntry(); 557 DatabaseEntry data = new DatabaseEntry(); 558 559 // Go up through the DIT hierarchy until we find a referral. 560 for (DN dn = entryContainer.getParentWithinBase(targetDN); dn != null; 561 dn = entryContainer.getParentWithinBase(dn)) 562 { 563 // Look for a record whose key matches the current DN. 564 String normDN = dn.toNormalizedString(); 565 key.setData(StaticUtils.getBytes(normDN)); 566 OperationStatus status = 567 cursor.getSearchKey(key, data, LockMode.DEFAULT); 568 if (status == OperationStatus.SUCCESS) 569 { 570 // Construct a set of all the labeled URIs in the referral. 571 Set<String> labeledURIs = 572 new LinkedHashSet<String>(cursor.count()); 573 do 574 { 575 String labeledURI = new String(data.getData(), "UTF-8"); 576 labeledURIs.add(labeledURI); 577 status = cursor.getNextDup(key, data, LockMode.DEFAULT); 578 } while (status == OperationStatus.SUCCESS); 579 580 throwReferralException(targetDN, dn, labeledURIs, searchScope); 581 } 582 } 583 } 584 finally 585 { 586 cursor.close(); 587 } 588 } 589 catch (DatabaseException e) 590 { 591 if (debugEnabled()) 592 { 593 TRACER.debugCaught(DebugLogLevel.ERROR, e); 594 } 595 } 596 catch (UnsupportedEncodingException e) 597 { 598 if (debugEnabled()) 599 { 600 TRACER.debugCaught(DebugLogLevel.ERROR, e); 601 } 602 } 603 } 604 605 /** 606 * Return search result references for a search operation using the referral 607 * database to find all referral entries within scope of the search. 608 * @param searchOp The search operation for which search result references 609 * should be returned. 610 * @return <CODE>true</CODE> if the caller should continue processing the 611 * search request and sending additional entries and references, or 612 * <CODE>false</CODE> if not for some reason (e.g., the size limit 613 * has been reached or the search has been abandoned). 614 * @throws DirectoryException If a Directory Server error occurs. 615 */ 616 public boolean returnSearchReferences(SearchOperation searchOp) 617 throws DirectoryException 618 { 619 if (containsReferrals == ConditionResult.UNDEFINED) 620 { 621 containsReferrals = containsReferrals(null); 622 } 623 624 if (containsReferrals == ConditionResult.FALSE) 625 { 626 return true; 627 } 628 629 Transaction txn = null; 630 CursorConfig cursorConfig = null; 631 632 /* 633 * We will iterate forwards through a range of the keys to 634 * find subordinates of the base entry from the top of the tree 635 * downwards. 636 */ 637 DN baseDN = searchOp.getBaseDN(); 638 String normBaseDN = baseDN.toNormalizedString(); 639 byte[] suffix = StaticUtils.getBytes("," + normBaseDN); 640 641 /* 642 * Set the ending value to a value of equal length but slightly 643 * greater than the suffix. Since keys are compared in 644 * reverse order we must set the first byte (the comma). 645 * No possibility of overflow here. 646 */ 647 byte[] end = null; 648 649 DatabaseEntry data = new DatabaseEntry(); 650 DatabaseEntry key = new DatabaseEntry(suffix); 651 652 try 653 { 654 Cursor cursor = openCursor(txn, cursorConfig); 655 try 656 { 657 // Initialize the cursor very close to the starting value then 658 // step forward until we pass the ending value. 659 for (OperationStatus status = 660 cursor.getSearchKeyRange(key, data, LockMode.DEFAULT); 661 status == OperationStatus.SUCCESS; 662 status = cursor.getNextNoDup(key, data, LockMode.DEFAULT)) 663 { 664 if (end == null) 665 { 666 end = suffix.clone(); 667 end[0] = (byte) (end[0] + 1); 668 } 669 670 int cmp = dn2uriComparator.compare(key.getData(), end); 671 if (cmp >= 0) 672 { 673 // We have gone past the ending value. 674 break; 675 } 676 677 // We have found a subordinate referral. 678 DN dn = DN.decode(new ASN1OctetString(key.getData())); 679 680 // Make sure the referral is within scope. 681 if (searchOp.getScope() == SearchScope.SINGLE_LEVEL) 682 { 683 if ((dn.getNumComponents() != 684 baseDN.getNumComponents() + 1)) 685 { 686 continue; 687 } 688 } 689 690 // Construct a list of all the URIs in the referral. 691 ArrayList<String> URIList = new ArrayList<String>(cursor.count()); 692 do 693 { 694 // Remove the label part of the labeled URI if there is a label. 695 String labeledURI = new String(data.getData(), "UTF-8"); 696 String uri = labeledURI; 697 int i = labeledURI.indexOf(' '); 698 if (i != -1) 699 { 700 uri = labeledURI.substring(0, i); 701 } 702 703 // From RFC 3296 section 5.4: 704 // If the URI component is not a LDAP URL, it should be returned as 705 // is. If the LDAP URL's DN part is absent or empty, the DN part 706 // must be modified to contain the DN of the referral object. If 707 // the URI component is a LDAP URL, the URI SHOULD be modified to 708 // add an explicit scope specifier. 709 try 710 { 711 LDAPURL ldapurl = LDAPURL.decode(uri, false); 712 713 if (ldapurl.getScheme().equalsIgnoreCase("ldap")) 714 { 715 if (ldapurl.getBaseDN().isNullDN()) 716 { 717 ldapurl.setBaseDN(dn); 718 } 719 ldapurl.getAttributes().clear(); 720 if (searchOp.getScope() == SearchScope.SINGLE_LEVEL) 721 { 722 ldapurl.setScope(SearchScope.BASE_OBJECT); 723 } 724 else 725 { 726 ldapurl.setScope(SearchScope.WHOLE_SUBTREE); 727 } 728 ldapurl.setFilter(null); 729 uri = ldapurl.toString(); 730 } 731 } 732 catch (DirectoryException e) 733 { 734 if (debugEnabled()) 735 { 736 TRACER.debugCaught(DebugLogLevel.ERROR, e); 737 } 738 // Return the non-LDAP URI as is. 739 } 740 741 URIList.add(uri); 742 status = cursor.getNextDup(key, data, LockMode.DEFAULT); 743 } while (status == OperationStatus.SUCCESS); 744 745 SearchResultReference reference = new SearchResultReference(URIList); 746 if (!searchOp.returnReference(dn, reference)) 747 { 748 return false; 749 } 750 } 751 } 752 finally 753 { 754 cursor.close(); 755 } 756 } 757 catch (DatabaseException e) 758 { 759 if (debugEnabled()) 760 { 761 TRACER.debugCaught(DebugLogLevel.ERROR, e); 762 } 763 } 764 catch (UnsupportedEncodingException e) 765 { 766 if (debugEnabled()) 767 { 768 TRACER.debugCaught(DebugLogLevel.ERROR, e); 769 } 770 } 771 772 return true; 773 } 774 775 /** 776 * Gets the comparator for records stored in this database. 777 * 778 * @return The comparator used for records stored in this database. 779 */ 780 public Comparator<byte[]> getComparator() 781 { 782 return dn2uriComparator; 783 } 784 }