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    }