[prev in list] [next in list] [prev in thread] [next in thread] 

List:       forgerock-opendj-dev
Subject:    [Opendj-dev] [11532] branches/opendj3-server-dev: OPENDJ-1690 CR-5724 Fix file-based changelog purgi
From:       noreply () forgerock ! org
Date:       2014-12-23 11:39:41
Message-ID: 20141223113941.49D073F87B () sources ! internal ! forgerock ! com
[Download RAW message or body]

[Attachment #2 (text/html)]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[11532] branches/opendj3-server-dev: OPENDJ-1690 CR-5724 Fix file-based \
changelog purging</title> </head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: \
verdana,arial,helvetica,sans-serif; font-size: 10pt;  } #msg dl a { font-weight: \
bold} #msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: \
bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: \
6px; } #logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em \
0; } #logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg \
h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; } \
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; \
} #logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: \
-1.5em; padding-left: 1.5em; } #logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em \
1em 0 1em; background: white;} #logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid \
#fa0; border-bottom: 1px solid #fa0; background: #fff; } #logmsg table th { \
text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted \
#fa0; } #logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: \
0.2em 0.5em; } #logmsg table thead th { text-align: center; border-bottom: 1px solid \
#fa0; } #logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: \
6px; } #patch { width: 100%; }
#patch h4 {font-family: \
verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
 #patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, \
#patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins \
{background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del \
{background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, \
                .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a \
href="http://sources.forgerock.org/changelog/opendj/?cs=11532">11532</a></dd> \
<dt>Author</dt> <dd>nicolas.capponi@forgerock.com</dd> <dt>Date</dt> <dd>2014-12-23 \
11:39:41 +0000 (Tue, 23 Dec 2014)</dd> </dl>

<h3>Log Message</h3>
<pre><a href="https://bugster.forgerock.org/jira/browse/OPENDJ-1690">OPENDJ-1690</a> \
CR-5724 Fix file-based changelog purging

- Log file rotation for CN Index DB is based on both size and time
- Time interval before log file rotation is a fraction of replication purge delay
- Last log file rotation time for CN index DB is stored in a file</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#branchesopendj3serverdevsrcmessagesmessagesreplicationproperties">branches/opendj3-server-dev/src/messages/messages/replication.properties</a></li>
 <li><a href="#branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchange \
logfileFileChangeNumberIndexDBjava">branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDB.java</a></li>
 <li><a href="#branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchange \
logfileFileChangelogDBjava">branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java</a></li>
 <li><a href="#branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchange \
logfileLogjava">branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Log.java</a></li>
 <li><a href="#branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchange \
logfileRecordjava">branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Record.java</a></li>
 <li><a href="#branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchange \
logfileReplicationEnvironmentjava">branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java</a></li>
 <li><a href="#branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverrep \
licationserverchangelogfileFileChangeNumberIndexDBTestjava">branches/opendj3-server-de \
v/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDBTest.java</a></li>
 <li><a href="#branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverrep \
licationserverchangelogfileFileReplicaDBTestjava">branches/opendj3-server-dev/tests/un \
it-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileReplicaDBTest.java</a></li>
 <li><a href="#branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverrep \
licationserverchangelogfileLogTestjava">branches/opendj3-server-dev/tests/unit-tests-t \
estng/src/server/org/opends/server/replication/server/changelog/file/LogTest.java</a></li>
 <li><a href="#branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverrep \
licationserverchangelogfileReplicationEnvironmentTestjava">branches/opendj3-server-dev \
/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironmentTest.java</a></li>
 </ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchangel \
ogfileRecordjava">branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Record.java</a></li>
 </ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="branchesopendj3serverdevsrcmessagesmessagesreplicationproperties"></a>
<div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/src/messages/messages/replication.properties (11531 => \
11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/src/messages/messages/replication.properties	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/src/messages/messages/replication.properties	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -626,5 +626,9 @@
</span><span class="cx">  perform a search request on cn=changelog
</span><span class="cx"> ERR_CHANGELOG_BACKEND_SEARCH_286 =An error occurred when \
</span><span class="cx">  searching base DN '%s' with filter '%s' in changelog \
backend : %s </span><del>-ERR_CHANGELOG_BACKEND_ATTRIBUTE_287 =An error occurred when \
                \
- retrieving attribute value for attribute '%s' for entry DN '%s' in changelog \
backend : %s </del><span class="cx">\ No newline at end of file
</span><ins>+ERR_CHANGELOG_BACKEND_ATTRIBUTE_287=An error occurred when \
+ retrieving attribute value for attribute '%s' for entry DN '%s' in changelog \
backend : %s +ERR_CHANGELOG_UNABLE_TO_CREATE_LAST_LOG_ROTATION_TIME_FILE_288=Could \
not create \ + file '%s' to store last log rotation time %d
+ERR_CHANGELOG_UNABLE_TO_DELETE_LAST_LOG_ROTATION_TIME_FILE_289=Could not delete \
+ file '%s' that stored the previous last log rotation time
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchangelogfileFileChangeNumberIndexDBjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDB.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDB.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDB.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -59,6 +59,16 @@
</span><span class="cx">   /** The parser of records stored in this \
ChangeNumberIndexDB. */ </span><span class="cx">   static final RecordParser&lt;Long, \
ChangeNumberIndexRecord&gt; RECORD_PARSER = new ChangeNumberIndexDBParser(); \
</span><span class="cx">  </span><ins>+  static final \
Record.Mapper&lt;ChangeNumberIndexRecord, CSN&gt; MAPPER_TO_CSN = +      new \
Record.Mapper&lt;ChangeNumberIndexRecord, CSN&gt;() +      {
+        @Override
+        public CSN map(ChangeNumberIndexRecord value)
+        {
+          return value.getCSN();
+        }
+      };
+
</ins><span class="cx">   /** The log in which records are persisted. */
</span><span class="cx">   private final Log&lt;Long, ChangeNumberIndexRecord&gt; \
log; </span><span class="cx"> 
</span><span class="lines">@@ -231,8 +241,14 @@
</span><span class="cx">     {
</span><span class="cx">       return null;
</span><span class="cx">     }
</span><del>-    final Record&lt;Long, ChangeNumberIndexRecord&gt; record = \
                log.purgeUpTo(purgeCSN.getTime());
-    return record != null ? record.getValue().getCSN() : null;
</del><ins>+    // Retrieve the oldest change number that must not be purged
+    final Long purgeChangeNumber = log.findBoundaryKeyFromRecord(MAPPER_TO_CSN, \
purgeCSN); +    if (purgeChangeNumber != null)
+    {
+      final Record&lt;Long, ChangeNumberIndexRecord&gt; record = \
log.purgeUpTo(purgeChangeNumber); +      return record != null ? \
record.getValue().getCSN() : null; +    }
+    return null;
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   /**
</span></span></pre></div>
<a id="branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchangelogfileFileChangelogDBjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -41,6 +41,7 @@
</span><span class="cx"> import org.forgerock.i18n.LocalizableMessageBuilder;
</span><span class="cx"> import org.forgerock.i18n.slf4j.LocalizedLogger;
</span><span class="cx"> import org.forgerock.opendj.config.server.ConfigException;
</span><ins>+import org.forgerock.util.time.TimeService;
</ins><span class="cx"> import \
org.opends.server.admin.std.server.ReplicationServerCfg; </span><span class="cx"> \
import org.opends.server.api.DirectoryThread; </span><span class="cx"> import \
org.opends.server.backends.ChangelogBackend; </span><span class="lines">@@ -296,7 \
+297,7 @@ </span><span class="cx">     try
</span><span class="cx">     {
</span><span class="cx">       final File dbDir = \
getFileForPath(config.getReplicationDBDirectory()); </span><del>-      replicationEnv \
= new ReplicationEnvironment(dbDir.getAbsolutePath(), replicationServer); \
</del><ins>+      replicationEnv = new \
ReplicationEnvironment(dbDir.getAbsolutePath(), replicationServer, \
TimeService.SYSTEM); </ins><span class="cx">       final ChangelogState \
changelogState = replicationEnv.getChangelogState(); </span><span class="cx">       \
initializeToChangelogState(changelogState); </span><span class="cx">       if \
(config.isComputeChangeNumber()) </span><span class="lines">@@ -574,6 +575,12 @@
</span><span class="cx">   public void setPurgeDelay(final long purgeDelayInMillis)
</span><span class="cx">   {
</span><span class="cx">     this.purgeDelayInMillis = purgeDelayInMillis;
</span><ins>+
+    // Rotation time interval for CN Index DB log file
+    // needs to be a fraction of the purge delay
+    // to ensure there is at least one file to purge
+    replicationEnv.setCNIndexDBRotationInterval(purgeDelayInMillis / 2);
+
</ins><span class="cx">     if (purgeDelayInMillis &gt; 0)
</span><span class="cx">     {
</span><span class="cx">       final ChangelogDBPurger newPurger = new \
ChangelogDBPurger(); </span></span></pre></div>
<a id="branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchangelogfileLogjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Log.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Log.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Log.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -51,6 +51,7 @@
</span><span class="cx"> import org.forgerock.i18n.slf4j.LocalizedLogger;
</span><span class="cx"> import org.forgerock.util.Reject;
</span><span class="cx"> import org.forgerock.util.Utils;
</span><ins>+import org.forgerock.util.time.TimeService;
</ins><span class="cx"> import \
org.opends.server.replication.server.changelog.api.ChangelogException; </span><span \
class="cx"> import org.opends.server.replication.server.changelog.api.DBCursor; \
</span><span class="cx"> import \
org.opends.server.replication.server.changelog.api.DBCursor.KeyMatchingStrategy; \
</span><span class="lines">@@ -74,9 +75,9 @@ </span><span class="cx">  * the string \
representation of lowest and highest key present in the log file.&lt;/li&gt; \
</span><span class="cx">  * &lt;/ul&gt; </span><span class="cx">  * A read-only log \
file is created each time the head log file has reached the </span><del>- * maximum \
                size limit. The head log file is then rotated to the read-only file \
                and a
- * new empty head log file is opened. There is no limit on the number of read-only
- * files, but they can be purged.
</del><ins>+ * maximum size limit or the time limit. The head log file is then \
rotated to the + * read-only file and a new empty head log file is opened. There is \
no limit on the + * number of read-only files, but they can be purged.
</ins><span class="cx">  * &lt;p&gt;
</span><span class="cx">  * A log is obtained using the {@code Log.openLog()} method \
and must always be </span><span class="cx">  * released using the {@code close()} \
method. </span><span class="lines">@@ -170,14 +171,23 @@
</span><span class="cx">   private final List&lt;LogCursor&lt;K, V&gt;&gt; \
openCursors = new CopyOnWriteArrayList&lt;LogCursor&lt;K, V&gt;&gt;(); </span><span \
class="cx">  </span><span class="cx">   /**
</span><del>-   * A log file is rotated once it has exceeded this size limit. The log \
file can have </del><ins>+   * A log file can be rotated once it has exceeded this \
size limit. The log file can have </ins><span class="cx">    * a size much larger \
than this limit if the last record written has a huge size. </span><span class="cx">  \
* </span><del>-   * TODO : to be replaced later by a (or a list of) configurable \
Rotation policy </del><ins>+   * TODO : to be replaced later by a list of \
configurable Rotation policy </ins><span class="cx">    * eg, \
List&lt;RotationPolicy&gt; rotationPolicies = new ArrayList&lt;RotationPolicy&gt;(); \
</span><span class="cx">    */ </span><span class="cx">   private final long \
sizeLimitPerLogFileInBytes; </span><span class="cx"> 
</span><ins>+  /** The time service used for timing. It is package private so it can \
be modified by test case. */ +  TimeService timeService = TimeService.SYSTEM;
+
+  /** A log file can be rotated once it has exceeded a given time interval. No \
rotation happens if equals to zero. */ +  private long rotationIntervalInMillis;
+
+  /** The last time a log file was rotated. */
+  private long lastRotationTime;
+
</ins><span class="cx">   /**
</span><span class="cx">    * The exclusive lock used for writes and lifecycle \
operations on this log: </span><span class="cx">    * initialize, clear, sync and \
close. </span><span class="lines">@@ -190,6 +200,12 @@
</span><span class="cx">   private final Lock sharedLock;
</span><span class="cx"> 
</span><span class="cx">   /**
</span><ins>+   * The replication environment used to create this log. The log is \
notifying it for any change +   * that must be persisted.
+   */
+  private final ReplicationEnvironment replicationEnv;
+
+  /**
</ins><span class="cx">    * Open a log with the provided log path, record parser and \
maximum size per </span><span class="cx">    * log file.
</span><span class="cx">    * &lt;p&gt;
</span><span class="lines">@@ -199,25 +215,28 @@
</span><span class="cx">    *          Type of the key of a record, which must be \
comparable. </span><span class="cx">    * @param &lt;V&gt;
</span><span class="cx">    *          Type of the value of a record.
</span><ins>+   * @param replicationEnv
+   *          The replication environment used to create this log.
</ins><span class="cx">    * @param logPath
</span><span class="cx">    *          Path of the log.
</span><span class="cx">    * @param parser
</span><span class="cx">    *          Parser for encoding/decoding of records.
</span><del>-   * @param sizeLimitPerFileInBytes
-   *          Limit in bytes before rotating the head log file of the log.
</del><ins>+   * @param rotationParameters
+   *          Parameters for the log files rotation.
</ins><span class="cx">    * @return a log
</span><span class="cx">    * @throws ChangelogException
</span><span class="cx">    *           If a problem occurs during initialization.
</span><span class="cx">    */
</span><del>-  static synchronized &lt;K extends Comparable&lt;K&gt;, V&gt; Log&lt;K, \
                V&gt; openLog(final File logPath,
-      final RecordParser&lt;K, V&gt; parser, final long sizeLimitPerFileInBytes) \
throws ChangelogException </del><ins>+  static synchronized &lt;K extends \
Comparable&lt;K&gt;, V&gt; Log&lt;K, V&gt; openLog(final ReplicationEnvironment \
replicationEnv, +      final File logPath, final RecordParser&lt;K, V&gt; parser, \
final LogRotationParameters rotationParameters) +      throws ChangelogException
</ins><span class="cx">   {
</span><span class="cx">     Reject.ifNull(logPath, parser);
</span><span class="cx">     @SuppressWarnings(&quot;unchecked&quot;)
</span><span class="cx">     Log&lt;K, V&gt; log = (Log&lt;K, V&gt;) \
logsCache.get(logPath); </span><span class="cx">     if (log == null)
</span><span class="cx">     {
</span><del>-      log = new Log&lt;K, V&gt;(logPath, parser, \
sizeLimitPerFileInBytes); </del><ins>+      log = new Log&lt;K, V&gt;(replicationEnv, \
logPath, parser, rotationParameters); </ins><span class="cx">       \
logsCache.put(logPath, log); </span><span class="cx">     }
</span><span class="cx">     else
</span><span class="lines">@@ -229,7 +248,44 @@
</span><span class="cx">     return log;
</span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  /** Holds the parameters for log files rotation. */
+  static class LogRotationParameters {
+
+    private final long sizeLimitPerFileInBytes;
+    private final long rotationInterval;
+    private final long lastRotationTime;
+
+    /**
+     * Creates rotation parameters.
+     *
+     * @param sizeLimitPerFileInBytes
+     *           Size limit before rotating a log file.
+     * @param rotationInterval
+     *           Time interval before rotating a log file.
+     * @param lastRotationTime
+     *           Last time a log file was rotated.
+     */
+    LogRotationParameters(long sizeLimitPerFileInBytes, long rotationInterval, long \
lastRotationTime) +    {
+      this.sizeLimitPerFileInBytes = sizeLimitPerFileInBytes;
+      this.rotationInterval = rotationInterval;
+      this.lastRotationTime = lastRotationTime;
+    }
+
+  }
+
</ins><span class="cx">   /**
</span><ins>+   * Set the time interval for rotation of log file.
+   *
+   * @param rotationIntervalInMillis
+   *           time interval before rotation of log file
+   */
+  void setRotationInterval(long rotationIntervalInMillis)
+  {
+    this.rotationIntervalInMillis = rotationIntervalInMillis;
+  }
+
+  /**
</ins><span class="cx">    * Release a reference to the log corresponding to provided \
path. The log is </span><span class="cx">    * closed if this is the last reference.
</span><span class="cx">    */
</span><span class="lines">@@ -256,21 +312,27 @@
</span><span class="cx">   /**
</span><span class="cx">    * Creates a new log.
</span><span class="cx">    *
</span><ins>+   * @param replicationEnv
+   *            The replication environment used to create this log.
</ins><span class="cx">    * @param logPath
</span><span class="cx">    *            The directory path of the log.
</span><span class="cx">    * @param parser
</span><span class="cx">    *          Parser of records.
</span><del>-   * @param sizeLimitPerFile
-   *            Limit in bytes before rotating a log file.
</del><ins>+   * @param rotationParams
+   *          Parameters for log-file rotation.
+   *
</ins><span class="cx">    * @throws ChangelogException
</span><span class="cx">    *            If a problem occurs during initialization.
</span><span class="cx">    */
</span><del>-  private Log(final File logPath, final RecordParser&lt;K, V&gt; parser, \
                final long sizeLimitPerFile)
-      throws ChangelogException
</del><ins>+  private Log(final ReplicationEnvironment replicationEnv, final File \
logPath, final RecordParser&lt;K, V&gt; parser, +      final LogRotationParameters \
rotationParams) throws ChangelogException </ins><span class="cx">   {
</span><ins>+    this.replicationEnv = replicationEnv;
</ins><span class="cx">     this.logPath = logPath;
</span><span class="cx">     this.recordParser = parser;
</span><del>-    this.sizeLimitPerLogFileInBytes = sizeLimitPerFile;
</del><ins>+    this.sizeLimitPerLogFileInBytes = \
rotationParams.sizeLimitPerFileInBytes; +    this.rotationIntervalInMillis = \
rotationParams.rotationInterval; +    this.lastRotationTime = \
rotationParams.lastRotationTime; </ins><span class="cx">     this.referenceCount = 1;
</span><span class="cx"> 
</span><span class="cx">     final ReadWriteLock lock = new \
ReentrantReadWriteLock(false); </span><span class="lines">@@ -376,9 +438,9 @@
</span><span class="cx">         return;
</span><span class="cx">       }
</span><span class="cx">       LogFile&lt;K, V&gt; headLogFile = getHeadLogFile();
</span><del>-      if (headLogFile.getSizeInBytes() &gt; sizeLimitPerLogFileInBytes)
</del><ins>+      if (mustRotate(headLogFile))
</ins><span class="cx">       {
</span><del>-        \
logger.error(INFO_CHANGELOG_LOG_FILE_ROTATION.get(logPath.getPath(), \
headLogFile.getSizeInBytes())); </del><ins>+        \
logger.info(INFO_CHANGELOG_LOG_FILE_ROTATION.get(logPath.getPath(), \
headLogFile.getSizeInBytes())); </ins><span class="cx"> 
</span><span class="cx">         rotateHeadLogFile();
</span><span class="cx">         headLogFile = getHeadLogFile();
</span><span class="lines">@@ -392,6 +454,27 @@
</span><span class="cx">     }
</span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  private boolean mustRotate(LogFile&lt;K, V&gt; headLogFile)
+  {
+    if (lastAppendedKey == null)
+    {
+      // never rotate an empty file
+      return false;
+    }
+    if (headLogFile.getSizeInBytes() &gt; sizeLimitPerLogFileInBytes)
+    {
+      // rotate because file size exceeded threshold
+      return true;
+    }
+    if (rotationIntervalInMillis &gt; 0)
+    {
+      // rotate if time limit is reached
+      final long timeElapsed = timeService.since(lastRotationTime);
+      return timeElapsed &gt; rotationIntervalInMillis;
+    }
+    return false;
+  }
+
</ins><span class="cx">   /**
</span><span class="cx">    * Indicates if the provided record has a key that would \
break the key </span><span class="cx">    * ordering in the log.
</span><span class="lines">@@ -711,6 +794,60 @@
</span><span class="cx">     releaseLog(logPath);
</span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  /**
+   * Find the highest key that corresponds to a record that is the oldest (or
+   * first) of a read-only log file and where value mapped from the record is
+   * lower or equals to provided limit value.
+   * &lt;p&gt;
+   * Example&lt;br&gt;
+   * Given a log with 3 log files, with Record&lt;Int, String&gt; and \
Mapper&lt;String, +   * Long&gt; mapping a string to its long value
+   * &lt;ul&gt;
+   * &lt;li&gt;1_10.log where oldest record is (key=1, \
value=&quot;50&quot;)&lt;/li&gt; +   * &lt;li&gt;11_20.log where oldest record is \
(key=11, value=&quot;150&quot;)&lt;/li&gt; +   * &lt;li&gt;head.log where oldest \
record is (key=25, value=&quot;250&quot;)&lt;/li&gt; +   * &lt;/ul&gt;
+   * Then
+   * &lt;ul&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 20) =&gt; null&lt;/li&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 50) =&gt; 1&lt;/li&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 100) =&gt; 1&lt;/li&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 150) =&gt; 11&lt;/li&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 200) =&gt; 11&lt;/li&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 250) =&gt; 25&lt;/li&gt;
+   * &lt;li&gt;findBoundaryKeyFromRecord(mapper, 300) =&gt; 25&lt;/li&gt;
+   * &lt;/ul&gt;
+   *
+   * @param &lt;V2&gt;
+   *          Type of the value extracted from the record
+   * @param mapper
+   *          The mapper to extract a value from a record. It is expected that
+   *          extracted values are ordered according to an order consistent with
+   *          this log ordering, i.e. for two records, if key(R1) &gt; key(R2) then
+   *          extractedValue(R1) &gt; extractedValue(R2).
+   * @param limitValue
+   *          The limit value to search for
+   * @return the key or {@code null} if no key corresponds
+   * @throws ChangelogException
+   *           If a problem occurs
+   */
+  &lt;V2 extends Comparable&lt;V2&gt;&gt; K \
findBoundaryKeyFromRecord(Record.Mapper&lt;V, V2&gt; mapper, V2 limitValue) +      \
throws ChangelogException +  {
+    K key = null;
+    for (LogFile&lt;K, V&gt; logFile : logFiles.values())
+    {
+      final Record&lt;K, V&gt; record = logFile.getOldestRecord();
+      final V2 oldestValue = mapper.map(record.getValue());
+      if (oldestValue.compareTo(limitValue) &gt; 0)
+      {
+        return key;
+      }
+      key = record.getKey();
+    }
+    return key;
+  }
+
</ins><span class="cx">   /** Effectively close this log. */
</span><span class="cx">   private void doClose()
</span><span class="cx">   {
</span><span class="lines">@@ -766,6 +903,9 @@
</span><span class="cx"> 
</span><span class="cx">     // Re-enable cursors previously opened on head, with the \
saved state </span><span class="cx">     \
updateOpenedCursorsOnHeadAfterRotation(cursorsOnHead); </span><ins>+
+    // Notify even if time-based rotation is not enabled, as it could be enabled at \
any time +    replicationEnv.notifyLogFileRotation(this);
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   private void renameHeadLogFileTo(final File \
rotatedLogFile) throws ChangelogException </span></span></pre></div>
<a id="branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchangelogfileRecordjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Record.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Record.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Record.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -35,6 +35,18 @@
</span><span class="cx">  */
</span><span class="cx"> class Record&lt;K, V&gt;
</span><span class="cx"> {
</span><ins>+  /** Map the record value to another value. */
+  static interface Mapper&lt;V, V2&gt; {
+      /**
+       * Map a record value to another value.
+       *
+       * @param value
+       *          The value to map
+       * @return the new value
+       */
+      V2 map(V value);
+  }
+
</ins><span class="cx">   private final K key;
</span><span class="cx">   private final V value;
</span><span class="cx"> 
</span><span class="cx">Property changes on: \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/Record.java
 </span><span class="cx">___________________________________________________________________
 </span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4>Added: svn:eol-style</h4></div>
<a id="branchesopendj3serverdevsrcserverorgopendsserverreplicationserverchangelogfileReplicationEnvironmentjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -46,13 +46,16 @@
</span><span class="cx"> import java.util.concurrent.CopyOnWriteArrayList;
</span><span class="cx"> import java.util.concurrent.atomic.AtomicBoolean;
</span><span class="cx"> 
</span><ins>+import org.forgerock.i18n.LocalizableMessage;
</ins><span class="cx"> import org.forgerock.i18n.slf4j.LocalizedLogger;
</span><ins>+import org.forgerock.util.time.TimeService;
</ins><span class="cx"> import org.opends.server.replication.common.CSN;
</span><span class="cx"> import org.opends.server.replication.protocol.UpdateMsg;
</span><span class="cx"> import org.opends.server.replication.server.ChangelogState;
</span><span class="cx"> import \
org.opends.server.replication.server.ReplicationServer; </span><span class="cx"> \
import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord; \
</span><span class="cx"> import \
org.opends.server.replication.server.changelog.api.ChangelogException; \
</span><ins>+import org.opends.server.replication.server.changelog.file.Log.LogRotationParameters;
 </ins><span class="cx"> import org.opends.server.types.DN;
</span><span class="cx"> import org.opends.server.types.DirectoryException;
</span><span class="cx"> import org.opends.server.util.StaticUtils;
</span><span class="lines">@@ -67,7 +70,8 @@
</span><span class="cx">  * created :
</span><span class="cx">  * &lt;ul&gt;
</span><span class="cx">  * &lt;li&gt;A &quot;changenumberindex&quot; directory \
containing the log files for </span><del>- * ChangeNumberIndexDB&lt;/li&gt;
</del><ins>+ * ChangeNumberIndexDB, and a file named \
&quot;rotationtime[millis].last&quot; where [millis] is + * the time of the last log \
file rotation in milliseconds&lt;/li&gt; </ins><span class="cx">  * &lt;li&gt;A \
&quot;domains.state&quot; file containing a mapping of each domain DN to an id. The \
</span><span class="cx">  * id is used to name the corresponding domain \
directory.&lt;/li&gt; </span><span class="cx">  * &lt;li&gt;One directory per domain, \
named after &quot;[id].domain&quot; where [id] is the id </span><span \
class="lines">@@ -76,7 +80,7 @@ </span><span class="cx">  * &lt;p&gt;
</span><span class="cx">  * Each domain directory contains the following directories \
and files : </span><span class="cx">  * &lt;ul&gt;
</span><del>- * &lt;li&gt;A &quot;generation_[id].id&quot; file, where [id] is the \
generation id&lt;/li&gt; </del><ins>+ * &lt;li&gt;A &quot;generation[id].id&quot; \
file, where [id] is the generation id&lt;/li&gt; </ins><span class="cx">  * \
&lt;li&gt;One directory per server id, named after &quot;[id].server&quot; where [id] \
is the </span><span class="cx">  * id of the server.&lt;/li&gt;
</span><span class="cx">  * &lt;/ul&gt;
</span><span class="lines">@@ -100,6 +104,7 @@
</span><span class="cx">  * |   \---changenumberindex
</span><span class="cx">  * |      \--- head.log [contains last records written]
</span><span class="cx">  * |      \--- 1_50.log [contains records with keys in \
interval [1, 50]] </span><ins>+ * |      \--- rtime198745512.last
</ins><span class="cx">  * |   \---1.domain
</span><span class="cx">  * |       \---generation1.id
</span><span class="cx">  * |       \---22.server
</span><span class="lines">@@ -119,8 +124,10 @@
</span><span class="cx"> {
</span><span class="cx">   private static final LocalizedLogger logger = \
LocalizedLogger.getLoggerForThisClass(); </span><span class="cx"> 
</span><del>-  private static final long MAX_LOG_FILE_SIZE_IN_BYTES = 10*1024*1024;
</del><ins>+  private static final long CN_INDEX_DB_MAX_LOG_FILE_SIZE_IN_BYTES = 1024 \
* 1024; </ins><span class="cx"> 
</span><ins>+  private static final long REPLICA_DB_MAX_LOG_FILE_SIZE_IN_BYTES = 10 * \
CN_INDEX_DB_MAX_LOG_FILE_SIZE_IN_BYTES; +
</ins><span class="cx">   private static final int NO_GENERATION_ID = -1;
</span><span class="cx"> 
</span><span class="cx">   private static final String CN_INDEX_DB_DIRNAME = \
&quot;changenumberindex&quot;; </span><span class="lines">@@ -129,9 +136,13 @@
</span><span class="cx"> 
</span><span class="cx">   static final String REPLICA_OFFLINE_STATE_FILENAME = \
&quot;offline.state&quot;; </span><span class="cx"> 
</span><ins>+  static final String LAST_ROTATION_TIME_FILE_PREFIX = \
&quot;rotationtime&quot;; +
+  static final String LAST_ROTATION_TIME_FILE_SUFFIX = &quot;.ms&quot;;
+
</ins><span class="cx">   private static final String DOMAIN_STATE_SEPARATOR = \
&quot;:&quot;; </span><span class="cx"> 
</span><del>-  private static final String DOMAIN_SUFFIX = &quot;.domain&quot;;
</del><ins>+  private static final String DOMAIN_SUFFIX = &quot;.dom&quot;;
</ins><span class="cx"> 
</span><span class="cx">   private static final String SERVER_ID_SUFFIX = \
&quot;.server&quot;; </span><span class="cx"> 
</span><span class="lines">@@ -170,6 +181,17 @@
</span><span class="cx">     }
</span><span class="cx">   };
</span><span class="cx"> 
</span><ins>+  private static final FileFilter LAST_ROTATION_TIME_FILE_FILTER = new \
FileFilter() +  {
+    @Override
+    public boolean accept(File file)
+    {
+      return file.isFile()
+          &amp;&amp; file.getName().startsWith(LAST_ROTATION_TIME_FILE_PREFIX)
+          &amp;&amp; file.getName().endsWith(LAST_ROTATION_TIME_FILE_SUFFIX);
+    }
+  };
+
</ins><span class="cx">   /** Root path where the replication log is stored. */
</span><span class="cx">   private final String replicationRootPath;
</span><span class="cx">   /**
</span><span class="lines">@@ -181,10 +203,18 @@
</span><span class="cx">    */
</span><span class="cx">   private final ChangelogState changelogState;
</span><span class="cx"> 
</span><del>-  /** The list of logs that are in use. */
-  private final List&lt;Log&lt;?, ?&gt;&gt; logs = new \
CopyOnWriteArrayList&lt;Log&lt;?, ?&gt;&gt;(); </del><ins>+  /** The list of logs \
that are in use for Replica DBs. */ +  private final List&lt;Log&lt;CSN, \
UpdateMsg&gt;&gt; logsReplicaDB = new CopyOnWriteArrayList&lt;Log&lt;CSN, \
UpdateMsg&gt;&gt;(); </ins><span class="cx"> 
</span><span class="cx">   /**
</span><ins>+   * The list of logs that are in use for the CN Index DB.
+   * There is a single CN Index DB for a ReplicationServer, but there can be \
multiple references opened on it. +   * This is the responsability of Log class to \
handle properly these multiple references. +   */
+  private List&lt;Log&lt;Long, ChangeNumberIndexRecord&gt;&gt; logsCNIndexDB =
+      new CopyOnWriteArrayList&lt;Log&lt;Long, ChangeNumberIndexRecord&gt;&gt;();;
+
+  /**
</ins><span class="cx">    * Maps each domain DN to a domain id that is used to name \
directory in file system. </span><span class="cx">    *
</span><span class="cx">    * @GuardedBy(&quot;domainsLock&quot;)
</span><span class="lines">@@ -205,25 +235,60 @@
</span><span class="cx"> 
</span><span class="cx">   private final AtomicBoolean isShuttingDown = new \
AtomicBoolean(false); </span><span class="cx"> 
</span><ins>+  /** The time service used for timing. */
+  private final TimeService timeService;
+
</ins><span class="cx">   /**
</span><ins>+   * For CN Index DB, a log file can be rotated once it has exceeded a \
given time interval. +   * &lt;p&gt;
+   * It is disabled if the interval is equals to zero.
+   * The interval can be modified at any time.
+   */
+  private long cnIndexDBRotationInterval;
+
+  /**
+   * For CN Index DB, the last time a log file was rotated.
+   * It is persisted to file each time it changes and read at server start. */
+  private long cnIndexDBLastRotationTime;
+
+  /**
</ins><span class="cx">    * Creates the replication environment.
</span><span class="cx">    *
</span><span class="cx">    * @param rootPath
</span><span class="cx">    *          Root path where replication log is stored.
</span><span class="cx">    * @param replicationServer
</span><span class="cx">    *          The underlying replication server.
</span><ins>+   * @param timeService
+   *          Time service to use for timing.
</ins><span class="cx">    * @throws ChangelogException
</span><span class="cx">    *           If an error occurs during initialization.
</span><span class="cx">    */
</span><span class="cx">   ReplicationEnvironment(final String rootPath,
</span><del>-      final ReplicationServer replicationServer) throws \
ChangelogException </del><ins>+      final ReplicationServer replicationServer, final \
TimeService timeService) throws ChangelogException </ins><span class="cx">   {
</span><span class="cx">     this.replicationRootPath = rootPath;
</span><span class="cx">     this.replicationServer = replicationServer;
</span><ins>+    this.timeService = timeService;
</ins><span class="cx">     this.changelogState = readOnDiskChangelogState();
</span><ins>+    this.cnIndexDBLastRotationTime = readOnDiskLastRotationTime();
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   /**
</span><ins>+   * Sets the rotation time interval of a log file for the CN Index DB.
+   *
+   * @param timeInterval
+   *          time interval for rotation of a log file.
+   */
+  void setCNIndexDBRotationInterval(long timeInterval)
+  {
+    cnIndexDBRotationInterval = timeInterval;
+    for (Log&lt;Long, ChangeNumberIndexRecord&gt; log : logsCNIndexDB)
+    {
+      log.setRotationInterval(cnIndexDBRotationInterval);
+    }
+  }
+
+  /**
</ins><span class="cx">    * Returns the state of the replication changelog.
</span><span class="cx">    *
</span><span class="cx">    * @return the {@link ChangelogState} read from the \
changelogState DB </span><span class="lines">@@ -257,6 +322,16 @@
</span><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   /**
</span><ins>+   * Return the last rotation time for CN Index DB log files.
+   *
+   * @return the last rotation time in millis
+   */
+  long getCnIndexDBLastRotationTime()
+  {
+    return cnIndexDBLastRotationTime;
+  }
+
+  /**
</ins><span class="cx">    * Finds or creates the log used to store changes from the \
replication server </span><span class="cx">    * with the given serverId and the \
given baseDN. </span><span class="cx">    *
</span><span class="lines">@@ -299,7 +374,8 @@
</span><span class="cx">         ensureGenerationIdFileExists(generationIdPath);
</span><span class="cx">         changelogState.setDomainGenerationId(domainDN, \
generationId); </span><span class="cx"> 
</span><del>-        return openLog(serverIdPath, FileReplicaDB.RECORD_PARSER);
</del><ins>+        return openLog(serverIdPath, FileReplicaDB.RECORD_PARSER,
+            new LogRotationParameters(REPLICA_DB_MAX_LOG_FILE_SIZE_IN_BYTES, 0, 0), \
logsReplicaDB); </ins><span class="cx">       }
</span><span class="cx">     }
</span><span class="cx">     catch (Exception e)
</span><span class="lines">@@ -325,7 +401,9 @@
</span><span class="cx">     final File path = getCNIndexDBPath();
</span><span class="cx">     try
</span><span class="cx">     {
</span><del>-      return openLog(path, FileChangeNumberIndexDB.RECORD_PARSER);
</del><ins>+      final LogRotationParameters rotationParams = new \
LogRotationParameters(CN_INDEX_DB_MAX_LOG_FILE_SIZE_IN_BYTES, +          \
cnIndexDBRotationInterval, cnIndexDBLastRotationTime); +      return openLog(path, \
FileChangeNumberIndexDB.RECORD_PARSER, rotationParams, logsCNIndexDB); </ins><span \
class="cx">     } </span><span class="cx">     catch (Exception e)
</span><span class="cx">     {
</span><span class="lines">@@ -344,7 +422,8 @@
</span><span class="cx">   {
</span><span class="cx">     if (isShuttingDown.compareAndSet(false, true))
</span><span class="cx">     {
</span><del>-      logs.clear();
</del><ins>+      logsReplicaDB.clear();
+      logsCNIndexDB.clear();
</ins><span class="cx">     }
</span><span class="cx">   }
</span><span class="cx"> 
</span><span class="lines">@@ -409,6 +488,25 @@
</span><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   /**
</span><ins>+   * Notify that log file has been rotated for provided log.
+   *
+   * The last rotation time is persisted to a file and read at startup time.
+   *
+   * @param log
+   *          the log that has a file rotated.
+   * @throws ChangelogException
+   *            If a problem occurs
+   */
+  void notifyLogFileRotation(Log&lt;?, ?&gt; log) throws ChangelogException
+  {
+    // only CN Index DB log rotation time is persisted
+    if (logsCNIndexDB.contains(log))
+    {
+      updateCNIndexDBLastRotationTime(timeService.now());
+    }
+  }
+
+  /**
</ins><span class="cx">    * Notify that the replica corresponding to provided domain \
and provided CSN </span><span class="cx">    * is offline.
</span><span class="cx">    *
</span><span class="lines">@@ -654,16 +752,17 @@
</span><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   /** Open a log from the provided path and record parser. \
*/ </span><del>-  private &lt;K extends Comparable&lt;K&gt;, V&gt; Log&lt;K, V&gt; \
                openLog(final File serverIdPath, final RecordParser&lt;K, V&gt; \
                parser)
-      throws ChangelogException
</del><ins>+  private &lt;K extends Comparable&lt;K&gt;, V&gt; Log&lt;K, V&gt; \
openLog(final File serverIdPath, final RecordParser&lt;K, V&gt; parser, +      \
LogRotationParameters rotationParams, List&lt;Log&lt;K, V&gt;&gt; logsCache) throws \
ChangelogException </ins><span class="cx">   {
</span><span class="cx">     checkShutDownBeforeOpening(serverIdPath);
</span><span class="cx"> 
</span><del>-    final Log&lt;K, V&gt; log = Log.openLog(serverIdPath, parser, \
MAX_LOG_FILE_SIZE_IN_BYTES); </del><ins>+    final Log&lt;K, V&gt; log = \
Log.openLog(this, serverIdPath, parser, rotationParams); </ins><span class="cx"> 
</span><span class="cx">     checkShutDownAfterOpening(serverIdPath, log);
</span><span class="cx"> 
</span><del>-    logs.add(log);
</del><ins>+    logsCache.add(log);
+
</ins><span class="cx">     return log;
</span><span class="cx">   }
</span><span class="cx"> 
</span><span class="lines">@@ -718,6 +817,46 @@
</span><span class="cx">     return (generationIds != null &amp;&amp; \
generationIds.length &gt; 0) ? generationIds[0] : null; </span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  /**
+   * Retrieve the last rotation time from the disk.
+   *
+   * @return the last rotation time in millis (which is the current time if no
+   *         rotation file is found or if a problem occurs).
+   */
+  private long readOnDiskLastRotationTime()
+  {
+    try
+    {
+      final File file = retrieveLastRotationTimeFile();
+      if (file != null)
+      {
+        final String filename = file.getName();
+        final String value = \
filename.substring(LAST_ROTATION_TIME_FILE_PREFIX.length(), +            \
filename.length() - LAST_ROTATION_TIME_FILE_SUFFIX.length()); +        return \
Long.valueOf(value); +      }
+    }
+    catch (Exception e)
+    {
+      logger.trace(LocalizableMessage.raw(&quot;Error when retrieving last log file \
rotation time from file&quot;), e); +    }
+    // Default to current time
+    return timeService.now();
+  }
+
+  /**
+   * Retrieve the file named after the last rotation time from the provided
+   * directory.
+   *
+   * @return the last rotation time file or {@code null} if the corresponding file
+   *         can't be found
+   */
+  private File retrieveLastRotationTimeFile()
+  {
+    File[] files = getCNIndexDBPath().listFiles(LAST_ROTATION_TIME_FILE_FILTER);
+    return (files != null &amp;&amp; files.length &gt; 0) ? files[0] : null;
+  }
+
</ins><span class="cx">   private File getDomainPath(final String domainId)
</span><span class="cx">   {
</span><span class="cx">     return new File(replicationRootPath, domainId + \
DOMAIN_SUFFIX); </span><span class="lines">@@ -748,9 +887,16 @@
</span><span class="cx">     return new File(replicationRootPath, \
CN_INDEX_DB_DIRNAME); </span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  private File getLastRotationTimePath(long lastRotationTime)
+  {
+    return new File(getCNIndexDBPath(),
+        LAST_ROTATION_TIME_FILE_PREFIX + lastRotationTime + \
LAST_ROTATION_TIME_FILE_SUFFIX); +  }
+
</ins><span class="cx">   private void closeLog(final Log&lt;?, ?&gt; log)
</span><span class="cx">   {
</span><del>-    logs.remove(log);
</del><ins>+    logsReplicaDB.remove(log);
+    logsCNIndexDB.remove(log);
</ins><span class="cx">     log.close();
</span><span class="cx">   }
</span><span class="cx"> 
</span><span class="lines">@@ -789,6 +935,30 @@
</span><span class="cx">     }
</span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  private void updateCNIndexDBLastRotationTime(final long \
lastRotationTime) throws ChangelogException { +    final File previousRotationFile = \
retrieveLastRotationTimeFile(); +    final File newRotationFile = \
getLastRotationTimePath(lastRotationTime); +    try
+    {
+      newRotationFile.createNewFile();
+    }
+    catch (IOException e)
+    {
+      throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_CREATE_LAST_LOG_ROTATION_TIME_FILE.get(
 +          newRotationFile.getPath(), lastRotationTime), e);
+    }
+    if (previousRotationFile != null)
+    {
+      final boolean isDeleted = previousRotationFile.delete();
+      if (!isDeleted)
+      {
+        throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_DELETE_LAST_LOG_ROTATION_TIME_FILE.get(
 +            previousRotationFile.getPath()));
+      }
+    }
+    cnIndexDBLastRotationTime = lastRotationTime;
+  }
+
</ins><span class="cx">   private void ensureGenerationIdFileExists(final File \
generationIdPath) </span><span class="cx">       throws ChangelogException
</span><span class="cx">   {
</span></span></pre></div>
<a id="branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverreplicationserverchangelogfileFileChangeNumberIndexDBTestjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDBTest.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDBTest.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileChangeNumberIndexDBTest.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -37,7 +37,6 @@
</span><span class="cx"> import \
org.opends.server.replication.server.changelog.api.ChangelogException; </span><span \
class="cx"> import org.opends.server.replication.server.changelog.api.DBCursor; \
</span><span class="cx"> import org.opends.server.types.DN; </span><del>-import \
org.opends.server.util.StaticUtils; </del><span class="cx"> import \
org.testng.annotations.DataProvider; </span><span class="cx"> import \
org.testng.annotations.Test; </span><span class="cx"> 
</span><span class="lines">@@ -137,23 +136,12 @@
</span><span class="cx">     }
</span><span class="cx">   }
</span><span class="cx"> 
</span><del>-
</del><span class="cx">   /**
</span><del>-   * This test makes basic operations of a ChangeNumberIndexDB:
-   * &lt;ol&gt;
-   * &lt;li&gt;create the db&lt;/li&gt;
-   * &lt;li&gt;add records&lt;/li&gt;
-   * &lt;li&gt;read them with a cursor&lt;/li&gt;
-   * &lt;li&gt;set a very short trim period&lt;/li&gt;
-   * &lt;li&gt;wait for the db to be trimmed / here since the changes are not stored
-   * in the replication changelog, the ChangeNumberIndexDB will be \
                cleared.&lt;/li&gt;
-   * &lt;/ol&gt;
</del><ins>+   * This test verifies purge is working by relying on a very short purge \
delay. +   * The purge can be done only if there is at least one read-only log file, \
so +   * this test ensures the rotation happens, using the rotation based on time.
</ins><span class="cx">    */
</span><del>-  // TODO : this works only if we ensure that there is a rotation of \
                ahead log file
-  // at the right place. First two records are 37 and 76 bytes long,
-  // so it means : 37 &lt; max file size &lt; 113 to have the last record alone in \
                the ahead log file
-  // Re-enable this test when max file size is customizable for log
-  @Test(enabled=false)
</del><ins>+  @Test
</ins><span class="cx">   public void testPurge() throws Exception
</span><span class="cx">   {
</span><span class="cx">     ReplicationServer replicationServer = null;
</span><span class="lines">@@ -163,49 +151,32 @@
</span><span class="cx">       final ChangelogDB changelogDB = \
replicationServer.getChangelogDB(); </span><span class="cx">       \
changelogDB.setPurgeDelay(0); // disable purging </span><span class="cx"> 
</span><del>-      // Prepare data to be stored in the db
-      DN baseDN1 = DN.valueOf(&quot;o=test1&quot;);
-      DN baseDN2 = DN.valueOf(&quot;o=test2&quot;);
-      DN baseDN3 = DN.valueOf(&quot;o=test3&quot;);
-
-      CSN[] csns = generateCSNs(1, 0, 3);
-
</del><span class="cx">       // Add records
</span><ins>+      DN[] baseDNs = { DN.valueOf(&quot;o=test1&quot;), \
DN.valueOf(&quot;o=test2&quot;), DN.valueOf(&quot;o=test3&quot;), \
DN.valueOf(&quot;o=test4&quot;) }; +      CSN[] csns = generateCSNs(1, 0, 4);
</ins><span class="cx">       final FileChangeNumberIndexDB cnIndexDB = \
getCNIndexDB(replicationServer); </span><del>-      long cn1 = addRecord(cnIndexDB, \
                baseDN1, csns[0]);
-                 addRecord(cnIndexDB, baseDN2, csns[1]);
-      long cn3 = addRecord(cnIndexDB, baseDN3, csns[2]);
</del><ins>+      long cn0 = addRecord(cnIndexDB, baseDNs[0], csns[0]);
+                 addRecord(cnIndexDB, baseDNs[1], csns[1]);
+      long cn2 = addRecord(cnIndexDB, baseDNs[2], csns[2]);
</ins><span class="cx"> 
</span><del>-      // The ChangeNumber should not get purged
-      final long oldestCN = cnIndexDB.getOldestRecord().getChangeNumber();
-      assertEquals(oldestCN, cn1);
-      assertEquals(cnIndexDB.getNewestRecord().getChangeNumber(), cn3);
</del><ins>+      // The CN DB should not be purged at this point
+      assertEquals(cnIndexDB.getOldestRecord().getChangeNumber(), cn0);
+      assertEquals(cnIndexDB.getNewestRecord().getChangeNumber(), cn2);
</ins><span class="cx"> 
</span><del>-      DBCursor&lt;ChangeNumberIndexRecord&gt; cursor = \
                cnIndexDB.getCursorFrom(oldestCN);
-      try
-      {
-        assertTrue(cursor.next());
-        assertEqualTo(cursor.getRecord(), csns[0], baseDN1);
-        assertTrue(cursor.next());
-        assertEqualTo(cursor.getRecord(), csns[1], baseDN2);
-        assertTrue(cursor.next());
-        assertEqualTo(cursor.getRecord(), csns[2], baseDN3);
-        assertFalse(cursor.next());
-      }
-      finally
-      {
-        StaticUtils.close(cursor);
-      }
</del><ins>+      // change the purge delay to a very short time
+      changelogDB.setPurgeDelay(5);
+      Thread.sleep(50);
+      // add a new record to force the rotation of the log
+      addRecord(cnIndexDB, baseDNs[3], csns[3]);
</ins><span class="cx"> 
</span><del>-      // Now test that purging removes all changes but the last one
-      changelogDB.setPurgeDelay(1);
</del><ins>+      // Now all changes should have been purged but the last one
</ins><span class="cx">       int count = 0;
</span><span class="cx">       while (cnIndexDB.count() &gt; 1 &amp;&amp; count &lt; \
100) </span><span class="cx">       {
</span><span class="cx">         Thread.sleep(10);
</span><span class="cx">         count++;
</span><span class="cx">       }
</span><del>-      assertOnlyNewestRecordIsLeft(cnIndexDB, 3);
</del><ins>+      assertOnlyNewestRecordIsLeft(cnIndexDB, 4);
</ins><span class="cx">     }
</span><span class="cx">     finally
</span><span class="cx">     {
</span></span></pre></div>
<a id="branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverreplicationserverchangelogfileFileReplicaDBTestjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileReplicaDBTest.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileReplicaDBTest.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/FileReplicaDBTest.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -34,6 +34,7 @@
</span><span class="cx"> import org.forgerock.i18n.slf4j.LocalizedLogger;
</span><span class="cx"> import org.forgerock.opendj.config.server.ConfigException;
</span><span class="cx"> import org.forgerock.opendj.ldap.ByteString;
</span><ins>+import org.forgerock.util.time.TimeService;
</ins><span class="cx"> import org.opends.server.TestCaseUtils;
</span><span class="cx"> import \
org.opends.server.admin.std.meta.ReplicationServerCfgDefn.ReplicationDBImplementation;
 </span><span class="cx"> import \
org.opends.server.admin.std.server.ReplicationServerCfg; </span><span \
class="lines">@@ -423,7 +424,7 @@ </span><span class="cx">       replicationServer = \
configureReplicationServer(100000, 10); </span><span class="cx"> 
</span><span class="cx">       testRoot = createCleanDir();
</span><del>-      dbEnv = new ReplicationEnvironment(testRoot.getPath(), \
replicationServer); </del><ins>+      dbEnv = new \
ReplicationEnvironment(testRoot.getPath(), replicationServer, TimeService.SYSTEM); \
</ins><span class="cx">       replicaDB = new FileReplicaDB(1, TEST_ROOT_DN, \
replicationServer, dbEnv); </span><span class="cx"> 
</span><span class="cx">       // Populate the db with 'max' msg
</span></span></pre></div>
<a id="branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverreplicationserverchangelogfileLogTestjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/LogTest.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/LogTest.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/LogTest.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -26,6 +26,7 @@
</span><span class="cx"> package org.opends.server.replication.server.changelog.file;
</span><span class="cx"> 
</span><span class="cx"> import static org.assertj.core.api.Assertions.*;
</span><ins>+import static org.mockito.Mockito.*;
</ins><span class="cx"> import static \
org.opends.server.replication.server.changelog.api.DBCursor.KeyMatchingStrategy.*; \
</span><span class="cx"> import static \
org.opends.server.replication.server.changelog.api.DBCursor.PositionStrategy.*; \
</span><span class="cx"> import static \
org.opends.server.replication.server.changelog.file.LogFileTest.*; </span><span \
class="lines">@@ -39,7 +40,9 @@ </span><span class="cx"> import \
org.opends.server.replication.server.changelog.api.DBCursor; </span><span class="cx"> \
import org.opends.server.replication.server.changelog.api.DBCursor.KeyMatchingStrategy;
 </span><span class="cx"> import \
org.opends.server.replication.server.changelog.api.DBCursor.PositionStrategy; \
</span><ins>+import org.opends.server.replication.server.changelog.file.Log.LogRotationParameters;
 </ins><span class="cx"> import \
org.opends.server.replication.server.changelog.file.LogFileTest.FailingStringRecordParser;
 </span><ins>+import \
org.opends.server.replication.server.changelog.file.Record.Mapper; </ins><span \
class="cx"> import org.opends.server.util.StaticUtils; </span><span class="cx"> \
import org.testng.annotations.BeforeMethod; </span><span class="cx"> import \
org.testng.annotations.DataProvider; </span><span class="lines">@@ -52,6 +55,8 @@
</span><span class="cx">   // Use a directory dedicated to this test class
</span><span class="cx">   private static final File LOG_DIRECTORY = new \
File(TestCaseUtils.getUnitTestRootPath(), &quot;changelog-unit&quot;); </span><span \
class="cx">  </span><ins>+  private static final long NO_TIME_BASED_LOG_ROTATION = 0;
+
</ins><span class="cx">   @BeforeMethod
</span><span class="cx">   public void initialize() throws Exception
</span><span class="cx">   {
</span><span class="lines">@@ -79,9 +84,12 @@
</span><span class="cx">     // This allow to ensure rotation mechanism is thoroughly \
tested </span><span class="cx">     // Some tests rely on having 2 records per log \
file (especially the purge tests), so take care </span><span class="cx">     // if \
this value has to be changed </span><del>-    int sizeLimitPerFileInBytes = 30;
</del><ins>+    final int sizeLimitPerFileInBytes = 30;
+    final LogRotationParameters rotationParams = new \
LogRotationParameters(sizeLimitPerFileInBytes, +        NO_TIME_BASED_LOG_ROTATION, \
NO_TIME_BASED_LOG_ROTATION); +    final ReplicationEnvironment replicationEnv = \
mock(ReplicationEnvironment.class); </ins><span class="cx"> 
</span><del>-    return Log.openLog(LOG_DIRECTORY, parser, sizeLimitPerFileInBytes);
</del><ins>+    return Log.openLog(replicationEnv, LOG_DIRECTORY, parser, \
rotationParams); </ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">   @Test
</span><span class="lines">@@ -366,8 +374,11 @@
</span><span class="cx">     Log&lt;String, String&gt; writeLog = null;
</span><span class="cx">     try
</span><span class="cx">     {
</span><del>-      long sizeOf1MB = 1024*1024;
-      writeLog = Log.openLog(LOG_DIRECTORY, LogFileTest.RECORD_PARSER, sizeOf1MB);
</del><ins>+      long sizeOf10MB = 10*1024*1024;
+      final LogRotationParameters rotationParams = new \
LogRotationParameters(sizeOf10MB, NO_TIME_BASED_LOG_ROTATION, +          \
NO_TIME_BASED_LOG_ROTATION); +      final ReplicationEnvironment replicationEnv = \
mock(ReplicationEnvironment.class); +      writeLog = Log.openLog(replicationEnv, \
LOG_DIRECTORY, LogFileTest.RECORD_PARSER, rotationParams); </ins><span class="cx"> 
</span><span class="cx">       for (int i = 1; i &lt; 1000000; i++)
</span><span class="cx">       {
</span><span class="lines">@@ -514,12 +525,10 @@
</span><span class="cx">   public void testPurge(String purgeKey, \
Record&lt;String,String&gt; firstRecordExpectedAfterPurge, </span><span class="cx">   \
int cursorStartIndex, int cursorEndIndex) throws Exception </span><span class="cx">   \
{ </span><del>-    Log&lt;String, String&gt; log = null;
</del><ins>+    Log&lt;String, String&gt; log = openLog(LogFileTest.RECORD_PARSER);
</ins><span class="cx">     DBCursor&lt;Record&lt;String, String&gt;&gt; cursor = \
null; </span><span class="cx">     try
</span><span class="cx">     {
</span><del>-      log = openLog(LogFileTest.RECORD_PARSER);
-
</del><span class="cx">       log.purgeUpTo(purgeKey);
</span><span class="cx"> 
</span><span class="cx">       cursor = log.getCursor();
</span><span class="lines">@@ -533,6 +542,51 @@
</span><span class="cx">     }
</span><span class="cx">   }
</span><span class="cx"> 
</span><ins>+  static final Mapper&lt;String, Integer&gt; MAPPER = new \
Record.Mapper&lt;String, Integer&gt;() +      {
+        @Override
+        public Integer map(String value)
+        {
+          // extract numeric value, e.g. from &quot;value10&quot; return 10
+          return Integer.valueOf(value.substring(&quot;value&quot;.length()));
+        }
+      };
+
+  @DataProvider
+  Object[][] findBoundaryKeyData()
+  {
+    return new Object[][] {
+       // limit value, expected key
+       { 0, null },
+       { 1, &quot;key001&quot; },
+       { 2, &quot;key001&quot; },
+       { 3, &quot;key003&quot; },
+       { 4, &quot;key003&quot; },
+       { 5, &quot;key005&quot; },
+       { 6, &quot;key005&quot; },
+       { 7, &quot;key007&quot; },
+       { 8, &quot;key007&quot; },
+       { 9, &quot;key009&quot; },
+       { 10, &quot;key009&quot; },
+       { 11, &quot;key009&quot; },
+       { 12, &quot;key009&quot; },
+    };
+  }
+
+  @Test(dataProvider = &quot;findBoundaryKeyData&quot;)
+  public void testFindBoundaryKeyFromRecord(int limitValue, String expectedKey) \
throws Exception +  {
+    Log&lt;String, String&gt; log = openLog(LogFileTest.RECORD_PARSER);
+    try
+    {
+      assertThat(log.findBoundaryKeyFromRecord(MAPPER, \
limitValue)).isEqualTo(expectedKey); +    }
+    finally
+    {
+      StaticUtils.close(log);
+    }
+  }
+
</ins><span class="cx">   private void \
advanceCursorUpTo(DBCursor&lt;Record&lt;String, String&gt;&gt; cursor, int fromIndex, \
int endIndex) </span><span class="cx">       throws Exception
</span><span class="cx">   {
</span></span></pre></div>
<a id="branchesopendj3serverdevtestsunitteststestngsrcserverorgopendsserverreplicationserverchangelogfileReplicationEnvironmentTestjava"></a>
 <div class="modfile"><h4>Modified: \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironmentTest.java \
(11531 => 11532)</h4> <pre class="diff"><span>
<span class="info">--- \
branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironmentTest.java	2014-12-23 \
                11:18:12 UTC (rev 11531)
+++ branches/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/file/ReplicationEnvironmentTest.java	2014-12-23 \
11:39:41 UTC (rev 11532) </span><span class="lines">@@ -31,12 +31,14 @@
</span><span class="cx"> import java.util.List;
</span><span class="cx"> 
</span><span class="cx"> import org.assertj.core.data.MapEntry;
</span><ins>+import org.forgerock.util.time.TimeService;
</ins><span class="cx"> import org.opends.server.DirectoryServerTestCase;
</span><span class="cx"> import org.opends.server.TestCaseUtils;
</span><span class="cx"> import org.opends.server.replication.common.CSN;
</span><span class="cx"> import org.opends.server.replication.common.CSNGenerator;
</span><span class="cx"> import org.opends.server.replication.protocol.UpdateMsg;
</span><span class="cx"> import org.opends.server.replication.server.ChangelogState;
</span><ins>+import org.opends.server.replication.server.ReplicationServer;
</ins><span class="cx"> import \
org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord; \
</span><span class="cx"> import \
org.opends.server.replication.server.changelog.api.ChangelogException; </span><span \
class="cx"> import org.opends.server.types.DN; </span><span class="lines">@@ -48,6 \
+50,7 @@ </span><span class="cx"> import org.testng.annotations.Test;
</span><span class="cx"> 
</span><span class="cx"> import static org.assertj.core.api.Assertions.*;
</span><ins>+import static org.mockito.Mockito.*;
</ins><span class="cx"> import static \
org.opends.server.replication.server.changelog.file.ReplicationEnvironment.*; \
</span><span class="cx">  </span><span class="cx"> \
@SuppressWarnings(&quot;javadoc&quot;) </span><span class="lines">@@ -94,7 +97,7 @@
</span><span class="cx">     {
</span><span class="cx">       final File rootPath = new \
File(TEST_DIRECTORY_CHANGELOG); </span><span class="cx">       final DN domainDN = \
DN.valueOf(DN1_AS_STRING); </span><del>-      ReplicationEnvironment environment = \
new ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1); </span><span \
class="cx">       replicaDB2 = environment.getOrCreateReplicaDB(domainDN, \
SERVER_ID_2, 1); </span><span class="lines">@@ -122,7 +125,7 @@
</span><span class="cx">     {
</span><span class="cx">       File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
</span><span class="cx">       List&lt;DN&gt; domainDNs = \
Arrays.asList(DN.valueOf(DN1_AS_STRING), DN.valueOf(DN2_AS_STRING), \
DN.valueOf(DN3_AS_STRING)); </span><del>-      ReplicationEnvironment environment = \
new ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
for (int i = 0; i &lt;= 2 ; i++) </span><span class="cx">       {
</span><span class="lines">@@ -163,7 +166,7 @@
</span><span class="cx">     {
</span><span class="cx">       final File rootPath = new \
File(TEST_DIRECTORY_CHANGELOG); </span><span class="cx">       final DN domainDN = \
DN.valueOf(DN1_AS_STRING); </span><del>-      ReplicationEnvironment environment = \
new ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1); </span><span \
class="cx">  </span><span class="lines">@@ -193,7 +196,7 @@
</span><span class="cx">     {
</span><span class="cx">       final File rootPath = new \
File(TEST_DIRECTORY_CHANGELOG); </span><span class="cx">       final DN domainDN = \
DN.valueOf(DN1_AS_STRING); </span><del>-      ReplicationEnvironment environment = \
new ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1); </span><span \
class="cx">  </span><span class="lines">@@ -218,7 +221,7 @@
</span><span class="cx">       final File rootPath = new \
File(TEST_DIRECTORY_CHANGELOG); </span><span class="cx">       final DN domainDN = \
DN.valueOf(DN1_AS_STRING); </span><span class="cx"> 
</span><del>-      ReplicationEnvironment environment = new \
ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1); </span><span \
class="cx">  </span><span class="lines">@@ -249,7 +252,7 @@
</span><span class="cx">       final File rootPath = new \
File(TEST_DIRECTORY_CHANGELOG); </span><span class="cx">       final DN domainDN = \
DN.valueOf(DN1_AS_STRING); </span><span class="cx"> 
</span><del>-      ReplicationEnvironment environment = new \
ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1); </span><span \
class="cx">  </span><span class="lines">@@ -278,7 +281,7 @@
</span><span class="cx">       final File rootPath = new \
File(TEST_DIRECTORY_CHANGELOG); </span><span class="cx">       final DN domainDN = \
DN.valueOf(DN1_AS_STRING); </span><span class="cx"> 
</span><del>-      ReplicationEnvironment environment = new \
ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       cnDB = environment.getOrCreateCNIndexDB(); </span><span class="cx"> \
replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, 1); </span><span \
class="cx">       CSN offlineCSN = new CSN(TimeThread.getTime(), 0, SERVER_ID_1); \
</span><span class="lines">@@ -309,13 +312,13 @@ </span><span class="cx">     {
</span><span class="cx">       File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
</span><span class="cx">       DN domainDN = DN.valueOf(DN1_AS_STRING);
</span><del>-      ReplicationEnvironment environment = new \
ReplicationEnvironment(rootPath.getAbsolutePath(), null); </del><ins>+      \
ReplicationEnvironment environment = createReplicationEnv(rootPath); </ins><span \
class="cx">       replicaDB = environment.getOrCreateReplicaDB(domainDN, SERVER_ID_1, \
1); </span><span class="cx">       replicaDB2 = \
environment.getOrCreateReplicaDB(domainDN, SERVER_ID_2, 1); </span><span class="cx"> 
</span><span class="cx">       // delete the domain directory created for the 2 \
replica DBs to break the </span><span class="cx">       // consistency with domain \
state file </span><del>-      StaticUtils.recursiveDelete(new File(rootPath, \
&quot;1.domain&quot;)); </del><ins>+      StaticUtils.recursiveDelete(new \
File(rootPath, &quot;1.dom&quot;)); </ins><span class="cx"> 
</span><span class="cx">       environment.readOnDiskChangelogState();
</span><span class="cx">     }
</span><span class="lines">@@ -324,4 +327,50 @@
</span><span class="cx">       StaticUtils.close(cnDB, replicaDB, replicaDB2);
</span><span class="cx">     }
</span><span class="cx">   }
</span><ins>+
+  private ReplicationEnvironment createReplicationEnv(File rootPath) throws \
ChangelogException +  {
+    ReplicationServer unusedReplicationServer = null;
+    return new ReplicationEnvironment(rootPath.getAbsolutePath(), \
unusedReplicationServer, TimeService.SYSTEM); +  }
+
+  @Test
+  public void testLastRotationTimeRetrievalWithNoRotationFile() throws Exception
+  {
+    final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
+    TimeService time = mock(TimeService.class);
+    when(time.now()).thenReturn(100L);
+    ReplicationEnvironment environment = new \
ReplicationEnvironment(rootPath.getAbsolutePath(), null, time); +
+    assertThat(environment.getCnIndexDBLastRotationTime()).isEqualTo(100L);
+  }
+
+  @Test
+  public void testLastRotationTimeRetrievalWithRotationFile() throws Exception
+  {
+    final File rootPath = new File(TEST_DIRECTORY_CHANGELOG);
+    final TimeService time = mock(TimeService.class);
+    when(time.now()).thenReturn(100L, 200L);
+    ReplicationEnvironment environment = new \
ReplicationEnvironment(rootPath.getAbsolutePath(), null, time); +    \
Log&lt;Long,ChangeNumberIndexRecord&gt; cnIndexDB = \
environment.getOrCreateCNIndexDB(); +
+    try {
+      environment.notifyLogFileRotation(cnIndexDB);
+
+      // check runtime change of last rotation time is effective
+      // this should also persist the time in a file, but this is checked later in \
the test +      assertThat(environment.getCnIndexDBLastRotationTime()).isEqualTo(200L);
 +    }
+    finally
+    {
+      cnIndexDB.close();
+      environment.shutdown();
+    }
+
+    // now check last rotation time is correctly read from persisted file when \
re-creating environment +    when(time.now()).thenReturn(0L);
+    environment = new ReplicationEnvironment(rootPath.getAbsolutePath(), null, \
time); +    assertThat(environment.getCnIndexDBLastRotationTime()).isEqualTo(200L);
+  }
+
</ins><span class="cx"> }
</span></span></pre>
</div>
</div>
<div id="footer">Copyright (c) by ForgeRock. All rights reserved.</div>

</body>
</html>



_______________________________________________
OpenDJ-dev mailing list
OpenDJ-dev@forgerock.org
https://lists.forgerock.org/mailman/listinfo/opendj-dev


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic