From 6b3ef14a652f6be0d559365d2fd2c78a61524fec Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Fri, 17 Sep 2010 22:06:25 +0000
Subject: [PATCH] Minimize Historical Data (dsreplication/client side). The purge historical can be executed on the local server even when it is stopped. This is matches the functionality provided by utilities such import-ldif, backup, etc.

---
 opends/src/server/org/opends/server/tasks/PurgeConflictsHistoricalTask.java                     |   19 
 opends/resource/bin/dsreplication                                                               |   21 
 opends/resource/bin/_mixed-script.sh                                                            |    6 
 opends/resource/bin/_server-script.bat                                                          |    4 
 opends/resource/bin/_client-script.sh                                                           |    4 
 opends/resource/bin/dsreplication.bat                                                           |   17 
 opends/src/messages/messages/utility.properties                                                 |    2 
 opends/resource/bin/_mixed-script.bat                                                           |    4 
 opends/resource/bin/_client-script.bat                                                          |    4 
 opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalScheduleInformation.java |  157 ++
 opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalUserData.java            |  313 ++++
 opends/src/server/org/opends/server/util/cli/PointAdder.java                                    |  153 ++
 opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliReturnCode.java           |   46 
 opends/src/server/org/opends/server/tools/tasks/TaskScheduleUserData.java                       |  303 ++++
 opends/src/server/org/opends/server/tools/tasks/TaskScheduleInteraction.java                    |  477 ++++++
 opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java                 | 1087 ++++++++++++++
 opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromDirContext.java             |   25 
 opends/src/server/org/opends/server/util/StaticUtils.java                                       |   20 
 opends/src/server/org/opends/server/tools/dsreplication/StatusReplicationUserData.java          |    4 
 opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java                    |    4 
 opends/src/messages/messages/backend.properties                                                 |   14 
 opends/resource/bin/_server-script.sh                                                           |    4 
 opends/src/messages/messages/tools.properties                                                   |   19 
 opends/src/server/org/opends/server/admin/client/cli/TaskScheduleArgs.java                      |  383 +++++
 opends/src/quicksetup/org/opends/quicksetup/UserData.java                                       |    6 
 opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromFile.java                   |    3 
 opends/src/server/org/opends/server/tools/dsreplication/LocalPurgeHistorical.java               |  226 +++
 opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliArgumentParser.java       |  231 +++
 opends/src/server/org/opends/server/tools/tasks/TaskTool.java                                   |  262 --
 opends/src/server/org/opends/server/tools/tasks/TaskClient.java                                 |  121 +
 opends/src/messages/messages/admin_tool.properties                                              |   73 
 opends/src/server/org/opends/server/backends/task/RecurringTask.java                            |  145 +
 opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java               |    7 
 opends/src/server/org/opends/server/util/cli/ConsoleApplication.java                            |  202 +-
 34 files changed, 3,868 insertions(+), 498 deletions(-)

diff --git a/opends/resource/bin/_client-script.bat b/opends/resource/bin/_client-script.bat
index ee00cc2..1babe1b 100644
--- a/opends/resource/bin/_client-script.bat
+++ b/opends/resource/bin/_client-script.bat
@@ -23,7 +23,7 @@
 rem CDDL HEADER END
 rem
 rem
-rem      Copyright 2006-2009 Sun Microsystems, Inc.
+rem      Copyright 2006-2010 Sun Microsystems, Inc.
 
 rem This script is used to invoke various client-side processes.  It should not
 rem be invoked directly by end users.
@@ -55,7 +55,7 @@
 call "%INSTALL_ROOT%\lib\_script-util.bat" %*
 if NOT %errorlevel% == 0 exit /B %errorlevel%
 
-"%OPENDS_JAVA_BIN%" %OPENDS_JAVA_ARGS% %SCRIPT_NAME_ARG% %OPENDS_INVOKE_CLASS% %*
+"%OPENDS_JAVA_BIN%" %OPENDS_JAVA_ARGS% %SCRIPT_ARGS% %SCRIPT_NAME_ARG% %OPENDS_INVOKE_CLASS% %*
 
 :end
 
diff --git a/opends/resource/bin/_client-script.sh b/opends/resource/bin/_client-script.sh
index 1ee6c4e..5d22e9e 100755
--- a/opends/resource/bin/_client-script.sh
+++ b/opends/resource/bin/_client-script.sh
@@ -23,7 +23,7 @@
 # CDDL HEADER END
 #
 #
-#      Copyright 2006-2008 Sun Microsystems, Inc.
+#      Copyright 2006-2010 Sun Microsystems, Inc.
 
 
 # This script is used to invoke various client-side processes.  It should not
@@ -66,4 +66,4 @@
 fi
 
 # Launch the appropriate client utility.
-"${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" "${@}"
+"${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" "${@}"
diff --git a/opends/resource/bin/_mixed-script.bat b/opends/resource/bin/_mixed-script.bat
index d4bc72c..f76ef20 100644
--- a/opends/resource/bin/_mixed-script.bat
+++ b/opends/resource/bin/_mixed-script.bat
@@ -23,7 +23,7 @@
 rem CDDL HEADER END
 rem
 rem
-rem      Copyright 2006-2009 Sun Microsystems, Inc.
+rem      Copyright 2006-2010 Sun Microsystems, Inc.
 
 rem This script is used to invoke various server-side processes.  It should not
 rem be invoked directly by end users.
@@ -90,7 +90,7 @@
 if NOT %errorlevel% == 0 exit /B %errorlevel%
 set SCRIPT_NAME_ARG="-Dorg.opends.server.scriptName=%OLD_SCRIPT_NAME%"
 
-"%OPENDS_JAVA_BIN%" %OPENDS_JAVA_ARGS% %SCRIPT_NAME_ARG% %OPENDS_INVOKE_CLASS% --configClass org.opends.server.extensions.ConfigFileHandler --configFile "%INSTANCE_ROOT%\config\config.ldif" %*
+"%OPENDS_JAVA_BIN%" %OPENDS_JAVA_ARGS% %SCRIPT_ARGS% %SCRIPT_NAME_ARG% %OPENDS_INVOKE_CLASS% --configClass org.opends.server.extensions.ConfigFileHandler --configFile "%INSTANCE_ROOT%\config\config.ldif" %*
 
 goto end
 
diff --git a/opends/resource/bin/_mixed-script.sh b/opends/resource/bin/_mixed-script.sh
index 5b29f66..29b0837 100644
--- a/opends/resource/bin/_mixed-script.sh
+++ b/opends/resource/bin/_mixed-script.sh
@@ -23,7 +23,7 @@
 # CDDL HEADER END
 #
 #
-#      Copyright 2008 Sun Microsystems, Inc.
+#      Copyright 2008-2010 Sun Microsystems, Inc.
 
 
 # This script is used to invoke processes that might be run on server or
@@ -78,7 +78,7 @@
 export SCRIPT_NAME_ARG
 
 # Check whether is local or remote
-"${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" \
+"${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_ARGS}  ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" \
      --configClass org.opends.server.extensions.ConfigFileHandler \
      --configFile "${INSTANCE_ROOT}/config/config.ldif" --testIfOffline "${@}"  
 EC=${?}
@@ -119,7 +119,7 @@
   export SCRIPT_NAME_ARG
   
   # Launch the server utility.
-  "${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" \
+  "${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" \
        --configClass org.opends.server.extensions.ConfigFileHandler \
        --configFile "${INSTANCE_ROOT}/config/config.ldif" "${@}"
 fi
diff --git a/opends/resource/bin/_server-script.bat b/opends/resource/bin/_server-script.bat
index 7b2fa15..e8c07e4 100644
--- a/opends/resource/bin/_server-script.bat
+++ b/opends/resource/bin/_server-script.bat
@@ -23,7 +23,7 @@
 rem CDDL HEADER END
 rem
 rem
-rem      Copyright 2006-2009 Sun Microsystems, Inc.
+rem      Copyright 2006-2010 Sun Microsystems, Inc.
 
 rem This script is used to invoke various server-side processes.  It should not
 rem be invoked directly by end users.
@@ -55,7 +55,7 @@
 call "%INSTALL_ROOT%\lib\_script-util.bat" %*
 if NOT %errorlevel% == 0 exit /B %errorlevel%
 
-"%OPENDS_JAVA_BIN%" %OPENDS_JAVA_ARGS% %SCRIPT_NAME_ARG% %OPENDS_INVOKE_CLASS% --configClass org.opends.server.extensions.ConfigFileHandler --configFile "%INSTANCE_ROOT%\config\config.ldif" %*
+"%OPENDS_JAVA_BIN%" %OPENDS_JAVA_ARGS% %SCRIPT_ARGS% %SCRIPT_NAME_ARG% %OPENDS_INVOKE_CLASS% --configClass org.opends.server.extensions.ConfigFileHandler --configFile "%INSTANCE_ROOT%\config\config.ldif" %*
 
 :end
 
diff --git a/opends/resource/bin/_server-script.sh b/opends/resource/bin/_server-script.sh
index d5ae72c..afb06da 100755
--- a/opends/resource/bin/_server-script.sh
+++ b/opends/resource/bin/_server-script.sh
@@ -23,7 +23,7 @@
 # CDDL HEADER END
 #
 #
-#      Copyright 2006-2008 Sun Microsystems, Inc.
+#      Copyright 2006-2010 Sun Microsystems, Inc.
 
 
 # This script is used to invoke various server-side processes.  It should not
@@ -60,6 +60,6 @@
 fi
 
 # Launch the appropriate server utility.
-"${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" \
+"${OPENDS_JAVA_BIN}" ${OPENDS_JAVA_ARGS} ${SCRIPT_ARGS} ${SCRIPT_NAME_ARG} "${OPENDS_INVOKE_CLASS}" \
      --configClass org.opends.server.extensions.ConfigFileHandler \
      --configFile "${INSTANCE_ROOT}/config/config.ldif" "${@}"
diff --git a/opends/resource/bin/dsreplication b/opends/resource/bin/dsreplication
index e785f31..14f65b6 100644
--- a/opends/resource/bin/dsreplication
+++ b/opends/resource/bin/dsreplication
@@ -23,16 +23,27 @@
 # CDDL HEADER END
 #
 #
-#      Copyright 2008 Sun Microsystems, Inc.
+#      Copyright 2008-2010 Sun Microsystems, Inc.
 
 
 # This script may be used to perform some replication specific operations.
 OPENDS_INVOKE_CLASS="org.opends.server.tools.dsreplication.ReplicationCliMain"
 export OPENDS_INVOKE_CLASS
 
-SCRIPT_NAME="dsreplication"
-export SCRIPT_NAME
-
 SCRIPT_DIR=`dirname "${0}"`
-"${SCRIPT_DIR}/../lib/_client-script.sh" "${@}"
+
+if test "${RECURSIVE_LOCAL_CALL}" = "true"
+then
+  SCRIPT_ARGS=""
+  export SCRIPT_ARGS
+  SCRIPT_NAME="dsreplication.offline"
+  export SCRIPT_NAME
+else
+  SCRIPT_ARGS="-Dorg.opends.server.dsreplicationcallstatus=firstcall"
+  export SCRIPT_ARGS
+  SCRIPT_NAME="dsreplication"
+  export SCRIPT_NAME
+fi
+"${SCRIPT_DIR}/../lib/_server-script.sh" "${@}"
+
 
diff --git a/opends/resource/bin/dsreplication.bat b/opends/resource/bin/dsreplication.bat
index 88e5398..1c4aa4e 100644
--- a/opends/resource/bin/dsreplication.bat
+++ b/opends/resource/bin/dsreplication.bat
@@ -23,10 +23,23 @@
 rem CDDL HEADER END
 rem
 rem
-rem      Copyright 2008 Sun Microsystems, Inc.
+rem      Copyright 2008-2010 Sun Microsystems, Inc.
 
 setlocal
 
 set OPENDS_INVOKE_CLASS="org.opends.server.tools.dsreplication.ReplicationCliMain"
+if "%RECURSIVE_LOCAL_CALL%" == "true" goto callOffline
+goto callOnline
+
+:callOffline
+set SCRIPT_ARGS=
+set SCRIPT_NAME=dsreplication.offline
+goto callScript
+
+:callOnline
+set SCRIPT_ARGS=-Dorg.opends.server.dsreplicationcallstatus=firstcall
 set SCRIPT_NAME=dsreplication
-for %%i in (%~sf0) do call "%%~dPsi\..\lib\_client-script.bat" %*
+goto callScript
+
+:callScript
+for %%i in (%~sf0) do call "%%~dPsi\..\lib\_server-script.bat" %*
diff --git a/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromDirContext.java b/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromDirContext.java
index 789d655..9bde4e1 100644
--- a/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromDirContext.java
+++ b/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromDirContext.java
@@ -96,7 +96,7 @@
 
   private boolean isLocal = true;
 
-  private Map<String, CustomSearchResult> hmConnectionHandlersMonitor =
+  private final Map<String, CustomSearchResult> hmConnectionHandlersMonitor =
     new HashMap<String, CustomSearchResult>();
 
   /**
@@ -784,11 +784,13 @@
    * @param sr the search result.
    * @param searchBaseDN the base search.
    * @param taskEntries the collection of TaskEntries to be updated.
+   * @param ex the list of exceptions to be updated if an error occurs.
    * @throws NamingException if there is an error retrieving the values of the
    * search result.
    */
-  protected void handleTaskSearchResult(SearchResult sr, String searchBaseDN,
-      Collection<TaskEntry> taskEntries)
+  private void handleTaskSearchResult(SearchResult sr,
+      String searchBaseDN,
+      Collection<TaskEntry> taskEntries, List<OpenDsException> ex)
   throws NamingException
   {
     CustomSearchResult csr = new CustomSearchResult(sr, searchBaseDN);
@@ -801,7 +803,7 @@
     }
     catch (OpenDsException ode)
     {
-      exceptions.add(ode);
+      ex.add(ode);
     }
   }
 
@@ -847,7 +849,15 @@
     }
   }
 
-  private void updateTaskInformation(InitialLdapContext ctx,
+  /**
+   * Updates the provided list of TaskEntry with the task entries found in
+   * a server.
+   * @param ctx the connection to the server.
+   * @param ex the list of exceptions encountered while retrieving the task
+   * entries.
+   * @param ts the list of task entries to be updated.
+   */
+  public void updateTaskInformation(InitialLdapContext ctx,
       List<OpenDsException> ex, Collection<TaskEntry> ts)
   {
     // Read monitoring information: since it is computed, it is faster
@@ -870,7 +880,7 @@
         while (taskEntries.hasMore())
         {
           SearchResult sr = taskEntries.next();
-          handleTaskSearchResult(sr, ConfigConstants.DN_TASK_ROOT, ts);
+          handleTaskSearchResult(sr, ConfigConstants.DN_TASK_ROOT, ts, ex);
         }
       }
       finally
@@ -1052,7 +1062,8 @@
     return isConnectionHandler;
   }
 
-  private boolean isTaskEntry(CustomSearchResult csr) throws OpenDsException
+  private static boolean isTaskEntry(CustomSearchResult csr)
+  throws OpenDsException
   {
     boolean isTaskEntry = false;
     List<Object> vs = csr.getAttributeValues("objectclass");
diff --git a/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromFile.java b/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromFile.java
index c4aefb3..c6f0963 100644
--- a/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromFile.java
+++ b/opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromFile.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008-2009 Sun Microsystems, Inc.
+ *      Copyright 2008-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.guitools.controlpanel.util;
@@ -331,6 +331,7 @@
                   if (baseDN.getDn().equals(dn))
                   {
                     baseDN.setType(BaseDNDescriptor.Type.REPLICATED);
+                    baseDN.setReplicaID(domain.getServerId());
                   }
                 }
               }
diff --git a/opends/src/messages/messages/admin_tool.properties b/opends/src/messages/messages/admin_tool.properties
index 44de812..6eb7b08 100644
--- a/opends/src/messages/messages/admin_tool.properties
+++ b/opends/src/messages/messages/admin_tool.properties
@@ -492,7 +492,7 @@
 INFO_DESCRIPTION_INITIALIZE_REPLICATION_SERVER_PORT_DESTINATION=Directory \
  server administration port number of the destination server whose contents will be initialized
 INFO_REPLICATION_TOOL_DESCRIPTION=This utility can be used to configure \
- replication between servers so that the data of the servers is synchronized.\
+ replication between servers so that the data of the servers is synchronized.  \
  For replication to work you must first enable replication using the \
  '%s' subcommand and then initialize the contents of one of \
  the servers with the contents of the other using the '%s' subcommand
@@ -546,6 +546,10 @@
  replication configuration of the base DN's of the servers defined in the \
  registration information.  If no base DN's are specified as parameter the \
  information for all base DN's is displayed
+INFO_DESCRIPTION_SUBCMD_PURGE_HISTORICAL=Launches a purge processing of the \
+ historical informations stored in the user entries by replication. Since this \
+ processing may take a while, you must specify the maximum duration for this \
+ processing.
 SEVERE_ERR_REPLICATION_NO_BASE_DN_PROVIDED=You must provide at least one base \
  DN in no interactive mode.
 SEVERE_ERR_REPLICATION_NO_ADMINISTRATOR_PASSWORD_PROVIDED=You must provide the \
@@ -657,9 +661,9 @@
 MILD_ERR_NO_SUFFIXES_AVAILABLE_TO_INITIALIZE_LOCAL_REPLICATION=There are no \
  base DN's replicated in the server.
 MILD_ERR_REPLICATION_DISABLE_SUFFIXES_NOT_FOUND=The following base DN's could \
- not be found on the server:%n%s
+ not be found in the server:%n%s
 MILD_ERR_REPLICATION_INITIALIZE_LOCAL_SUFFIXES_NOT_FOUND=The following base \
- DN's could not be found on the server:%n%s
+ DN's could not be found in the server:%n%s
 MILD_ERR_NO_SUFFIXES_SELECTED_TO_DISABLE=You must choose at least one \
  base DN to be disabled.
 MILD_ERR_NO_SUFFIXES_SELECTED_TO_INITIALIZE_ALL=You must choose at least one \
@@ -676,7 +680,7 @@
  also the replication server (changelog and replication port) to be disabled \
  you must also specify the '--%s' or '--%s' option.
 INFO_REPLICATION_DISABLE_ALL_SUFFIXES_DISABLE_REPLICATION_SERVER=You have \
- chosen to disable all the replicated base DN's on the server '%s'.  Do you \
+ chosen to disable all the replicated base DN's in the server '%s'.  Do you \
  want to disable also the replication port '%d'?
 INFO_DISABLE_REPLICATION_ONE_POINT_OF_FAILURE=You have decided to disable the \
  replication server (replication changelog).  After disabling the replication \
@@ -933,6 +937,7 @@
 INFO_REPLICATION_POST_EXTERNAL_INITIALIZATION_MENU_PROMPT=Post External \
  Initialization
 INFO_REPLICATION_STATUS_MENU_PROMPT=Display Replication Status
+INFO_REPLICATION_PURGE_HISTORICAL_MENU_PROMPT=Purge Historical
 INFO_REPLICATION_POST_ENABLE_INFO=Replication has been successfully enabled.  \
  Note that for replication to work you must initialize the contents of the \
  base DN's that are being replicated (use %s %s to do so).
@@ -2993,3 +2998,63 @@
 INFO_REQUIRED_ICON_ACCESSIBLE_DESCRIPTION=Required Icon.
 INFO_WARNING_ICON_ACCESSIBLE_DESCRIPTION=Warning Icon.
 INFO_ACCESSIBLE_TABLE_CELL_NAME=%s - Column %s
+INFO_ERROR_DURING_PURGE_HISTORICAL_NO_LOG=Unexpected error during \
+ the operation.  Task state: %s.  Check the error logs of %s for more \
+ information.
+INFO_ERROR_DURING_PURGE_HISTORICAL_LOG=Unexpected error during the \
+ operation.  Last log details: %s.  Task state: %s.  Check the error logs of \
+ %s for more information.
+SEVERE_ERR_POOLING_PURGE_HISTORICAL=Error reading the progress of \
+ the operation.
+INFO_PROGRESS_PURGE_HISTORICAL=Purging historical on base DNs:%s%s
+INFO_PROGRESS_PURGE_HISTORICAL_FINISHED_PROCEDURE=Purge of historical \
+ has been successfully completed
+MILD_ERR_HISTORICAL_CANNOT_BE_PURGED_ON_BASEDN=The following base DN's cannot \
+ be purged because they are not replicated.
+SEVERE_ERR_NO_SUFFIXES_AVAILABLE_TO_PURGE_HISTORICAL=There are no base DN's \
+ available to purge historical.
+MILD_ERR_REPLICATION_PURGE_SUFFIXES_NOT_FOUND=The following base DN's could \
+ not be found in the server:%n%s
+MILD_ERR_NO_SUFFIXES_SELECTED_TO_PURGE_HISTORICAL=You must choose at least one \
+ base DN to be purged from historical.
+INFO_REPLICATION_PURGE_HISTORICAL_PROMPT=Purge historical on base DN %s?
+INFO_REPLICATION_PURGE_HISTORICAL_MAXIMUM_DURATION_PROMPT=Maximum duration for \
+ the historical purge in seconds?
+SEVERE_ERR_LAUNCHING_PURGE_HISTORICAL=Error launching the operation.
+INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_PROMPT=Do you want to execute the \
+ purge on the local server (which is stopped)?
+INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_ENVIRONMENT=Initializing environment \
+ for purge historical
+INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_STARTING=Purging historical started.
+SEVERE_ERR_REPLICATION_PURGE_HISTORICAL_TIMEOUT=The specified maximum time of \
+ %d seconds was elapsed before the purge historical completed.
+SEVERE_ERR_REPLICATION_PURGE_HISTORICAL_EXECUTING=An error occurred executing \
+ the purge historical.  Details: %s.%nYou can check the error logs of the \
+ local server for more information.
+INFO_RUN_TASK_NOW=Launch now
+INFO_RUN_TASK_LATER=Launch later
+INFO_SCHEDULE_TASK=Schedule to run the task periodically
+INFO_TASK_FAILED_DEPENDENCY_ACTION_PROMPT=Which action must this task take if \
+ one if its dependent tasks fails?
+INFO_TASK_HAS_DEPENDENCIES_PROMPT=Has this task a dependency on another task?
+INFO_TASK_DEPENDENCIES_PROMPT=ID of the tasks this task depends on [continue]:
+INFO_HAS_ERROR_NOTIFICATION_PROMPT=Do you want to send an email notification \
+ if this task fails?
+INFO_ERROR_NOTIFICATION_PROMPT=Email addresses to send the error notification \
+ to [continue]:
+MILD_ERR_INVALID_EMAIL_ADDRESS=The value '%s' is not a valid email address.
+INFO_HAS_COMPLETION_NOTIFICATION_PROMPT=Do you want to send an email \
+ notification when this task completes?
+INFO_COMPLETION_NOTIFICATION_PROMPT=Email addresses to send the completion \
+ notification to [continue]:
+INFO_TASK_SCHEDULE_PROMPT=Specify when the task '%s' will be launched.
+INFO_TASK_START_DATE_PROMPT=Launch date (in YYYYMMDDhhmmssZ or YYYYMMDDhhmmss \
+ format):
+INFO_TASK_RECURRING_SCHEDULE_PROMPT=Periodical schedule when the \
+ task must run (in crontab(5) format):
+INFO_PURGE_HISTORICAL_TASK_NAME=Purge historical
+INFO_TASK_SCHEDULE_PROMPT_HEADER=>>>> Specify task scheduling parameters
+SEVERE_ERR_DEPENDENCY_TASK_NOT_DEFINED=There is no task with ID '%s' in the \
+ server.
+INFO_AVAILABLE_DEFINED_TASKS=The available defined tasks are:%s
+
diff --git a/opends/src/messages/messages/backend.properties b/opends/src/messages/messages/backend.properties
index 2c88b48..31c2177 100644
--- a/opends/src/messages/messages/backend.properties
+++ b/opends/src/messages/messages/backend.properties
@@ -20,7 +20,7 @@
 #
 # CDDL HEADER END
 #
-#      Copyright 2006-2009 Sun Microsystems, Inc.
+#      Copyright 2006-2010 Sun Microsystems, Inc.
 
 
 
@@ -1150,3 +1150,15 @@
  to an attribute syntax that is already implemented
 MILD_ERR_SCHEMA_MODIFY_CANNOT_DECODE_LDAP_SYNTAX_418=An error occurred while \
  attempting to decode the ldapsyntax description "%s":  %s
+SEVERE_ERR_RECURRINGTASK_INVALID_N_TOKENS_SIMPLE_419=The provided recurring \
+ task schedule value has an invalid number of tokens
+SEVERE_ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN_SIMPLE_420=The provided \
+ recurring task schedule value has an invalid minute token
+SEVERE_ERR_RECURRINGTASK_INVALID_HOUR_TOKEN_SIMPLE_421=The provided \
+ recurring task schedule value has an invalid hour token
+SEVERE_ERR_RECURRINGTASK_INVALID_DAY_TOKEN_SIMPLE_422=The provided \
+ recurring task schedule value has an invalid day of the month token
+SEVERE_ERR_RECURRINGTASK_INVALID_MONTH_TOKEN_SIMPLE_423=The provided \
+ recurring task schedule value has an invalid month of the year token
+SEVERE_ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN_SIMPLE_424=The provided \
+ recurring task schedule value has an invalid day of the week token
diff --git a/opends/src/messages/messages/tools.properties b/opends/src/messages/messages/tools.properties
index de2e120..59203c0 100644
--- a/opends/src/messages/messages/tools.properties
+++ b/opends/src/messages/messages/tools.properties
@@ -674,7 +674,8 @@
  restarted
 INFO_STOPDS_DESCRIPTION_STOP_TIME_384=Indicates the date/time at which the \
  shutdown operation will begin as a server task expressed in format \
- 'YYYYMMDDhhmmss'.  A value of '0' will cause the shutdown to be scheduled for \
+ YYYYMMDDhhmmssZ for UTC time or YYYYMMDDhhmmss for local time.  A value of \
+ '0' will cause the shutdown to be scheduled for \
  immediate execution.  When this option is specified the operation will be \
  scheduled to start at the specified time after which this utility will exit \
  immediately
@@ -2153,13 +2154,14 @@
 SEVERE_ERR_TASK_CLIENT_TASK_STATE_UNKNOWN_1455=State for task '%s' cannot be \
   determined
 INFO_DESCRIPTION_START_DATETIME_1456=Indicates the date/time at which this \
-  operation will start when scheduled as a server task expressed in format \
-  'YYYYMMDDhhmmss'.  A value of '0' will cause the task to be scheduled for \
+  operation will start when scheduled as a server task expressed in \
+  YYYYMMDDhhmmssZ format for UTC time or YYYYMMDDhhmmss for local time.  A \
+  value of '0' will cause the task to be scheduled for \
   immediate execution.  When this option is specified the operation will be \
   scheduled to start at the specified time after which this utility will exit \
   immediately
-SEVERE_ERR_START_DATETIME_FORMAT_1457=The start date/time must in format \
-  'YYYYMMDDhhmmss'
+SEVERE_ERR_START_DATETIME_FORMAT_1457=The start date/time must in \
+  YYYYMMDDhhmmssZ format for UTC time or YYYYMMDDhhmmss for local time
 INFO_TASK_TOOL_TASK_SCHEDULED_FUTURE_1458=%s task %s scheduled to start %s
 SEVERE_ERR_TASK_TOOL_START_TIME_NO_LDAP_1459=You have provided options for \
   scheduling this operation as a task but options provided for connecting to \
@@ -2574,4 +2576,9 @@
 SEVERE_ERR_CLIENT_SIDE_TIMEOUT_1714=A client side timeout occurred.\
  %nAdditional Information:  %s
 INFO_LABEL_DBTEST_INDEX_UNDEFINED_RECORD_COUNT_1715=Undefined
-
+INFO_MAXIMUM_DURATION_PLACEHOLDER_1716={maximum duration}
+INFO_DESCRIPTION_PURGE_HISTORICAL_MAXIMUM_DURATION_1717=This argument specifies \
+the maximum duration the purge processing must last expressed in seconds
+SEVERE_ERR_RECURRING_SCHEDULE_FORMAT_ERROR_1718=The provided schedule value \
+ has an invalid format.  The schedule must be expressed using a crontab(5) \
+ format.  Error details: %s
diff --git a/opends/src/messages/messages/utility.properties b/opends/src/messages/messages/utility.properties
index 5516881..0f46761 100644
--- a/opends/src/messages/messages/utility.properties
+++ b/opends/src/messages/messages/utility.properties
@@ -621,4 +621,6 @@
  setting file permissions for the LDIF file %s: %s
 MILD_ERR_LDIF_READ_ATTR_SKIP_301=Skipping entry %s because the following error \
 was received when reading its attributes: %s
+SEVERE_ERR_LDAP_CONN_BAD_INTEGER_302=Invalid integer number "%s". Please \
+  enter a valid integer
 
diff --git a/opends/src/quicksetup/org/opends/quicksetup/UserData.java b/opends/src/quicksetup/org/opends/quicksetup/UserData.java
index 7cbbaa9..255a8d1 100644
--- a/opends/src/quicksetup/org/opends/quicksetup/UserData.java
+++ b/opends/src/quicksetup/org/opends/quicksetup/UserData.java
@@ -84,7 +84,8 @@
 
   private SuffixesToReplicateOptions suffixesToReplicateOptions;
 
-  private Map<ServerDescriptor, AuthenticationData> remoteWithNoReplicationPort;
+  private final Map<ServerDescriptor, AuthenticationData>
+  remoteWithNoReplicationPort;
 
   private boolean quiet;
 
@@ -880,7 +881,8 @@
   {
     return new String[]
     {
-        "backup.offline", "encode-password", "export-ldif.offline",
+        "backup.offline", "dsreplication.offline",
+        "encode-password", "export-ldif.offline",
         IMPORT_SCRIPT_NAME, "ldif-diff", "ldifmodify", "ldifsearch",
         "make-ldif", "rebuild-index", "restore.offline", SERVER_SCRIPT_NAME,
         "upgrade", "verify-index", "dbtest"
diff --git a/opends/src/server/org/opends/server/admin/client/cli/TaskScheduleArgs.java b/opends/src/server/org/opends/server/admin/client/cli/TaskScheduleArgs.java
new file mode 100644
index 0000000..6245867
--- /dev/null
+++ b/opends/src/server/org/opends/server/admin/client/cli/TaskScheduleArgs.java
@@ -0,0 +1,383 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.client.cli;
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.server.backends.task.FailedDependencyAction;
+import org.opends.server.backends.task.RecurringTask;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.util.StaticUtils;
+import org.opends.server.util.args.Argument;
+import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.args.StringArgument;
+import org.opends.server.util.cli.CLIException;
+
+/**
+ * A class that contains all the arguments related to the task scheduling.
+ *
+ */
+public class TaskScheduleArgs
+{
+  /**
+   * Magic value used to indicate that the user would like to schedule
+   * this operation to run immediately as a task as opposed to running
+   * the operation in the local VM.
+   */
+  public static final String NOW = "0";
+  /**
+   *  Argument for describing the task's start time.
+   */
+  public StringArgument startArg;
+
+  /**
+   *  Argument to indicate a recurring task.
+   */
+  public StringArgument recurringArg;
+
+  /**
+   *  Argument for specifying completion notifications.
+   */
+  public StringArgument completionNotificationArg;
+
+  /**
+   *  Argument for specifying error notifications.
+   */
+  public StringArgument errorNotificationArg;
+
+  /**
+   *  Argument for specifying dependency.
+   */
+  public StringArgument dependencyArg;
+
+  /**
+   *  Argument for specifying a failed dependency action.
+   */
+  public StringArgument failedDependencyActionArg;
+
+  /**
+   * Default constructor.
+   */
+  public TaskScheduleArgs()
+  {
+    try
+    {
+     createTaskArguments();
+    }
+    catch (ArgumentException ae)
+    {
+      // This is a bug.
+      throw new RuntimeException("Unexpected error: "+ae, ae);
+    }
+  }
+
+  private void createTaskArguments() throws ArgumentException
+  {
+    startArg = new StringArgument(OPTION_LONG_START_DATETIME,
+        OPTION_SHORT_START_DATETIME, OPTION_LONG_START_DATETIME, false, false,
+        true, INFO_START_DATETIME_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_START_DATETIME.get());
+
+    recurringArg = new StringArgument(OPTION_LONG_RECURRING_TASK,
+        OPTION_SHORT_RECURRING_TASK, OPTION_LONG_RECURRING_TASK, false, false,
+        true, INFO_RECURRING_TASK_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_RECURRING_TASK.get());
+
+    completionNotificationArg = new StringArgument(
+        OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
+        OPTION_SHORT_COMPLETION_NOTIFICATION_EMAIL,
+        OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL, false, true, true,
+        INFO_EMAIL_ADDRESS_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TASK_COMPLETION_NOTIFICATION.get());
+
+    errorNotificationArg = new StringArgument(
+        OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
+        OPTION_SHORT_ERROR_NOTIFICATION_EMAIL,
+        OPTION_LONG_ERROR_NOTIFICATION_EMAIL, false, true, true,
+        INFO_EMAIL_ADDRESS_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TASK_ERROR_NOTIFICATION.get());
+
+    dependencyArg = new StringArgument(OPTION_LONG_DEPENDENCY,
+        OPTION_SHORT_DEPENDENCY, OPTION_LONG_DEPENDENCY, false, true, true,
+        INFO_TASK_ID_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TASK_DEPENDENCY_ID.get());
+
+    Set<FailedDependencyAction> fdaValSet =
+      EnumSet.allOf(FailedDependencyAction.class);
+    failedDependencyActionArg = new StringArgument(
+        OPTION_LONG_FAILED_DEPENDENCY_ACTION,
+        OPTION_SHORT_FAILED_DEPENDENCY_ACTION,
+        OPTION_LONG_FAILED_DEPENDENCY_ACTION, false, true, true,
+        INFO_ACTION_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TASK_FAILED_DEPENDENCY_ACTION.get(StaticUtils
+            .collectionToString(fdaValSet, ","), FailedDependencyAction
+            .defaultValue().name()));
+
+    for (Argument arg : getArguments())
+    {
+      arg.setPropertyName(arg.getLongIdentifier());
+    }
+  }
+
+  /**
+   * Returns all the task schedule related arguments.
+   * @return all the task schedule related arguments.
+   */
+  public Argument[] getArguments()
+  {
+    return new Argument[] {startArg, recurringArg, completionNotificationArg,
+       errorNotificationArg, dependencyArg, failedDependencyActionArg};
+  }
+
+  /**
+   * Validates arguments related to task scheduling.  This should be
+   * called after the <code>ArgumentParser.parseArguments</code> has
+   * been called.
+   * <br>
+   * Note that this method does only validation that is not dependent on whether
+   * the operation will be launched as a task or not.  If the operation is not
+   * to be launched as a task, the method {@link #validateArgsIfOffline()}
+   * should be called instead of this method.
+   * @throws ArgumentException if there is a problem with the arguments.
+   * @throws CLIException if there is a problem with one of the values provided
+   * by the user.
+   */
+  public void validateArgs()
+  throws ArgumentException, CLIException {
+    if (startArg.isPresent() && !NOW.equals(startArg.getValue())) {
+      try {
+        Date date = StaticUtils.parseDateTimeString(startArg.getValue());
+        // Check that the provided date is not previous to the current date.
+        Date currentDate = new Date(System.currentTimeMillis());
+        if (currentDate.after(date))
+        {
+          throw new CLIException(ERR_START_DATETIME_ALREADY_PASSED.get(
+              startArg.getValue()));
+        }
+      } catch (ParseException pe) {
+        throw new ArgumentException(ERR_START_DATETIME_FORMAT.get());
+      }
+    }
+
+    if (recurringArg.isPresent())
+    {
+      try
+      {
+        RecurringTask.parseTaskTab(recurringArg.getValue());
+      }
+      catch (DirectoryException de)
+      {
+        throw new ArgumentException(ERR_RECURRING_SCHEDULE_FORMAT_ERROR.get(
+            de.getMessageObject()), de);
+      }
+    }
+
+    if (completionNotificationArg.isPresent()) {
+      LinkedList<String> addrs = completionNotificationArg.getValues();
+      for (String addr : addrs) {
+        if (!StaticUtils.isEmailAddress(addr)) {
+          throw new ArgumentException(ERR_TASKTOOL_INVALID_EMAIL_ADDRESS.get(
+                  addr, completionNotificationArg.getLongIdentifier()));
+        }
+      }
+    }
+
+    if (errorNotificationArg.isPresent()) {
+      LinkedList<String> addrs = errorNotificationArg.getValues();
+      for (String addr : addrs) {
+        if (!StaticUtils.isEmailAddress(addr)) {
+          throw new ArgumentException(ERR_TASKTOOL_INVALID_EMAIL_ADDRESS.get(
+                  addr, errorNotificationArg.getLongIdentifier()));
+        }
+      }
+    }
+
+    if (failedDependencyActionArg.isPresent()) {
+
+      if (!dependencyArg.isPresent()) {
+        throw new ArgumentException(ERR_TASKTOOL_FDA_WITH_NO_DEPENDENCY.get());
+      }
+
+      String fda = failedDependencyActionArg.getValue();
+      if (null == FailedDependencyAction.fromString(fda)) {
+        Set<FailedDependencyAction> fdaValSet =
+          EnumSet.allOf(FailedDependencyAction.class);
+        throw new ArgumentException(ERR_TASKTOOL_INVALID_FDA.get(fda,
+                        StaticUtils.collectionToString(fdaValSet, ",")));
+      }
+    }
+  }
+
+  /**
+   * Validates arguments related to task scheduling.  This should be
+   * called after the <code>ArgumentParser.parseArguments</code> has
+   * been called.
+   * <br>
+   * This method assumes that the operation is not to be launched as a task.
+   * This method covers all the checks done by {@link #validateArgs()}, so it
+   * is not necessary to call that method if this method is being called.
+   * @throws ArgumentException if there is a problem with the arguments.
+   * @throws CLIException if there is a problem with one of the values provided
+   * by the user.
+   */
+  public void validateArgsIfOffline()
+  throws ArgumentException, CLIException
+  {
+    Argument[] incompatibleArgs = {startArg, recurringArg,
+        completionNotificationArg,
+        errorNotificationArg, dependencyArg, failedDependencyActionArg};
+    for (Argument arg : incompatibleArgs)
+    {
+      if (arg.isPresent()) {
+        throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
+                arg.getLongIdentifier()));
+      }
+    }
+    validateArgs();
+  }
+
+  /**
+   * Gets the date at which the associated task should be scheduled to start.
+   *
+   * @return date/time at which the task should be scheduled
+   */
+  public Date getStartDateTime() {
+    Date start = null;
+
+    // If the start time arg is present parse its value
+    if (startArg != null && startArg.isPresent()) {
+      if (NOW.equals(startArg.getValue())) {
+        start = new Date();
+      } else {
+        try {
+          start = StaticUtils.parseDateTimeString(startArg.getValue());
+        } catch (ParseException pe) {
+          // ignore; validated in validateTaskArgs()
+        }
+      }
+    }
+    return start;
+  }
+
+  /**
+   * Whether the arguments provided by the user, indicate that the task should
+   * be executed immediately.
+   * <br>
+   * This method assumes that the arguments have already been parsed and
+   * validated.
+   * @return {@code true} if the task must be executed immediately and
+   * {@code false} otherwise.
+   */
+  public boolean isStartNow()
+  {
+    boolean isStartNow = true;
+    if (startArg != null && startArg.isPresent()) {
+      isStartNow = NOW.equals(startArg.getValue());
+    }
+    return isStartNow;
+  }
+
+  /**
+   * Gets the date/time pattern for recurring task schedule.
+   *
+   * @return recurring date/time pattern at which the task
+   *         should be scheduled.
+   */
+  public String getRecurringDateTime() {
+    String pattern = null;
+
+    // If the recurring task arg is present parse its value
+    if (recurringArg != null && recurringArg.isPresent()) {
+      pattern = recurringArg.getValue();
+    }
+    return pattern;
+  }
+
+  /**
+   * Gets a list of task IDs upon which the associated task is dependent.
+   *
+   * @return list of task IDs
+   */
+  public List<String> getDependencyIds() {
+    if (dependencyArg.isPresent()) {
+      return dependencyArg.getValues();
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+  /**
+   * Gets the action to take should one of the dependent task fail.
+   *
+   * @return action to take
+   */
+  public FailedDependencyAction getFailedDependencyAction() {
+    FailedDependencyAction fda = null;
+    if (failedDependencyActionArg.isPresent()) {
+      String fdaString = failedDependencyActionArg.getValue();
+      fda = FailedDependencyAction.fromString(fdaString);
+    }
+    return fda;
+  }
+
+  /**
+   * Gets a list of email address to which an email will be sent when this
+   * task completes.
+   *
+   * @return list of email addresses
+   */
+  public List<String> getNotifyUponCompletionEmailAddresses() {
+    if (completionNotificationArg.isPresent()) {
+      return completionNotificationArg.getValues();
+    } else {
+      return Collections.emptyList();
+    }
+  }
+
+  /**
+   * Gets a list of email address to which an email will be sent if this
+   * task encounters an error during execution.
+   *
+   * @return list of email addresses
+   */
+  public List<String> getNotifyUponErrorEmailAddresses() {
+    if (errorNotificationArg.isPresent()) {
+      return errorNotificationArg.getValues();
+    } else {
+      return Collections.emptyList();
+    }
+  }
+}
diff --git a/opends/src/server/org/opends/server/backends/task/RecurringTask.java b/opends/src/server/org/opends/server/backends/task/RecurringTask.java
index 16b4ed9..c58a9d6 100644
--- a/opends/src/server/org/opends/server/backends/task/RecurringTask.java
+++ b/opends/src/server/org/opends/server/backends/task/RecurringTask.java
@@ -64,6 +64,9 @@
 /**
  * This class defines a information about a recurring task, which will be used
  * to repeatedly schedule tasks for processing.
+ * <br>
+ * It also provides some static methods that allow to validate strings in
+ * crontab (5) format.
  */
 public class RecurringTask
 {
@@ -102,6 +105,12 @@
    */
   private static enum TaskTab {MINUTE, HOUR, DAY, MONTH, WEEKDAY};
 
+  private final static int MINUTE_INDEX = 0;
+  private final static int HOUR_INDEX = 1;
+  private final static int DAY_INDEX = 2;
+  private final static int MONTH_INDEX = 3;
+  private final static int WEEKDAY_INDEX = 4;
+
   // Exact match pattern.
   private static final Pattern exactPattern =
     Pattern.compile("\\d+");
@@ -115,11 +124,11 @@
     Pattern.compile("^(\\d+,)(.*)(\\d+)$");
 
   // Boolean arrays holding task tab slots.
-  private boolean[] minutesArray;
-  private boolean[] hoursArray;
-  private boolean[] daysArray;
-  private boolean[] monthArray;
-  private boolean[] weekdayArray;
+  private final boolean[] minutesArray;
+  private final boolean[] hoursArray;
+  private final boolean[] daysArray;
+  private final boolean[] monthArray;
+  private final boolean[] weekdayArray;
 
   /**
    * Creates a new recurring task based on the information in the provided
@@ -225,7 +234,16 @@
     }
 
     String taskScheduleTab = value.toString();
-    parseTaskTab(taskScheduleTab);
+
+    boolean[][] taskArrays = new boolean[][]{null, null, null, null, null};
+
+    parseTaskTab(taskScheduleTab, taskArrays, true);
+
+    minutesArray = taskArrays[MINUTE_INDEX];
+    hoursArray = taskArrays[HOUR_INDEX];
+    daysArray = taskArrays[DAY_INDEX];
+    monthArray = taskArrays[MONTH_INDEX];
+    weekdayArray = taskArrays[WEEKDAY_INDEX];
 
     // Get the class name from the entry.  If there isn't one, then fail.
     attrType = DirectoryServer.getAttributeType(
@@ -451,14 +469,41 @@
    * @param taskSchedule recurring task schedule tab in crontab(5) format.
    * @throws DirectoryException to indicate an error.
    */
-  private void parseTaskTab(String taskSchedule) throws DirectoryException
+  public static void parseTaskTab(String taskSchedule) throws DirectoryException
+  {
+    parseTaskTab(taskSchedule, new boolean[][]{null, null, null, null, null},
+        false);
+  }
+
+  /**
+   * Parse and validate recurring task schedule.
+   * @param taskSchedule recurring task schedule tab in crontab(5) format.
+   * @param arrays, an array of 5 boolean arrays.  The array has the following
+   * structure: {minutesArray, hoursArray, daysArray, monthArray, weekdayArray}.
+   * @param referToTaskEntryAttribute whether the error messages must refer
+   * to the task entry attribute or not.  This is used to have meaningful
+   * messages when the {@link #parseTaskTab(String)} is called to validate
+   * a crontab formatted string.
+   * @throws DirectoryException to indicate an error.
+   */
+  private static void parseTaskTab(String taskSchedule,
+      boolean[][] arrays,
+      boolean referToTaskEntryAttribute) throws DirectoryException
   {
     StringTokenizer st = new StringTokenizer(taskSchedule);
 
     if (st.countTokens() != TASKTAB_NUM_TOKENS) {
-      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-        ERR_RECURRINGTASK_INVALID_N_TOKENS.get(
-        ATTR_RECURRING_TASK_SCHEDULE));
+      if (referToTaskEntryAttribute)
+      {
+        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+            ERR_RECURRINGTASK_INVALID_N_TOKENS.get(
+                ATTR_RECURRING_TASK_SCHEDULE));
+      }
+      else
+      {
+        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+            ERR_RECURRINGTASK_INVALID_N_TOKENS_SIMPLE.get());
+      }
     }
 
     for (TaskTab taskTabToken : TaskTab.values()) {
@@ -466,47 +511,87 @@
       switch (taskTabToken) {
         case MINUTE:
           try {
-            minutesArray = parseTaskTabField(token, 0, 59);
+            arrays[MINUTE_INDEX] = parseTaskTabField(token, 0, 59);
           } catch (IllegalArgumentException e) {
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-              ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN.get(
-              ATTR_RECURRING_TASK_SCHEDULE));
+            if (referToTaskEntryAttribute)
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN.get(
+                      ATTR_RECURRING_TASK_SCHEDULE));
+            }
+            else
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN_SIMPLE.get());
+            }
           }
           break;
         case HOUR:
           try {
-            hoursArray = parseTaskTabField(token, 0, 23);
+            arrays[HOUR_INDEX] = parseTaskTabField(token, 0, 23);
           } catch (IllegalArgumentException e) {
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-              ERR_RECURRINGTASK_INVALID_HOUR_TOKEN.get(
-              ATTR_RECURRING_TASK_SCHEDULE));
+            if (referToTaskEntryAttribute)
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_HOUR_TOKEN.get(
+                      ATTR_RECURRING_TASK_SCHEDULE));
+            }
+            else
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_HOUR_TOKEN_SIMPLE.get());
+            }
           }
           break;
         case DAY:
           try {
-            daysArray = parseTaskTabField(token, 1, 31);
+            arrays[DAY_INDEX] = parseTaskTabField(token, 1, 31);
           } catch (IllegalArgumentException e) {
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-              ERR_RECURRINGTASK_INVALID_DAY_TOKEN.get(
-              ATTR_RECURRING_TASK_SCHEDULE));
+            if (referToTaskEntryAttribute)
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_DAY_TOKEN.get(
+                      ATTR_RECURRING_TASK_SCHEDULE));
+            }
+            else
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_DAY_TOKEN_SIMPLE.get());
+            }
           }
           break;
         case MONTH:
           try {
-            monthArray = parseTaskTabField(token, 1, 12);
+            arrays[MONTH_INDEX] = parseTaskTabField(token, 1, 12);
           } catch (IllegalArgumentException e) {
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-              ERR_RECURRINGTASK_INVALID_MONTH_TOKEN.get(
-              ATTR_RECURRING_TASK_SCHEDULE));
+            if (referToTaskEntryAttribute)
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_MONTH_TOKEN.get(
+                      ATTR_RECURRING_TASK_SCHEDULE));
+            }
+            else
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_MONTH_TOKEN_SIMPLE.get());
+            }
           }
           break;
         case WEEKDAY:
           try {
-            weekdayArray = parseTaskTabField(token, 0, 6);
+            arrays[WEEKDAY_INDEX] = parseTaskTabField(token, 0, 6);
           } catch (IllegalArgumentException e) {
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-              ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN.get(
-              ATTR_RECURRING_TASK_SCHEDULE));
+            if (referToTaskEntryAttribute)
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN.get(
+                      ATTR_RECURRING_TASK_SCHEDULE));
+            }
+            else
+            {
+              throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN_SIMPLE.get());
+            }
           }
           break;
       }
diff --git a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index f4bc7a4..5ac41d8 100644
--- a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -5680,7 +5680,9 @@
          attrs, null);
 
      int count = 0;
-     task.setProgressStats(lastChangeNumberPurgedFromHist, count);
+
+     if (task != null)
+       task.setProgressStats(lastChangeNumberPurgedFromHist, count);
 
      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
      for (SearchResultEntry entry : entries)
@@ -5731,7 +5733,8 @@
        }
        else
        {
-         task.setProgressStats(lastChangeNumberPurgedFromHist, count);
+         if (task != null)
+           task.setProgressStats(lastChangeNumberPurgedFromHist, count);
        }
      }
    }
diff --git a/opends/src/server/org/opends/server/tasks/PurgeConflictsHistoricalTask.java b/opends/src/server/org/opends/server/tasks/PurgeConflictsHistoricalTask.java
index f2cb1f0..b45a9e0 100644
--- a/opends/src/server/org/opends/server/tasks/PurgeConflictsHistoricalTask.java
+++ b/opends/src/server/org/opends/server/tasks/PurgeConflictsHistoricalTask.java
@@ -62,6 +62,11 @@
 public class PurgeConflictsHistoricalTask extends Task
 {
   /**
+   * The default value for the maximum duration of the purge expressed in
+   * seconds.
+   */
+  public static final int DEFAULT_MAX_DURATION = 60 * 60;
+  /**
    * The tracer object for the debug logger.
    */
   private static final DebugTracer TRACER = getTracer();
@@ -69,14 +74,6 @@
   private String  domainString = null;
   private LDAPReplicationDomain domain = null;
 
-  // The last changeNumber purged : help the user to know how well the purge
-  // processing has done its job.
-  // We want to help the user know:
-  // - the task has started at dateX time, will end at dateY max
-  //   and is currently purging dateZ
-  long currentCNPurgedDate = 0;
-  long taskMaxEndDate = 0;
-
   /**
    *                 current historical purge delay
    *                <--------------------------------->
@@ -95,7 +92,7 @@
    *
    */
 
-  long purgeTaskMaxDurationInSec = 3600; // Default:1h
+  private int purgeTaskMaxDurationInSec = DEFAULT_MAX_DURATION;
 
   TaskState initState;
 
@@ -112,6 +109,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public Message getDisplayName() {
     return TaskMessages.INFO_TASK_PURGE_CONFLICTS_HIST_NAME.get();
   }
@@ -162,7 +160,7 @@
     {
       try
       {
-        purgeTaskMaxDurationInSec = Long.decode(maxDurationStringInSec);
+        purgeTaskMaxDurationInSec = Integer.decode(maxDurationStringInSec);
       }
       catch(Exception e)
       {
@@ -177,6 +175,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   protected TaskState runTask()
   {
     Boolean purgeCompletedInTime = false;
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/LocalPurgeHistorical.java b/opends/src/server/org/opends/server/tools/dsreplication/LocalPurgeHistorical.java
new file mode 100644
index 0000000..dec2a8a
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/dsreplication/LocalPurgeHistorical.java
@@ -0,0 +1,226 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.dsreplication;
+
+import static org.opends.messages.AdminToolMessages.*;
+import static org.opends.messages.CoreMessages.*;
+
+import java.io.File;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.opends.messages.Message;
+import org.opends.quicksetup.util.ProgressMessageFormatter;
+import org.opends.server.replication.plugin.LDAPReplicationDomain;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryEnvironmentConfig;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.OpenDsException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.EmbeddedUtils;
+import org.opends.server.util.StaticUtils;
+import org.opends.server.util.TimeThread;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.PointAdder;
+
+/**
+ * The class that is in charge of taking the different information provided
+ * by the user through the command-line and actually executing the local
+ * purge.
+ *
+ */
+public class LocalPurgeHistorical
+{
+  private static final Logger LOG =
+    Logger.getLogger(LocalPurgeHistorical.class.getName());
+
+  private final PurgeHistoricalUserData uData;
+  private final ConsoleApplication app;
+  private final ProgressMessageFormatter formatter;
+  private final String configFile;
+  private final String configClass;
+
+  /**
+   * The default constructor.
+   * @param uData the object containing the information provided by the user.
+   * @param app the console application that is used to write the progress
+   * and error messages.
+   * @param formatter the formatter to be used to generated the messages.
+   * @param configFile the file that contains the configuration.  This is
+   * required to initialize properly the server.
+   * @param configClass the class to be used to read the configuration.  This is
+   * required to initialize properly the server.
+   */
+  public LocalPurgeHistorical(PurgeHistoricalUserData uData,
+      ConsoleApplication app,
+      ProgressMessageFormatter formatter, String configFile,
+      String configClass)
+  {
+    this.uData = uData;
+    this.app = app;
+    this.formatter = formatter;
+    this.configFile = configFile;
+    this.configClass = configClass;
+  }
+
+  /**
+   * Executes the purge historical operation locally.
+   * @return the result code.
+   */
+  public ReplicationCliReturnCode execute()
+  {
+    boolean applyTimeout = uData.getMaximumDuration() > 0;
+
+    long startTime = TimeThread.getTime();
+    long purgeMaxTime = getTimeoutInSeconds() * 1000;
+
+    long endMaxTime = startTime + purgeMaxTime;
+
+    app.printProgress(formatter.getFormattedProgress(
+        INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_ENVIRONMENT.get()));
+
+    PointAdder pointAdder = new PointAdder(app);
+    pointAdder.start();
+
+    LDAPReplicationDomain domain = null;
+    Class<?> cfgClass;
+
+    try
+    {
+      cfgClass = Class.forName(configClass);
+    }
+    catch (Exception e)
+    {
+      pointAdder.stop();
+      Message message =
+        ERR_CANNOT_LOAD_CONFIG_HANDLER_CLASS.get(
+            configClass, StaticUtils.stackTraceToSingleLineString(e));
+      app.println(message);
+      LOG.log(Level.SEVERE, "Error loading configuration class "+configClass+
+          ": "+e, e);
+      return ReplicationCliReturnCode.ERROR_LOCAL_PURGE_HISTORICAL_CLASS_LOAD;
+    }
+
+    try
+    {
+      // Create a configuration for the server.
+      DirectoryEnvironmentConfig environmentConfig =
+        new DirectoryEnvironmentConfig();
+      environmentConfig.setConfigClass(cfgClass);
+      environmentConfig.setConfigFile(new File(configFile));
+      environmentConfig.setDisableConnectionHandlers(true);
+      EmbeddedUtils.startServer(environmentConfig);
+    }
+    catch (OpenDsException ode)
+    {
+      pointAdder.stop();
+      Message message = ode.getMessageObject();
+        ERR_CANNOT_LOAD_CONFIG_HANDLER_CLASS.get(
+            configClass, StaticUtils.stackTraceToSingleLineString(ode));
+      app.println(message);
+      LOG.log(Level.SEVERE, "Error starting server with file "+configFile+
+          ": "+ode, ode);
+      return ReplicationCliReturnCode.ERROR_LOCAL_PURGE_HISTORICAL_SERVER_START;
+    }
+    pointAdder.stop();
+    app.printProgress(formatter.getFormattedDone());
+    app.printlnProgress();
+    app.printlnProgress();
+    app.printProgress(formatter.getFormattedProgress(
+        INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_STARTING.get()));
+    app.printlnProgress();
+
+    if (applyTimeout && timeoutOccurred(endMaxTime))
+    {
+      return handleTimeout();
+    }
+
+    try
+    {
+      // launch the job
+      for (String baseDN : uData.getBaseDNs())
+      {
+        DN dn = DN.decode(baseDN);
+        // We can assume that this is an LDAP replication domain
+        domain = LDAPReplicationDomain.retrievesReplicationDomain(dn);
+
+        domain.purgeConflictsHistorical(null, startTime + purgeMaxTime);
+      }
+
+    }
+    catch (DirectoryException de)
+    {
+      if (de.getResultCode() == ResultCode.ADMIN_LIMIT_EXCEEDED)
+      {
+        return handleTimeout();
+      }
+      else
+      {
+        return handleGenericExecuting(de);
+      }
+    }
+    return ReplicationCliReturnCode.SUCCESSFUL;
+  }
+
+  private ReplicationCliReturnCode handleGenericExecuting(OpenDsException ode)
+  {
+    LOG.log(Level.SEVERE, "Error executing purge historical: "+ode, ode);
+    app.println();
+    app.println(ERR_REPLICATION_PURGE_HISTORICAL_EXECUTING.get(
+        ode.getMessageObject()));
+    return ReplicationCliReturnCode.ERROR_LOCAL_PURGE_HISTORICAL_EXECUTING;
+  }
+
+  private ReplicationCliReturnCode handleTimeout()
+  {
+    app.println();
+    app.println(ERR_REPLICATION_PURGE_HISTORICAL_TIMEOUT.get(
+        getTimeoutInSeconds()));
+    return ReplicationCliReturnCode.ERROR_LOCAL_PURGE_HISTORICAL_TIMEOUT;
+  }
+
+  /**
+   * Returns the time-out provided by the user in seconds.
+   * @return the time-out provided by the user in seconds.
+   */
+  private int getTimeoutInSeconds()
+  {
+    return uData.getMaximumDuration();
+  }
+
+  /**
+   * A method that tells whether the maximum time to execute the operation was
+   * elapsed or not.
+   * @param endMaxTime the latest time in milliseconds when the operation should
+   * be completed.
+   * @return {@code true} if the time-out occurred and {@code false} otherwise.
+   */
+  private boolean timeoutOccurred(long endMaxTime)
+  {
+    return TimeThread.getTime() > endMaxTime;
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalScheduleInformation.java b/opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalScheduleInformation.java
new file mode 100644
index 0000000..ded9271
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalScheduleInformation.java
@@ -0,0 +1,157 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.dsreplication;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.opends.server.backends.task.FailedDependencyAction;
+import org.opends.server.config.ConfigConstants;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.tools.tasks.TaskScheduleInformation;
+import org.opends.server.tools.tasks.TaskScheduleUserData;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.RawAttribute;
+
+/**
+ * This is a simple adaptor to create a task schedule information object
+ * using the data provided by the user.  It is used to be able to share some
+ * code with the {@link TaskTool} class.
+ *
+ */
+public class PurgeHistoricalScheduleInformation
+implements TaskScheduleInformation
+{
+  private final PurgeHistoricalUserData uData;
+  private TaskScheduleUserData taskSchedule;
+
+  /**
+   * Default constructor.
+   * @param uData the data provided by the user to do the purge historical.
+   */
+  public PurgeHistoricalScheduleInformation(
+      PurgeHistoricalUserData uData)
+  {
+    this.uData = uData;
+    this.taskSchedule = uData.getTaskSchedule();
+    if (taskSchedule == null)
+    {
+      taskSchedule = new TaskScheduleUserData();
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addTaskAttributes(List<RawAttribute> attributes)
+  {
+    ArrayList<ByteString> baseDNs = new ArrayList<ByteString>();
+    for (String baseDN : uData.getBaseDNs())
+    {
+      baseDNs.add(ByteString.valueOf(baseDN));
+    }
+    attributes.add(new LDAPAttribute(
+        ConfigConstants.ATTR_TASK_CONFLICTS_HIST_PURGE_DOMAIN_DN, baseDNs));
+    attributes.add(new LDAPAttribute(
+        ConfigConstants.ATTR_TASK_CONFLICTS_HIST_PURGE_MAX_DURATION,
+        Long.toString(uData.getMaximumDuration())));
+
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getDependencyIds()
+  {
+    return taskSchedule.getDependencyIds();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public FailedDependencyAction getFailedDependencyAction()
+  {
+    return taskSchedule.getFailedDependencyAction();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getNotifyUponCompletionEmailAddresses()
+  {
+    return taskSchedule.getNotifyUponCompletionEmailAddresses();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getNotifyUponErrorEmailAddresses()
+  {
+    return taskSchedule.getNotifyUponErrorEmailAddresses();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRecurringDateTime()
+  {
+    return taskSchedule.getRecurringDateTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Date getStartDateTime()
+  {
+    return taskSchedule.getStartDate();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Class<?> getTaskClass()
+  {
+    return org.opends.server.tasks.PurgeConflictsHistoricalTask.class;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getTaskId()
+  {
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getTaskObjectclass()
+  {
+    return "ds-task-purge-conflicts-historical";
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalUserData.java b/opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalUserData.java
new file mode 100644
index 0000000..c6c7dc0
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalUserData.java
@@ -0,0 +1,313 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.tools.dsreplication;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+
+import org.opends.server.admin.client.cli.TaskScheduleArgs;
+import org.opends.server.tools.tasks.TaskClient;
+import org.opends.server.tools.tasks.TaskScheduleUserData;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.RawAttribute;
+
+/**
+ * This class is used to store the information provided by the user to
+ * purge historical data.
+ *
+ */
+public class PurgeHistoricalUserData extends MonoServerReplicationUserData
+{
+  private int maximumDuration;
+  private boolean online;
+  private TaskScheduleUserData taskSchedule = new TaskScheduleUserData();
+
+  /**
+   * Default constructor.
+   */
+  public PurgeHistoricalUserData()
+  {
+  }
+
+
+  /**
+   * Returns the maximum duration that the purge can take in seconds.
+   * @return the maximum duration that the purge can take in seconds.
+   */
+  public int getMaximumDuration()
+  {
+    return maximumDuration;
+  }
+
+  /**
+   * Sets the maximum duration that the purge can take in seconds.
+   * @param maximumDuration the maximum duration that the purge can take in
+   * seconds.
+   */
+  public void setMaximumDuration(int maximumDuration)
+  {
+    this.maximumDuration = maximumDuration;
+  }
+
+  /**
+   * Whether the task will be executed on an online server (using an LDAP
+   * connection and the tasks backend) or not.
+   * @return {@code true} if the task will be executed on an online server
+   * and {@code false} otherwise.
+   */
+  public boolean isOnline()
+  {
+    return online;
+  }
+
+  /**
+   * Sets whether the task will be executed on an online server or not.
+   * @param online {@code true} if the task will be executed on an online server
+   * and {@code false} otherwise.
+   */
+  public void setOnline(boolean online)
+  {
+    this.online = online;
+  }
+
+  /**
+   * Returns the object describing the schedule of the task.  If the operation
+   * is not online, the value returned by this method should not be taken into
+   * account.
+   * @return the object describing the schedule of the task.
+   */
+  public TaskScheduleUserData getTaskSchedule()
+  {
+    return taskSchedule;
+  }
+
+  /**
+   * Sets the object describing the schedule of the task.
+   * @param taskSchedule the object describing the schedule of the task.
+   */
+  public void setTaskSchedule(TaskScheduleUserData taskSchedule)
+  {
+    this.taskSchedule = taskSchedule;
+  }
+
+
+  /**
+   * Initializes the contents of the provided purge historical replication user
+   * data object with what was provided in the command-line without prompting to
+   * the user.
+   * @param uData the purge historical replication user data object to be
+   * initialized.
+   * @param argParser the argument parser with the arguments provided by the
+   * user.
+   */
+  public static  void initializeWithArgParser(PurgeHistoricalUserData uData,
+      ReplicationCliArgumentParser argParser)
+  {
+    uData.setBaseDNs(new LinkedList<String>(argParser.getBaseDNs()));
+
+    if (argParser.connectionArgumentsPresent())
+    {
+      String adminUid = getValue(argParser.getAdministratorUID(),
+          argParser.getDefaultAdministratorUID());
+      uData.setAdminUid(adminUid);
+      String adminPwd = argParser.getBindPasswordAdmin();
+      uData.setAdminPwd(adminPwd);
+
+      String hostName = getValue(argParser.getHostNameToStatus(),
+          argParser.getDefaultHostNameToStatus());
+      uData.setHostName(hostName);
+      int port = getValue(argParser.getPortToStatus(),
+          argParser.getDefaultPortToStatus());
+      uData.setPort(port);
+      uData.setOnline(true);
+      TaskScheduleUserData taskSchedule = new TaskScheduleUserData();
+      TaskScheduleArgs taskArgs = argParser.getTaskArgsList();
+      taskSchedule.setStartNow(taskArgs.isStartNow());
+      if (!taskSchedule.isStartNow())
+      {
+        taskSchedule.setStartDate(taskArgs.getStartDateTime());
+        taskSchedule.setDependencyIds(taskArgs.getDependencyIds());
+        taskSchedule.setFailedDependencyAction(
+            taskArgs.getFailedDependencyAction());
+        taskSchedule.setNotifyUponErrorEmailAddresses(
+            taskArgs.getNotifyUponErrorEmailAddresses());
+        taskSchedule.setNotifyUponCompletionEmailAddresses(
+            taskArgs.getNotifyUponCompletionEmailAddresses());
+        taskSchedule.setRecurringDateTime(
+            taskArgs.getRecurringDateTime());
+      }
+      uData.setTaskSchedule(taskSchedule);
+    }
+    else
+    {
+      uData.setOnline(false);
+    }
+
+    uData.setMaximumDuration(getValue(argParser.getMaximumDuration(),
+        argParser.getDefaultMaximumDuration()));
+  }
+
+
+  /**
+   * Commodity method that simply checks if a provided value is null or not,
+   * if it is not <CODE>null</CODE> returns it and if it is <CODE>null</CODE>
+   * returns the provided default value.
+   * @param v the value to analyze.
+   * @param defaultValue the default value.
+   * @return if the provided value is not <CODE>null</CODE> returns it and if it
+   * is <CODE>null</CODE> returns the provided default value.
+   */
+  private static String getValue(String v, String defaultValue)
+  {
+    if (v != null)
+    {
+      return v;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+  /**
+   * Commodity method that simply checks if a provided value is -1 or not,
+   * if it is not -1 returns it and if it is -1 returns the provided default
+   * value.
+   * @param v the value to analyze.
+   * @param defaultValue the default value.
+   * @return if the provided value is not -1 returns it and if it is -1 returns
+   * the provided default value.
+   */
+  private static int getValue(int v, int defaultValue)
+  {
+    if (v != -1)
+    {
+      return v;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  /**
+   * Commodity method that returns the list of basic task attributes required
+   * to launch a task corresponding to the provided user data.
+   * @param uData the user data describing the purge historical to be executed.
+   * @return the list of basic task attributes required
+   * to launch a task corresponding to the provided user data.
+   */
+  public static BasicAttributes getTaskAttributes(PurgeHistoricalUserData uData)
+  {
+    PurgeHistoricalScheduleInformation information =
+      new PurgeHistoricalScheduleInformation(uData);
+    ArrayList<RawAttribute> rawAttrs =
+      TaskClient.getTaskAttributes(information);
+    BasicAttributes attrs = getAttributes(rawAttrs);
+    return attrs;
+  }
+
+  private static BasicAttributes getAttributes(ArrayList<RawAttribute> rawAttrs)
+  {
+    BasicAttributes attrs = new BasicAttributes();
+    for (RawAttribute rawAttr : rawAttrs)
+    {
+      BasicAttribute attr = new BasicAttribute(rawAttr.getAttributeType());
+      for (ByteString v : rawAttr.getValues())
+      {
+        attr.add(v.toString());
+      }
+      attrs.put(attr);
+    }
+    return attrs;
+  }
+
+  /**
+   * Returns the DN of the task corresponding to the provided list of
+   * attributes.  The code assumes that the attributes have been generated
+   * calling the method {@link #getTaskAttributes(PurgeHistoricalUserData)}.
+   * @param attrs the attributes of the task entry.
+   * @return the DN of the task entry.
+   */
+  public static String getTaskDN(BasicAttributes attrs)
+  {
+    ArrayList<RawAttribute> rawAttrs = getRawAttributes(attrs);
+    return TaskClient.getTaskDN(rawAttrs);
+  }
+
+  /**
+   * Returns the ID of the task corresponding to the provided list of
+   * attributes.  The code assumes that the attributes have been generated
+   * calling the method {@link #getTaskAttributes(PurgeHistoricalUserData)}.
+   * @param attrs the attributes of the task entry.
+   * @return the ID of the task entry.
+   */
+  public static String getTaskID(BasicAttributes attrs)
+  {
+    ArrayList<RawAttribute> rawAttrs = getRawAttributes(attrs);
+    return TaskClient.getTaskID(rawAttrs);
+  }
+
+
+  private static ArrayList<RawAttribute> getRawAttributes(BasicAttributes attrs)
+  {
+    ArrayList<RawAttribute> rawAttrs = new ArrayList<RawAttribute>();
+    NamingEnumeration<Attribute> nAtt = attrs.getAll();
+
+    try
+    {
+      while (nAtt.hasMore())
+      {
+        Attribute attr = nAtt.next();
+        NamingEnumeration<?> values = attr.getAll();
+        ArrayList<ByteString> rawValues = new ArrayList<ByteString>();
+        while (values.hasMore())
+        {
+          Object v = values.next();
+          rawValues.add(ByteString.valueOf(v.toString()));
+        }
+        RawAttribute rAttr = RawAttribute.create(attr.getID(), rawValues);
+        rawAttrs.add(rAttr);
+      }
+    }
+    catch (NamingException ne)
+    {
+      // This is a bug.
+      throw new RuntimeException("Unexpected error: "+ne, ne);
+    }
+    return rawAttrs;
+  }
+}
+
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliArgumentParser.java b/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliArgumentParser.java
index 4409a8a..a4d85ed 100644
--- a/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliArgumentParser.java
+++ b/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliArgumentParser.java
@@ -44,6 +44,10 @@
 import org.opends.server.admin.AdministrationConnector;
 import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
 import org.opends.server.admin.client.cli.SecureConnectionCliParser;
+import org.opends.server.admin.client.cli.TaskScheduleArgs;
+import org.opends.server.extensions.ConfigFileHandler;
+import org.opends.server.tasks.PurgeConflictsHistoricalTask;
+import org.opends.server.types.OpenDsException;
 import org.opends.server.util.args.Argument;
 import org.opends.server.util.args.ArgumentException;
 import org.opends.server.util.args.ArgumentGroup;
@@ -68,6 +72,7 @@
   private SubCommand postExternalInitializationSubCmd;
   private SubCommand preExternalInitializationSubCmd;
   private SubCommand statusReplicationSubCmd;
+  private SubCommand purgeHistoricalSubCmd;
 
   int defaultAdminPort =
     AdministrationConnector.DEFAULT_ADMINISTRATION_CONNECTOR_PORT;
@@ -219,7 +224,7 @@
   /**
    * The 'suffixes' global argument.
    */
-  private StringArgument baseDNsArg = null;
+  StringArgument baseDNsArg = null;
 
   /**
    * The 'quiet' argument.
@@ -259,6 +264,22 @@
    */
   BooleanArgument advancedArg;
 
+  // The argument set by the user to specify the configuration class
+  // (useful when dsreplication purge-historical runs locally/starts the server)
+  private StringArgument  configClassArg;
+
+  // The argument set by the user to specify the configuration file
+  // (useful when dsreplication purge-historical runs locally/starts the server)
+  private StringArgument  configFileArg;
+
+  TaskScheduleArgs taskArgs;
+
+
+  /**
+   * The 'maximumDuration' argument for the purge of historical.
+   */
+  IntegerArgument maximumDurationArg;
+
   /**
    * The text of the enable replication subcommand.
    */
@@ -297,6 +318,11 @@
    */
   public static final String STATUS_REPLICATION_SUBCMD_NAME = "status";
 
+  /**
+   * The text of the purge historical subcommand.
+   */
+  public static final String PURGE_HISTORICAL_SUBCMD_NAME = "purge-historical";
+
   // This CLI is always using the administration connector with SSL
   private final boolean alwaysSSL = true;
 
@@ -329,6 +355,7 @@
   public void initializeParser(OutputStream outStream)
       throws ArgumentException
   {
+    taskArgs = new TaskScheduleArgs();
     initializeGlobalArguments(outStream);
     try
     {
@@ -345,6 +372,7 @@
     createPreExternalInitializationSubCommand();
     createPostExternalInitializationSubCommand();
     createStatusReplicationSubCommand();
+    createPurgeHistoricalSubCommand();
   }
 
   /**
@@ -365,6 +393,7 @@
   /**
    * {@inheritDoc}
    */
+  @Override
   public int validateGlobalOptions(MessageBuilder buf)
   {
     int returnValue;
@@ -418,7 +447,8 @@
       {
         errors.add(ERR_REPLICATION_NO_BASE_DN_PROVIDED.get());
       }
-      if (getBindPasswordAdmin() == null)
+      if (getBindPasswordAdmin() == null &&
+          !isPurgeHistoricalSubcommand())
       {
         errors.add(ERR_REPLICATION_NO_ADMINISTRATOR_PASSWORD_PROVIDED.get(
             "--"+secureArgsList.bindPasswordArg.getLongIdentifier(),
@@ -556,6 +586,23 @@
         INFO_REPLICATION_DESCRIPTION_ADVANCED.get());
     defaultArgs.add(index++, advancedArg);
 
+    configClassArg =
+      new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
+                         OPTION_LONG_CONFIG_CLASS, true, false,
+                         true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
+                         ConfigFileHandler.class.getName(), null,
+                         INFO_DESCRIPTION_CONFIG_CLASS.get());
+    configClassArg.setHidden(true);
+    defaultArgs.add(index++, configClassArg);
+
+    configFileArg =
+      new StringArgument("configfile", 'f', "configFile", true, false,
+                         true, INFO_CONFIGFILE_PLACEHOLDER.get(), null,
+                         null,
+                         INFO_DESCRIPTION_CONFIG_FILE.get());
+    configFileArg.setHidden(true);
+    defaultArgs.add(index++, configFileArg);
+
     for (int i=0; i<index; i++)
     {
       Argument arg = defaultArgs.get(i);
@@ -585,6 +632,7 @@
    * @throws ArgumentException if there is a conflict with the provided
    * arguments.
    */
+  @Override
   protected void initializeGlobalArguments(
           Collection<Argument> args,
           ArgumentGroup argGroup)
@@ -625,6 +673,8 @@
     port1Arg = new IntegerArgument("port1", OPTION_SHORT_PORT, "port1",
         false, false, true, INFO_PORT_PLACEHOLDER.get(),
         defaultAdminPort, null,
+        true, 1,
+        true, 65336,
         INFO_DESCRIPTION_ENABLE_REPLICATION_SERVER_PORT1.get());
 
     bindDn1Arg = new StringArgument("bindDN1", OPTION_SHORT_BINDDN,
@@ -645,6 +695,8 @@
     replicationPort1Arg = new IntegerArgument("replicationPort1", 'r',
         "replicationPort1", false, false, true, INFO_PORT_PLACEHOLDER.get(),
         8989, null,
+        true, 1,
+        true, 65336,
         INFO_DESCRIPTION_ENABLE_REPLICATION_PORT1.get());
 
     secureReplication1Arg = new BooleanArgument("secureReplication1", null,
@@ -666,6 +718,8 @@
 
     port2Arg = new IntegerArgument("port2", null, "port2",
         false, false, true, INFO_PORT_PLACEHOLDER.get(), defaultAdminPort, null,
+        true, 1,
+        true, 65336,
         INFO_DESCRIPTION_ENABLE_REPLICATION_SERVER_PORT2.get());
 
     bindDn2Arg = new StringArgument("bindDN2", null,
@@ -686,6 +740,8 @@
     replicationPort2Arg = new IntegerArgument("replicationPort2", 'R',
         "replicationPort2", false, false, true, INFO_PORT_PLACEHOLDER.get(),
         8989, null,
+        true, 1,
+        true, 65336,
         INFO_DESCRIPTION_ENABLE_REPLICATION_PORT2.get());
 
     secureReplication2Arg = new BooleanArgument("secureReplication2", null,
@@ -783,6 +839,8 @@
     portSourceArg = new IntegerArgument("portSource", OPTION_SHORT_PORT,
         "portSource", false, false, true, INFO_PORT_PLACEHOLDER.get(),
         defaultAdminPort, null,
+        true, 1,
+        true, 65336,
         INFO_DESCRIPTION_INITIALIZE_REPLICATION_SERVER_PORT_SOURCE.get());
 
     hostNameDestinationArg = new StringArgument("hostDestination", 'O',
@@ -794,6 +852,8 @@
         "portDestination", false, false, true, INFO_PORT_PLACEHOLDER.get(),
         defaultAdminPort,
         null,
+        true, 1,
+        true, 65336,
         INFO_DESCRIPTION_INITIALIZE_REPLICATION_SERVER_PORT_DESTINATION.get());
 
     initializeReplicationSubCmd = new SubCommand(this,
@@ -916,6 +976,50 @@
   }
 
   /**
+   * Creates the purge historical subcommand and all the specific options
+   * for the subcommand.  Note: this method assumes that
+   * initializeGlobalArguments has already been called and that hostNameArg and
+   * portArg have been created.
+   */
+  private void createPurgeHistoricalSubCommand()
+  throws ArgumentException
+  {
+    maximumDurationArg = new IntegerArgument(
+        "maximumDuration",
+        null, // shortId
+        "maximumDuration",
+        true, // isRequired
+        false, // isMultivalued
+        true,  // needsValue
+        INFO_MAXIMUM_DURATION_PLACEHOLDER.get(),
+        PurgeConflictsHistoricalTask.DEFAULT_MAX_DURATION,
+        null,
+        true, 0,
+        false, Integer.MAX_VALUE,
+        INFO_DESCRIPTION_PURGE_HISTORICAL_MAXIMUM_DURATION.get());
+
+    purgeHistoricalSubCmd = new SubCommand(
+        this,
+        PURGE_HISTORICAL_SUBCMD_NAME,
+        INFO_DESCRIPTION_SUBCMD_PURGE_HISTORICAL.get());
+
+    Argument[] argsToAdd = {
+        secureArgsList.hostNameArg,
+        secureArgsList.portArg,
+        maximumDurationArg};
+
+    for (int i=0; i<argsToAdd.length; i++)
+    {
+      argsToAdd[i].setPropertyName(argsToAdd[i].getLongIdentifier());
+      purgeHistoricalSubCmd.addArgument(argsToAdd[i]);
+    }
+    for (Argument arg : taskArgs.getArguments())
+    {
+      purgeHistoricalSubCmd.addArgument(arg);
+    }
+  }
+
+  /**
    * Tells whether the user specified to have an interactive operation or not.
    * This method must be called after calling parseArguments.
    * @return <CODE>true</CODE> if the user specified to have an interactive
@@ -1061,6 +1165,7 @@
    * Returns the Administrator UID explicitly provided in the command-line.
    * @return the Administrator UID explicitly provided in the command-line.
    */
+  @Override
   public String getAdministratorUID()
   {
     return getValue(secureArgsList.adminUidArg);
@@ -1642,6 +1747,28 @@
   }
 
   /**
+   * Returns the config class value provided in the hidden argument of the
+   * command-line.
+   * @return the config class value provided in the hidden argument of the
+   * command-line.
+   */
+  public String getConfigClass()
+  {
+    return getValue(configClassArg);
+  }
+
+  /**
+   * Returns the config file value provided in the hidden argument of the
+   * command-line.
+   * @return the config file value provided in the hidden argument of the
+   * command-line.
+   */
+  public String getConfigFile()
+  {
+    return getValue(configFileArg);
+  }
+
+  /**
    * Returns the value of the provided argument only if the user provided it
    * explicitly.
    * @param arg the StringArgument to be handled.
@@ -1752,6 +1879,10 @@
     {
       validatePostExternalInitializationOptions(buf);
     }
+    else if (isPurgeHistoricalSubcommand())
+    {
+      validatePurgeHistoricalOptions(buf);
+    }
 
     else
     {
@@ -1762,6 +1893,35 @@
   }
 
   /**
+   * Checks the purge historical subcommand options and updates the
+   * provided MessageBuilder with the errors that were encountered with the
+   * subcommand options.
+   *
+   * This method assumes that the method parseArguments for the parser has
+   * already been called.
+   * @param buf the MessageBuilder object where we add the error messages
+   * describing the errors encountered.
+   */
+  private void validatePurgeHistoricalOptions(MessageBuilder buf)
+  {
+    try
+    {
+      if (!isInteractive() && !connectionArgumentsPresent())
+      {
+        taskArgs.validateArgsIfOffline();
+      }
+      else
+      {
+        taskArgs.validateArgs();
+      }
+    }
+    catch (OpenDsException ode)
+    {
+      addMessage(buf, ode.getMessageObject());
+    }
+  }
+
+  /**
    * Returns whether the user provided subcommand is the enable replication
    * or not.
    * @return <CODE>true</CODE> if the user provided subcommand is the
@@ -1795,6 +1955,17 @@
   }
 
   /**
+   * Returns whether the user provided subcommand is the purge historical
+   * or not.
+   * @return <CODE>true</CODE> if the user provided subcommand is the
+   * purge historical and <CODE>false</CODE> otherwise.
+   */
+  public boolean isPurgeHistoricalSubcommand()
+  {
+    return isSubcommand(PURGE_HISTORICAL_SUBCMD_NAME);
+  }
+
+  /**
    * Returns whether the user provided subcommand is the initialize all
    * replication or not.
    * @return <CODE>true</CODE> if the user provided subcommand is the
@@ -2071,4 +2242,60 @@
   {
     return secureArgsList;
   }
+
+  /**
+   * Returns the TaskScheduleArgs object containing the arguments
+   * of this parser.
+   * @return the TaskScheduleArgs object containing the arguments
+   * of this parser.
+   */
+  public TaskScheduleArgs getTaskArgsList()
+  {
+    return taskArgs;
+  }
+
+  /**
+   * Returns whether the user specified connection arguments or not.
+   * @return {@code true} if the user specified connection arguments and
+   * {@code false} otherwise.
+   */
+  public boolean connectionArgumentsPresent()
+  {
+    if (isPurgeHistoricalSubcommand()) {
+      boolean secureArgsPresent = getSecureArgsList() != null &&
+      getSecureArgsList().argumentsPresent();
+      // This have to be explicitly specified because their original definition
+      // has been replaced.
+      boolean adminArgsPresent = secureArgsList.adminUidArg.isPresent() ||
+      secureArgsList.bindPasswordArg.isPresent() ||
+      secureArgsList.bindPasswordFileArg.isPresent();
+      return secureArgsPresent || adminArgsPresent;
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  /**
+    * Returns the maximum duration explicitly provided in the purge historical
+    * replication subcommand.
+    * @return the maximum duration explicitly provided in the purge historical
+    * replication subcommand.  Returns -1 if no port was explicitly provided.
+    */
+  public int getMaximumDuration()
+  {
+     return getValue(maximumDurationArg);
+  }
+
+  /**
+   * Returns the maximum duration default value in the purge historical
+   * replication subcommand.
+   * @return the maximum duration default value in the purge historical
+   * replication subcommand.
+   */
+  public int getDefaultMaximumDuration()
+  {
+    return getDefaultValue(maximumDurationArg);
+  }
 }
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java b/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java
index 30b07cf..addd00d 100644
--- a/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java
+++ b/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java
@@ -49,6 +49,7 @@
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
@@ -56,6 +57,7 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
@@ -90,11 +92,18 @@
 import org.opends.admin.ads.util.ConnectionUtils;
 import org.opends.admin.ads.util.PreferredConnection;
 import org.opends.admin.ads.util.ServerLoader;
+import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
+import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
+import org.opends.guitools.controlpanel.util.ConfigFromDirContext;
+import org.opends.guitools.controlpanel.util.ConfigFromFile;
 import org.opends.guitools.controlpanel.util.ControlPanelLog;
+import org.opends.guitools.controlpanel.util.ProcessReader;
+import org.opends.guitools.controlpanel.util.Utilities;
 import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
 import org.opends.quicksetup.ApplicationException;
 import org.opends.quicksetup.Constants;
+import org.opends.quicksetup.Installation;
 import org.opends.quicksetup.ReturnCode;
 import org.opends.quicksetup.event.ProgressUpdateEvent;
 import org.opends.quicksetup.event.ProgressUpdateListener;
@@ -116,14 +125,19 @@
 import org.opends.server.admin.std.meta.*;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.tasks.PurgeConflictsHistoricalTask;
 import org.opends.server.tools.ClientException;
 import org.opends.server.tools.ToolConstants;
+import org.opends.server.tools.tasks.TaskEntry;
+import org.opends.server.tools.tasks.TaskScheduleInteraction;
+import org.opends.server.tools.tasks.TaskScheduleUserData;
 import org.opends.server.types.DN;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.NullOutputStream;
 import org.opends.server.types.OpenDsException;
 import org.opends.server.util.ServerConstants;
 import org.opends.server.util.SetupUtils;
+import org.opends.server.util.StaticUtils;
 import org.opends.server.util.args.Argument;
 import org.opends.server.util.args.ArgumentException;
 import org.opends.server.util.args.BooleanArgument;
@@ -136,6 +150,7 @@
 import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
 import org.opends.server.util.cli.MenuBuilder;
 import org.opends.server.util.cli.MenuResult;
+import org.opends.server.util.cli.PointAdder;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TextTablePrinter;
 
@@ -158,6 +173,20 @@
   /** Suffix for log files. */
   static public final String LOG_FILE_SUFFIX = ".log";
 
+  /**
+   * Property used to call the dsreplication script and ReplicationCliMain to
+   * know which are the java properties to be used (those of dsreplication or
+   * those of dsreplication.offline).
+   */
+  private static final String SCRIPT_CALL_STATUS =
+    "org.opends.server.dsreplicationcallstatus";
+
+  /**
+   * The value set by the dsreplication script if it is called the first time.
+   */
+  private static final String FIRST_SCRIPT_CALL =
+    "firstcall";
+
   private boolean forceNonInteractive;
 
   private static final Logger LOG =
@@ -204,6 +233,10 @@
      */
     STATUS(INFO_REPLICATION_STATUS_MENU_PROMPT.get()),
     /**
+     * Replication purge historical.
+     */
+    PURGE_HISTORICAL(INFO_REPLICATION_PURGE_HISTORICAL_MENU_PROMPT.get()),
+    /**
      * Cancel operation.
      */
     CANCEL(null);
@@ -389,7 +422,7 @@
           returnValue = ERROR_USER_DATA;
         }
       }
-      if (initializeServer)
+      if (initializeServer && returnValue == SUCCESSFUL_NOP)
       {
         DirectoryServer.bootstrapClient();
 
@@ -484,6 +517,12 @@
           subCommand =
             ReplicationCliArgumentParser.STATUS_REPLICATION_SUBCMD_NAME;
         }
+        else if (argParser.isPurgeHistoricalSubcommand())
+        {
+          returnValue = purgeHistorical();
+          subCommand =
+            ReplicationCliArgumentParser.PURGE_HISTORICAL_SUBCMD_NAME;
+        }
         else
         {
           if (argParser.isInteractive())
@@ -526,6 +565,11 @@
                 ReplicationCliArgumentParser.STATUS_REPLICATION_SUBCMD_NAME;
               break;
 
+            case PURGE_HISTORICAL:
+              subCommand =
+                ReplicationCliArgumentParser.PURGE_HISTORICAL_SUBCMD_NAME;
+              break;
+
             default:
               // User canceled
               returnValue = USER_CANCELLED;
@@ -573,6 +617,11 @@
     return returnValue.getReturnCode();
   }
 
+  private boolean isFirstCallFromScript()
+  {
+    return FIRST_SCRIPT_CALL.equals(System.getProperty(SCRIPT_CALL_STATUS));
+  }
+
   private void createArgumenParser() throws ArgumentException
   {
     argParser = new ReplicationCliArgumentParser(CLASS_NAME);
@@ -783,6 +832,663 @@
   }
 
   /**
+   * Based on the data provided in the command-line it displays replication
+   * status.
+   * @return the error code if the operation failed and SUCCESSFUL if it was
+   * successful.
+   */
+  private ReplicationCliReturnCode purgeHistorical()
+  {
+    ReplicationCliReturnCode returnValue;
+    PurgeHistoricalUserData uData = new PurgeHistoricalUserData();
+
+    if (argParser.isInteractive())
+    {
+      uData = new PurgeHistoricalUserData();
+      if (promptIfRequired(uData))
+      {
+        returnValue = purgeHistorical(uData);
+      }
+      else
+      {
+        returnValue = USER_CANCELLED;
+      }
+    }
+    else
+    {
+      initializeWithArgParser(uData);
+      returnValue = purgeHistorical(uData);
+    }
+    return returnValue;
+  }
+
+  /**
+   * Initializes the contents of the provided purge historical replication user
+   * data object with what was provided in the command-line without prompting to
+   * the user.
+   * @param uData the purge historical replication user data object to be
+   * initialized.
+   */
+  private void initializeWithArgParser(PurgeHistoricalUserData uData)
+  {
+    PurgeHistoricalUserData.initializeWithArgParser(uData, argParser);
+  }
+
+  private ReplicationCliReturnCode purgeHistorical(
+      PurgeHistoricalUserData uData)
+  {
+      ReplicationCliReturnCode returnValue = null;
+      if (uData.isOnline())
+      {
+        returnValue = purgeHistoricalRemotely(uData);
+      }
+      else
+      {
+        returnValue = purgeHistoricalLocally(uData);
+      }
+      return returnValue;
+  }
+
+  private ReplicationCliReturnCode purgeHistoricalLocally(
+      PurgeHistoricalUserData uData)
+  {
+    ReplicationCliReturnCode returnValue;
+    LinkedList<String> baseDNs = uData.getBaseDNs();
+    checkSuffixesForLocalPurgeHistorical(baseDNs, false);
+    if (!baseDNs.isEmpty())
+    {
+      uData.setBaseDNs(baseDNs);
+      printPurgeHistoricalEquivalentIfRequired(uData);
+
+      returnValue = SUCCESSFUL;
+      try
+      {
+        returnValue = purgeHistoricalLocallyTask(uData);
+      }
+      catch (ReplicationCliException rce)
+      {
+        println();
+        println(getCriticalExceptionMessage(rce));
+        returnValue = rce.getErrorCode();
+        LOG.log(Level.SEVERE, "Complete error stack:", rce);
+      }
+    }
+    else
+    {
+      returnValue = HISTORICAL_CANNOT_BE_PURGED_ON_BASEDN;
+    }
+
+    return returnValue;
+  }
+
+  private void printPurgeProgressMessage(PurgeHistoricalUserData uData)
+  {
+    String separator =  formatter.getLineBreak().toString() +
+    formatter.getTab().toString();
+    printlnProgress();
+    Message msg = formatter.getFormattedProgress(
+        INFO_PROGRESS_PURGE_HISTORICAL.get(separator,
+            Utils.getStringFromCollection(uData.getBaseDNs(), separator)));
+    printProgress(msg);
+    printlnProgress();
+
+  }
+
+  private ReplicationCliReturnCode purgeHistoricalLocallyTask(
+      PurgeHistoricalUserData uData)
+  throws ReplicationCliException
+  {
+    ReplicationCliReturnCode returnCode = ReplicationCliReturnCode.SUCCESSFUL;
+    if (isFirstCallFromScript())
+    {
+      // Launch the process: launch dsreplication in non-interactive mode with
+      // the recursive property set.
+      ArrayList<String> args = new ArrayList<String>();
+      args.add(getCommandLinePath(getCommandName()));
+      args.add(ReplicationCliArgumentParser.PURGE_HISTORICAL_SUBCMD_NAME);
+      args.add("--"+argParser.noPromptArg.getLongIdentifier());
+      args.add("--"+argParser.maximumDurationArg.getLongIdentifier());
+      args.add(String.valueOf(uData.getMaximumDuration()));
+      for (String baseDN : uData.getBaseDNs())
+      {
+        args.add("--"+argParser.baseDNsArg.getLongIdentifier());
+        args.add(baseDN);
+      }
+      ProcessBuilder pb = new ProcessBuilder(args);
+      // Use the java args in the script.
+      Map<String, String> env = pb.environment();
+      env.put("RECURSIVE_LOCAL_CALL", "true");
+      try
+      {
+        ProcessReader outReader = null;
+        ProcessReader errReader = null;
+        Process process = pb.start();
+        outReader = new ProcessReader(process, getOutputStream(), false);
+        errReader = new ProcessReader(process, getErrorStream(), true);
+
+        outReader.startReading();
+        errReader.startReading();
+
+        int code = process.waitFor();
+        for (ReplicationCliReturnCode c : ReplicationCliReturnCode.values())
+        {
+          if (c.getReturnCode() == code)
+          {
+            returnCode = c;
+            break;
+          }
+        }
+      }
+      catch (Exception e)
+      {
+        Message msg = ERR_LAUNCHING_PURGE_HISTORICAL.get();
+        ReplicationCliReturnCode code = ERROR_LAUNCHING_PURGE_HISTORICAL;
+        throw new ReplicationCliException(
+            getThrowableMsg(msg, e), code, e);
+      }
+    }
+    else
+    {
+      printPurgeProgressMessage(uData);
+      LocalPurgeHistorical localPurgeHistorical =
+        new LocalPurgeHistorical(uData, this, formatter,
+            argParser.getConfigFile(),
+            argParser.getConfigClass());
+      returnCode = localPurgeHistorical.execute();
+
+      if (returnCode == ReplicationCliReturnCode.SUCCESSFUL)
+      {
+        printSuccessMessage(uData, null);
+      }
+    }
+    return returnCode;
+  }
+
+  private void printPurgeHistoricalEquivalentIfRequired(
+      PurgeHistoricalUserData uData)
+  {
+    if (mustPrintCommandBuilder())
+    {
+      try
+      {
+        CommandBuilder commandBuilder = createCommandBuilder(
+            ReplicationCliArgumentParser.PURGE_HISTORICAL_SUBCMD_NAME,
+            uData);
+        printCommandBuilder(commandBuilder);
+      }
+      catch (Throwable t)
+      {
+        LOG.log(Level.SEVERE, "Error printing equivalente command-line: "+t,
+            t);
+      }
+    }
+  }
+
+  private ReplicationCliReturnCode purgeHistoricalRemotely(
+      PurgeHistoricalUserData uData)
+  {
+    ReplicationCliReturnCode returnValue = SUCCESSFUL_NOP;
+    InitialLdapContext ctx = null;
+
+    // Connect to the provided server
+    try
+    {
+      ctx = createAdministrativeContext(uData.getHostName(), uData.getPort(),
+          useSSL, useStartTLS,
+          ADSContext.getAdministratorDN(uData.getAdminUid()),
+          uData.getAdminPwd(), getConnectTimeout(), getTrustManager());
+    }
+    catch (NamingException ne)
+    {
+      String hostPort =
+        getServerRepresentation(uData.getHostName(), uData.getPort());
+      println();
+      println(getMessageForException(ne, hostPort));
+      LOG.log(Level.SEVERE, "Complete error stack:", ne);
+    }
+
+    if (ctx != null)
+    {
+      LinkedList<String> baseDNs = uData.getBaseDNs();
+      checkSuffixesForPurgeHistorical(baseDNs, ctx, false);
+      if (!baseDNs.isEmpty())
+      {
+        uData.setBaseDNs(baseDNs);
+        printPurgeHistoricalEquivalentIfRequired(uData);
+
+        returnValue = SUCCESSFUL;
+        try
+        {
+          returnValue = purgeHistoricalRemoteTask(ctx, uData);
+        }
+        catch (ReplicationCliException rce)
+        {
+          println();
+          println(getCriticalExceptionMessage(rce));
+          returnValue = rce.getErrorCode();
+          LOG.log(Level.SEVERE, "Complete error stack:", rce);
+        }
+      }
+      else
+      {
+        returnValue = HISTORICAL_CANNOT_BE_PURGED_ON_BASEDN;
+      }
+    }
+    else
+    {
+      returnValue = ERROR_CONNECTING;
+    }
+
+    if (ctx != null)
+    {
+      try
+      {
+        ctx.close();
+      }
+      catch (Throwable t)
+      {
+      }
+    }
+    return returnValue;
+  }
+
+  private void printSuccessMessage(PurgeHistoricalUserData uData, String taskID)
+  {
+    printlnProgress();
+    if (!uData.isOnline())
+    {
+      printProgress(
+          INFO_PROGRESS_PURGE_HISTORICAL_FINISHED_PROCEDURE.get());
+    }
+    else if (uData.getTaskSchedule().isStartNow())
+    {
+      printProgress(INFO_TASK_TOOL_TASK_SUCESSFULL.get(
+          INFO_PURGE_HISTORICAL_TASK_NAME.get(),
+          taskID));
+    }
+    else if (uData.getTaskSchedule().getStartDate() != null)
+    {
+      printProgress(INFO_TASK_TOOL_TASK_SCHEDULED_FUTURE.get(
+          INFO_PURGE_HISTORICAL_TASK_NAME.get(),
+          taskID,
+          StaticUtils.formatDateTimeString(
+              uData.getTaskSchedule().getStartDate())));
+    }
+    else
+    {
+      printProgress(INFO_TASK_TOOL_RECURRING_TASK_SCHEDULED.get(
+          INFO_PURGE_HISTORICAL_TASK_NAME.get(),
+          taskID));
+    }
+
+    printlnProgress();
+  }
+
+  /**
+   * Launches the purge historical operation using the
+   * provided connection.
+   * @param ctx the connection to the server.
+   * @throws ReplicationCliException if there is an error performing the
+   * operation.
+   */
+  private ReplicationCliReturnCode purgeHistoricalRemoteTask(
+      InitialLdapContext ctx,
+      PurgeHistoricalUserData uData)
+  throws ReplicationCliException
+  {
+    printPurgeProgressMessage(uData);
+    ReplicationCliReturnCode returnCode = ReplicationCliReturnCode.SUCCESSFUL;
+    boolean taskCreated = false;
+    int i = 0;
+    boolean isOver = false;
+    String dn = null;
+    String taskID = null;
+    while (!taskCreated)
+    {
+      BasicAttributes attrs = PurgeHistoricalUserData.getTaskAttributes(uData);
+      dn = PurgeHistoricalUserData.getTaskDN(attrs);
+      taskID = PurgeHistoricalUserData.getTaskID(attrs);
+      try
+      {
+        DirContext dirCtx = ctx.createSubcontext(dn, attrs);
+        taskCreated = true;
+        LOG.log(Level.INFO, "created task entry: "+attrs);
+        dirCtx.close();
+      }
+      catch (NameAlreadyBoundException x)
+      {
+      }
+      catch (NamingException ne)
+      {
+        LOG.log(Level.SEVERE, "Error creating task "+attrs, ne);
+        Message msg = ERR_LAUNCHING_PURGE_HISTORICAL.get();
+        ReplicationCliReturnCode code = ERROR_LAUNCHING_PURGE_HISTORICAL;
+        throw new ReplicationCliException(
+            getThrowableMsg(msg, ne), code, ne);
+      }
+      i++;
+    }
+    // Wait until it is over
+    SearchControls searchControls = new SearchControls();
+    searchControls.setCountLimit(1);
+    searchControls.setSearchScope(
+        SearchControls. OBJECT_SCOPE);
+    String filter = "objectclass=*";
+    searchControls.setReturningAttributes(
+        new String[] {
+            "ds-task-log-message",
+            "ds-task-state",
+            "ds-task-purge-conflicts-historical-purged-values-count",
+            "ds-task-purge-conflicts-historical-purge-completed-in-time",
+            "ds-task-purge-conflicts-historical-purge-completed-in-time",
+            "ds-task-purge-conflicts-historical-last-purged-changenumber"
+        });
+    String lastLogMsg = null;
+
+    // Polling only makes sense when we are recurrently scheduling a task
+    // or the task is being executed now.
+    while (!isOver && (uData.getTaskSchedule().getStartDate() == null))
+    {
+      try
+      {
+        Thread.sleep(500);
+      }
+      catch (Throwable t)
+      {
+      }
+      try
+      {
+        NamingEnumeration<SearchResult> res =
+          ctx.search(dn, filter, searchControls);
+        SearchResult sr = null;
+        try
+        {
+          sr = res.next();
+        }
+        finally
+        {
+          res.close();
+        }
+        String logMsg = getFirstValue(sr, "ds-task-log-message");
+        if (logMsg != null)
+        {
+          if (!logMsg.equals(lastLogMsg))
+          {
+            LOG.log(Level.INFO, logMsg);
+            lastLogMsg = logMsg;
+          }
+        }
+        InstallerHelper helper = new InstallerHelper();
+        String state = getFirstValue(sr, "ds-task-state");
+
+        if (helper.isDone(state) || helper.isStoppedByError(state))
+        {
+          isOver = true;
+          Message errorMsg;
+          String server = ConnectionUtils.getHostPort(ctx);
+          if (lastLogMsg == null)
+          {
+            errorMsg = INFO_ERROR_DURING_PURGE_HISTORICAL_NO_LOG.get(
+                state, server);
+          }
+          else
+          {
+            errorMsg = INFO_ERROR_DURING_PURGE_HISTORICAL_LOG.get(
+                lastLogMsg, state, server);
+          }
+
+          if (helper.isCompletedWithErrors(state))
+          {
+            LOG.log(Level.WARNING, "Completed with error: "+errorMsg);
+            println(errorMsg);
+          }
+          else if (!helper.isSuccessful(state) ||
+              helper.isStoppedByError(state))
+          {
+            LOG.log(Level.WARNING, "Error: "+errorMsg);
+            ReplicationCliReturnCode code = ERROR_LAUNCHING_PURGE_HISTORICAL;
+            throw new ReplicationCliException(errorMsg, code, null);
+          }
+        }
+      }
+      catch (NameNotFoundException x)
+      {
+        isOver = true;
+      }
+      catch (NamingException ne)
+      {
+        Message msg = ERR_POOLING_PURGE_HISTORICAL.get();
+        throw new ReplicationCliException(
+          getThrowableMsg(msg, ne), ERROR_CONNECTING, ne);
+      }
+    }
+
+    if (returnCode == ReplicationCliReturnCode.SUCCESSFUL)
+    {
+      printSuccessMessage(uData, taskID);
+    }
+    return returnCode;
+  }
+
+  /**
+   * Checks that historical can actually be purged in the provided baseDNs
+   * for the server.
+   * @param suffixes the suffixes provided by the user.  This Collection is
+   * updated with the base DNs that the user provided interactively.
+   * @param ctx connection to the server.
+   * @param interactive whether to ask the user to provide interactively
+   * base DNs if none of the provided base DNs can be purged.
+   */
+  private void checkSuffixesForPurgeHistorical(Collection<String> suffixes,
+      InitialLdapContext ctx, boolean interactive)
+  {
+    TreeSet<String> availableSuffixes = new TreeSet<String>();
+    TreeSet<String> notReplicatedSuffixes = new TreeSet<String>();
+
+    Collection<ReplicaDescriptor> replicas = getReplicas(ctx);
+    for (ReplicaDescriptor rep : replicas)
+    {
+      String dn = rep.getSuffix().getDN();
+      if (rep.isReplicated())
+      {
+        availableSuffixes.add(dn);
+      }
+      else
+      {
+        notReplicatedSuffixes.add(dn);
+      }
+    }
+
+    checkSuffixesForPurgeHistorical(suffixes, availableSuffixes,
+        notReplicatedSuffixes, interactive);
+  }
+
+  /**
+   * Checks that historical can actually be purged in the provided baseDNs
+   * for the local server.
+   * @param suffixes the suffixes provided by the user.  This Collection is
+   * updated with the base DNs that the user provided interactively.
+   * @param interactive whether to ask the user to provide interactively
+   * base DNs if none of the provided base DNs can be purged.
+   */
+  private void checkSuffixesForLocalPurgeHistorical(Collection<String> suffixes,
+      boolean interactive)
+  {
+    TreeSet<String> availableSuffixes = new TreeSet<String>();
+    TreeSet<String> notReplicatedSuffixes = new TreeSet<String>();
+
+    Collection<ReplicaDescriptor> replicas = getLocalReplicas();
+
+    for (ReplicaDescriptor rep : replicas)
+    {
+      String dn = rep.getSuffix().getDN();
+      if (rep.isReplicated())
+      {
+        availableSuffixes.add(dn);
+      }
+      else
+      {
+        notReplicatedSuffixes.add(dn);
+      }
+    }
+
+    checkSuffixesForPurgeHistorical(suffixes, availableSuffixes,
+        notReplicatedSuffixes, interactive);
+  }
+
+  private Collection<ReplicaDescriptor> getLocalReplicas()
+  {
+    Collection<ReplicaDescriptor> replicas = new ArrayList<ReplicaDescriptor>();
+    ConfigFromFile configFromFile = new ConfigFromFile();
+    configFromFile.readConfiguration();
+    Collection<BackendDescriptor> backends = configFromFile.getBackends();
+    for (BackendDescriptor backend : backends)
+    {
+      for (BaseDNDescriptor baseDN : backend.getBaseDns())
+      {
+        SuffixDescriptor suffix = new SuffixDescriptor();
+        suffix.setDN(baseDN.getDn().toString());
+
+        ReplicaDescriptor replica = new ReplicaDescriptor();
+
+        if (baseDN.getType() == BaseDNDescriptor.Type.REPLICATED)
+        {
+          replica.setReplicationId(baseDN.getReplicaID());
+        }
+        else
+        {
+          replica.setReplicationId(-1);
+        }
+        replica.setBackendName(backend.getBackendID());
+        replica.setSuffix(suffix);
+        suffix.setReplicas(Collections.singleton(replica));
+
+        replicas.add(replica);
+      }
+    }
+    return replicas;
+  }
+
+  private void checkSuffixesForPurgeHistorical(Collection<String> suffixes,
+      Collection<String> availableSuffixes,
+      Collection<String> notReplicatedSuffixes,
+      boolean interactive)
+  {
+    if (availableSuffixes.size() == 0)
+    {
+      println();
+      println(ERR_NO_SUFFIXES_AVAILABLE_TO_PURGE_HISTORICAL.get());
+      suffixes.clear();
+    }
+    else
+    {
+      // Verify that the provided suffixes are configured in the servers.
+      TreeSet<String> notFound = new TreeSet<String>();
+      TreeSet<String> alreadyNotReplicated = new TreeSet<String>();
+      for (String dn : suffixes)
+      {
+        boolean found = false;
+        for (String dn1 : availableSuffixes)
+        {
+          if (Utils.areDnsEqual(dn, dn1))
+          {
+            found = true;
+            break;
+          }
+        }
+        if (!found)
+        {
+          boolean notReplicated = false;
+          for (String s : notReplicatedSuffixes)
+          {
+            if (Utils.areDnsEqual(s, dn))
+            {
+              notReplicated = true;
+              break;
+            }
+          }
+          if (notReplicated)
+          {
+            alreadyNotReplicated.add(dn);
+          }
+          else
+          {
+            notFound.add(dn);
+          }
+        }
+      }
+      suffixes.removeAll(notFound);
+      suffixes.removeAll(alreadyNotReplicated);
+      if (notFound.size() > 0)
+      {
+        println();
+        println(ERR_REPLICATION_PURGE_SUFFIXES_NOT_FOUND.get(
+                Utils.getStringFromCollection(notFound,
+                    Constants.LINE_SEPARATOR)));
+      }
+      if (interactive)
+      {
+        boolean confirmationLimitReached = false;
+        while (suffixes.isEmpty())
+        {
+          boolean noSchemaOrAds = false;
+          for (String s: availableSuffixes)
+          {
+            if (!Utils.areDnsEqual(s, ADSContext.getAdministrationSuffixDN()) &&
+                !Utils.areDnsEqual(s, Constants.SCHEMA_DN) &&
+                !Utils.areDnsEqual(s, Constants.REPLICATION_CHANGES_DN))
+            {
+              noSchemaOrAds = true;
+            }
+          }
+          if (!noSchemaOrAds)
+          {
+            // In interactive mode we do not propose to manage the
+            // administration suffix.
+            println();
+            println(ERR_NO_SUFFIXES_AVAILABLE_TO_PURGE_HISTORICAL.get());
+            break;
+          }
+          else
+          {
+            println();
+            println(ERR_NO_SUFFIXES_SELECTED_TO_PURGE_HISTORICAL.get());
+            for (String dn : availableSuffixes)
+            {
+              if (!Utils.areDnsEqual(dn,
+                  ADSContext.getAdministrationSuffixDN()) &&
+                  !Utils.areDnsEqual(dn, Constants.SCHEMA_DN) &&
+                  !Utils.areDnsEqual(dn, Constants.REPLICATION_CHANGES_DN))
+              {
+                try
+                {
+                  if (askConfirmation(
+                      INFO_REPLICATION_PURGE_HISTORICAL_PROMPT.get(dn), true,
+                      LOG))
+                  {
+                    suffixes.add(dn);
+                  }
+                }
+                catch (CLIException ce)
+                {
+                  println(ce.getMessageObject());
+                  confirmationLimitReached = true;
+                  break;
+                }
+              }
+            }
+          }
+          if (confirmationLimitReached)
+          {
+            suffixes.clear();
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  /**
    * Based on the data provided in the command-line it initializes replication
    * between two servers.
    * @return the error code if the operation failed and SUCCESSFUL if it was
@@ -811,6 +1517,186 @@
     return returnValue;
   }
 
+
+  /**
+   * Updates the contents of the provided PurgeHistoricalUserData
+   * object with the information provided in the command-line.  If some
+   * information is missing, ask the user to provide valid data.
+   * We assume that if this method is called we are in interactive mode.
+   * @param uData the object to be updated.
+   * @return <CODE>true</CODE> if the object was successfully updated and
+   * <CODE>false</CODE> if the user canceled the operation.
+   */
+  private boolean promptIfRequired(PurgeHistoricalUserData uData)
+  {
+    boolean cancelled = false;
+    boolean onlineSet = false;
+
+    boolean firstTry = true;
+    Boolean serverRunning = null;
+
+    InitialLdapContext ctx = null;
+    while (!cancelled && !onlineSet)
+    {
+      boolean promptForConnection = false;
+      if (argParser.connectionArgumentsPresent() && firstTry)
+      {
+        promptForConnection = true;
+      }
+      else
+      {
+        if (serverRunning == null)
+        {
+          serverRunning = Utilities.isServerRunning(
+              Installation.getLocal().getInstanceDirectory());
+        }
+        if (!serverRunning)
+        {
+          try
+          {
+            printlnProgress();
+            promptForConnection =
+              !askConfirmation(
+                  INFO_REPLICATION_PURGE_HISTORICAL_LOCAL_PROMPT.get(),
+                  true, LOG);
+          }
+          catch (CLIException ce)
+          {
+            println(ce.getMessageObject());
+            cancelled = true;
+          }
+        }
+        else
+        {
+          promptForConnection = true;
+        }
+      }
+      if (promptForConnection)
+      {
+        try
+        {
+          ci.run();
+          String host = ci.getHostName();
+          int port = ci.getPortNumber();
+          String adminUid = ci.getAdministratorUID();
+          String adminPwd = ci.getBindPassword();
+
+          ctx = createInitialLdapContextInteracting(ci);
+          if (ctx == null)
+          {
+            cancelled = true;
+          }
+          else
+          {
+            uData.setOnline(true);
+            uData.setAdminUid(adminUid);
+            uData.setAdminPwd(adminPwd);
+            uData.setHostName(host);
+            uData.setPort(port);
+            onlineSet = true;
+          }
+        }
+        catch (ClientException ce)
+        {
+          LOG.log(Level.WARNING, "Client exception "+ce);
+          println();
+          println(ce.getMessageObject());
+          println();
+          ci.resetConnectionArguments();
+        }
+        catch (ArgumentException ae)
+        {
+          LOG.log(Level.WARNING, "Argument exception "+ae);
+          println();
+          println(ae.getMessageObject());
+          println();
+          cancelled = true;
+        }
+      }
+      else
+      {
+        uData.setOnline(false);
+        onlineSet = true;
+      }
+      firstTry = false;
+    }
+
+    if (!cancelled)
+    {
+      int maximumDuration = argParser.getMaximumDuration();
+      /* Prompt for maximum duration */
+      if (!argParser.maximumDurationArg.isPresent())
+      {
+        printlnProgress();
+        maximumDuration = askInteger(
+            INFO_REPLICATION_PURGE_HISTORICAL_MAXIMUM_DURATION_PROMPT.get(),
+            argParser.getDefaultMaximumDuration(), LOG);
+      }
+      uData.setMaximumDuration(maximumDuration);
+    }
+
+    if (!cancelled)
+    {
+      LinkedList<String> suffixes = argParser.getBaseDNs();
+      if (uData.isOnline())
+      {
+        checkSuffixesForPurgeHistorical(suffixes, ctx, true);
+      }
+      else
+      {
+        checkSuffixesForLocalPurgeHistorical(suffixes, true);
+      }
+      cancelled = suffixes.isEmpty();
+      uData.setBaseDNs(suffixes);
+    }
+
+    if (uData.isOnline() && !cancelled)
+    {
+      List<? extends TaskEntry> taskEntries = getAvailableTaskEntries(ctx);
+
+      TaskScheduleInteraction interaction =
+        new TaskScheduleInteraction(uData.getTaskSchedule(), argParser.taskArgs,
+            this, formatter, taskEntries,
+            INFO_PURGE_HISTORICAL_TASK_NAME.get());
+      try
+      {
+        interaction.run();
+      }
+      catch (CLIException ce)
+      {
+        println(ce.getMessageObject());
+        cancelled = true;
+      }
+    }
+
+    if (ctx != null)
+    {
+      try
+      {
+        ctx.close();
+      }
+      catch (Throwable t)
+      {
+      }
+    }
+
+    return !cancelled;
+  }
+
+  private List<? extends TaskEntry> getAvailableTaskEntries(
+      InitialLdapContext ctx)
+  {
+    List<TaskEntry> taskEntries = new ArrayList<TaskEntry>();
+    List<OpenDsException> exceptions = new ArrayList<OpenDsException>();
+    ConfigFromDirContext cfg = new ConfigFromDirContext();
+    cfg.updateTaskInformation(ctx, exceptions, taskEntries);
+    for (OpenDsException ode : exceptions)
+    {
+      LOG.log(Level.WARNING, "Error retrieving task entries: "+ode, ode);
+    }
+    return taskEntries;
+  }
+
   /**
    * Updates the contents of the provided EnableReplicationUserData object
    * with the information provided in the command-line.  If some information
@@ -2734,7 +3620,7 @@
    * Initializes the contents of the provided status replication user data
    * object with what was provided in the command-line without prompting to the
    * user.
-   * @param uData the disable replication user data object to be initialized.
+   * @param uData the status replication user data object to be initialized.
    */
   private void initializeWithArgParser(StatusReplicationUserData uData)
   {
@@ -3525,7 +4411,7 @@
   }
 
   /**
-   * Disbles the replication in the server for the provided suffixes using the
+   * Disables the replication in the server for the provided suffixes using the
    * data in the DisableReplicationUserData object.  This method does not prompt
    * to the user for information if something is missing.
    * @param uData the DisableReplicationUserData object.
@@ -5560,7 +6446,7 @@
     // done).
     if (adsMergeDone)
     {
-      PointAdder pointAdder = new PointAdder();
+      PointAdder pointAdder = new PointAdder(this);
       printProgress(
           INFO_ENABLE_REPLICATION_INITIALIZING_ADS_ALL.get(
               ConnectionUtils.getHostPort(ctxSource)));
@@ -5606,7 +6492,7 @@
       }
       if (adsMergeDone)
       {
-        PointAdder pointAdder = new PointAdder();
+        PointAdder pointAdder = new PointAdder(this);
         printProgress(
             INFO_ENABLE_REPLICATION_INITIALIZING_SCHEMA.get(
                 ConnectionUtils.getHostPort(ctxDestination),
@@ -8968,12 +9854,7 @@
   private CommandBuilder createCommandBuilder(String subcommandName,
       ReplicationUserData uData) throws ArgumentException
   {
-    String commandName =
-      System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
-    if (commandName == null)
-    {
-      commandName = "dsreplication";
-    }
+    String commandName = getCommandName();
 
     CommandBuilder commandBuilder =
       new CommandBuilder(commandName, subcommandName);
@@ -8992,48 +9873,17 @@
       updateCommandBuilder(commandBuilder,
           (InitializeReplicationUserData)uData);
     }
+    else if (subcommandName.equals(
+        ReplicationCliArgumentParser.PURGE_HISTORICAL_SUBCMD_NAME))
+    {
+      // All the arguments for initialize replication are update here.
+      updateCommandBuilder(commandBuilder, (PurgeHistoricalUserData)uData);
+    }
     else
     {
       // Update the arguments used in the console interaction with the
       // actual arguments of dsreplication.
-      if ((ci != null) && (ci.getCommandBuilder() != null))
-      {
-        CommandBuilder interactionBuilder = ci.getCommandBuilder();
-        for (Argument arg : interactionBuilder.getArguments())
-        {
-          if (arg.getLongIdentifier().equals(OPTION_LONG_BINDPWD))
-          {
-            StringArgument bindPasswordArg = new StringArgument("adminPassword",
-                OPTION_SHORT_BINDPWD, "adminPassword", false, false, true,
-                INFO_BINDPWD_PLACEHOLDER.get(), null, null,
-                INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORD.get());
-            bindPasswordArg.addValue(arg.getValue());
-            commandBuilder.addObfuscatedArgument(bindPasswordArg);
-          }
-          else if (arg.getLongIdentifier().equals(OPTION_LONG_BINDPWD_FILE))
-          {
-            FileBasedArgument bindPasswordFileArg = new FileBasedArgument(
-                "adminPasswordFile",
-                OPTION_SHORT_BINDPWD_FILE, "adminPasswordFile", false, false,
-                INFO_BINDPWD_FILE_PLACEHOLDER.get(), null, null,
-                INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORDFILE.get());
-            bindPasswordFileArg.getNameToValueMap().putAll(
-                ((FileBasedArgument)arg).getNameToValueMap());
-            commandBuilder.addArgument(bindPasswordFileArg);
-          }
-          else
-          {
-            if (interactionBuilder.isObfuscated(arg))
-            {
-              commandBuilder.addObfuscatedArgument(arg);
-            }
-            else
-            {
-              commandBuilder.addArgument(arg);
-            }
-          }
-        }
-      }
+      updateCommandBuilderWithConsoleInteraction(commandBuilder, ci);
     }
 
     if (subcommandName.equals(
@@ -9063,6 +9913,97 @@
     return commandBuilder;
   }
 
+  private String getCommandName()
+  {
+    String commandName =
+      System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
+    if (commandName == null)
+    {
+      commandName = "dsreplication";
+    }
+    return commandName;
+  }
+
+  private void updateCommandBuilderWithConsoleInteraction(
+      CommandBuilder commandBuilder,
+      LDAPConnectionConsoleInteraction ci) throws ArgumentException
+  {
+    if ((ci != null) && (ci.getCommandBuilder() != null))
+    {
+      CommandBuilder interactionBuilder = ci.getCommandBuilder();
+      for (Argument arg : interactionBuilder.getArguments())
+      {
+        if (arg.getLongIdentifier().equals(OPTION_LONG_BINDPWD))
+        {
+          StringArgument bindPasswordArg = new StringArgument("adminPassword",
+              OPTION_SHORT_BINDPWD, "adminPassword", false, false, true,
+              INFO_BINDPWD_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORD.get());
+          bindPasswordArg.addValue(arg.getValue());
+          commandBuilder.addObfuscatedArgument(bindPasswordArg);
+        }
+        else if (arg.getLongIdentifier().equals(OPTION_LONG_BINDPWD_FILE))
+        {
+          FileBasedArgument bindPasswordFileArg = new FileBasedArgument(
+              "adminPasswordFile",
+              OPTION_SHORT_BINDPWD_FILE, "adminPasswordFile", false, false,
+              INFO_BINDPWD_FILE_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_REPLICATION_ADMIN_BINDPASSWORDFILE.get());
+          bindPasswordFileArg.getNameToValueMap().putAll(
+              ((FileBasedArgument)arg).getNameToValueMap());
+          commandBuilder.addArgument(bindPasswordFileArg);
+        }
+        else
+        {
+          if (interactionBuilder.isObfuscated(arg))
+          {
+            commandBuilder.addObfuscatedArgument(arg);
+          }
+          else
+          {
+            commandBuilder.addArgument(arg);
+          }
+        }
+      }
+    }
+  }
+
+  private void updateCommandBuilder(CommandBuilder commandBuilder,
+      PurgeHistoricalUserData uData) throws ArgumentException
+  {
+    if (uData.isOnline())
+    {
+      updateCommandBuilderWithConsoleInteraction(commandBuilder, ci);
+      if (uData.getTaskSchedule() != null)
+      {
+        updateCommandBuilderWithTaskSchedule(commandBuilder,
+            uData.getTaskSchedule());
+      }
+    }
+
+    IntegerArgument maximumDurationArg = new IntegerArgument(
+        argParser.maximumDurationArg.getName(),
+        argParser.maximumDurationArg.getShortIdentifier(),
+        argParser.maximumDurationArg.getLongIdentifier(),
+        argParser.maximumDurationArg.isRequired(),
+        argParser.maximumDurationArg.isMultiValued(),
+        argParser.maximumDurationArg.needsValue(),
+        argParser.maximumDurationArg.getValuePlaceholder(),
+        PurgeConflictsHistoricalTask.DEFAULT_MAX_DURATION,
+        argParser.maximumDurationArg.getPropertyName(),
+        argParser.maximumDurationArg.getDescription());
+    maximumDurationArg.addValue(String.valueOf(uData.getMaximumDuration()));
+    commandBuilder.addArgument(maximumDurationArg);
+  }
+
+  private void updateCommandBuilderWithTaskSchedule(
+      CommandBuilder commandBuilder,
+      TaskScheduleUserData taskSchedule)
+  {
+    TaskScheduleUserData.updateCommandBuilderWithTaskSchedule(
+        commandBuilder, taskSchedule);
+  }
+
   private void addGlobalArguments(CommandBuilder commandBuilder,
       ReplicationUserData uData)
   throws ArgumentException
@@ -10064,7 +11005,7 @@
   private boolean mergeRegistries(ADSContext adsCtx1, ADSContext adsCtx2)
   throws ReplicationCliException
   {
-    PointAdder pointAdder = new PointAdder();
+    PointAdder pointAdder = new PointAdder(this);
     try
     {
       LinkedHashSet<PreferredConnection> cnx =
@@ -10516,6 +11457,52 @@
   {
     return argParser.getConnectTimeout();
   }
+
+  private String binDir;
+  /**
+   * Returns the binary/script directory.
+   * @return the binary/script directory.
+   */
+  private String getBinaryDir()
+  {
+    if (binDir == null)
+    {
+      File f = Installation.getLocal().getBinariesDirectory();
+      try
+      {
+        binDir = f.getCanonicalPath();
+      }
+      catch (Throwable t)
+      {
+        binDir = f.getAbsolutePath();
+      }
+      if (binDir.lastIndexOf(File.separatorChar) != (binDir.length() - 1))
+      {
+        binDir += File.separatorChar;
+      }
+    }
+
+    return binDir;
+  }
+
+  /**
+   * Returns the full path of the command-line for a given script name.
+   * @param scriptBasicName the script basic name (with no extension).
+   * @return the full path of the command-line for a given script name.
+   */
+  private String getCommandLinePath(String scriptBasicName)
+  {
+    String cmdLineName;
+    if (Utilities.isWindows())
+    {
+      cmdLineName = getBinaryDir()+scriptBasicName+".bat";
+    }
+    else
+    {
+      cmdLineName = getBinaryDir()+scriptBasicName;
+    }
+    return cmdLineName;
+  }
 }
 
 
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliReturnCode.java b/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliReturnCode.java
index 968ce15..e9fb1dc 100644
--- a/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliReturnCode.java
+++ b/opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliReturnCode.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2007-2008 Sun Microsystems, Inc.
+ *      Copyright 2007-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.server.tools.dsreplication;
@@ -181,7 +181,49 @@
   /**
    * Error disabling replication server.
    */
-  ERROR_DISABLING_REPLICATION_SERVER(25, ERR_REPLICATION_NO_MESSAGE.get());
+  ERROR_DISABLING_REPLICATION_SERVER(25, ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+   * Error executing purge historical.
+   */
+  ERROR_EXECUTING_PURGE_HISTORICAL(26,
+      ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+   * The provided base DNs cannot be purged.
+   */
+  HISTORICAL_CANNOT_BE_PURGED_ON_BASEDN(27,
+      ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+    * Error launching purge historical.
+    */
+  ERROR_LAUNCHING_PURGE_HISTORICAL(28,
+      ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+    * Error loading configuration class in local purge historical.
+    */
+  ERROR_LOCAL_PURGE_HISTORICAL_CLASS_LOAD(29,
+         ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+   * Error starting server in local purge historical.
+   */
+   ERROR_LOCAL_PURGE_HISTORICAL_SERVER_START(30,
+       ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+    * Timeout error in local purge historical.
+    */
+  ERROR_LOCAL_PURGE_HISTORICAL_TIMEOUT(31,
+       ERR_REPLICATION_NO_MESSAGE.get()),
+
+  /**
+    * Generic error executing local purge historical.
+    */
+  ERROR_LOCAL_PURGE_HISTORICAL_EXECUTING(32,
+      ERR_REPLICATION_NO_MESSAGE.get());
 
 
   private Message message;
diff --git a/opends/src/server/org/opends/server/tools/dsreplication/StatusReplicationUserData.java b/opends/src/server/org/opends/server/tools/dsreplication/StatusReplicationUserData.java
index 8a83b3b..cb8e1e3 100644
--- a/opends/src/server/org/opends/server/tools/dsreplication/StatusReplicationUserData.java
+++ b/opends/src/server/org/opends/server/tools/dsreplication/StatusReplicationUserData.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008-2009 Sun Microsystems, Inc.
+ *      Copyright 2008-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.server.tools.dsreplication;
@@ -33,7 +33,7 @@
  * interactive mode the ReplicationCliArgumentParser is not enough.
  *
  */
-public class StatusReplicationUserData extends InitializeAllReplicationUserData
+public class StatusReplicationUserData extends MonoServerReplicationUserData
 {
   private boolean scriptFriendly;
 
diff --git a/opends/src/server/org/opends/server/tools/tasks/TaskClient.java b/opends/src/server/org/opends/server/tools/tasks/TaskClient.java
index ac0348f..cc2d73c 100644
--- a/opends/src/server/org/opends/server/tools/tasks/TaskClient.java
+++ b/opends/src/server/org/opends/server/tools/tasks/TaskClient.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2009 Sun Microsystems, Inc.
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.server.tools.tasks;
@@ -100,29 +100,86 @@
   }
 
   /**
-   * Schedule a task for execution by writing an entry to the task backend.
-   *
-   * @param information to be scheduled
-   * @return String task ID assigned the new task
-   * @throws IOException if there is a stream communication problem
-   * @throws LDAPException if there is a problem getting information
-   *         out to the directory
-   * @throws ASN1Exception if there is a problem with the encoding
-   * @throws TaskClientException if there is a problem with the task entry
+   * Returns the ID of the task entry for a given list of task attributes.
+   * @param taskAttributes the task attributes.
+   * @return the ID of the task entry for a given list of task attributes.
    */
-  public synchronized TaskEntry schedule(TaskScheduleInformation information)
-          throws LDAPException, IOException, ASN1Exception, TaskClientException
+  public static String getTaskID(List<RawAttribute> taskAttributes)
   {
     String taskID = null;
-    ByteString entryDN = null;
-    boolean scheduleRecurring = false;
 
-    LDAPReader reader = connection.getLDAPReader();
-    LDAPWriter writer = connection.getLDAPWriter();
+    RawAttribute recurringIDAttr = getAttribute(ATTR_RECURRING_TASK_ID,
+        taskAttributes);
+
+    if (recurringIDAttr != null) {
+      taskID = recurringIDAttr.getValues().get(0).toString();
+    } else {
+      RawAttribute taskIDAttr = getAttribute(ATTR_TASK_ID,
+          taskAttributes);
+      taskID = taskIDAttr.getValues().get(0).toString();
+    }
+
+    return taskID;
+  }
+
+  private static RawAttribute getAttribute(String attrName,
+      List<RawAttribute> taskAttributes)
+  {
+    for (RawAttribute attr : taskAttributes)
+    {
+      if (attr.getAttributeType().equalsIgnoreCase(attrName))
+      {
+        return attr;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the DN of the task entry for a given list of task attributes.
+   * @param taskAttributes the task attributes.
+   * @return the DN of the task entry for a given list of task attributes.
+   */
+  public static String getTaskDN(List<RawAttribute> taskAttributes)
+  {
+    String entryDN = null;
+    String taskID = getTaskID(taskAttributes);
+    RawAttribute recurringIDAttr = getAttribute(ATTR_RECURRING_TASK_ID,
+        taskAttributes);
+
+    if (recurringIDAttr != null) {
+      entryDN = ATTR_RECURRING_TASK_ID + "=" +
+      taskID + "," + RECURRING_TASK_BASE_RDN + "," + DN_TASK_ROOT;
+    } else {
+      entryDN = ATTR_TASK_ID + "=" + taskID + "," +
+      SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT;
+    }
+    return entryDN;
+  }
+
+  private static boolean isScheduleRecurring(
+      TaskScheduleInformation information)
+  {
+    boolean scheduleRecurring = false;
 
     if (information.getRecurringDateTime() != null) {
       scheduleRecurring = true;
     }
+    return scheduleRecurring;
+  }
+
+  /**
+   * This is a commodity method that returns the common attributes (those
+   * related to scheduling) of a task entry for a given
+   * {@link TaskScheduleInformation} object.
+   * @param information the scheduling information.
+   * @return the schedule attributes of the task entry.
+   */
+  public static ArrayList<RawAttribute> getTaskAttributes(
+      TaskScheduleInformation information)
+  {
+    String taskID = null;
+    boolean scheduleRecurring = isScheduleRecurring(information);
 
     if (scheduleRecurring) {
       taskID = information.getTaskId();
@@ -130,18 +187,12 @@
         taskID = information.getTaskClass().getSimpleName() +
           "-" + UUID.randomUUID().toString();
       }
-      entryDN = ByteString.valueOf(ATTR_RECURRING_TASK_ID + "=" +
-        taskID + "," + RECURRING_TASK_BASE_RDN + "," + DN_TASK_ROOT);
     } else {
       // Use a formatted time/date for the ID so that is remotely useful
       SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
       taskID = df.format(new Date());
-
-      entryDN = ByteString.valueOf(ATTR_TASK_ID + "=" + taskID + "," +
-        SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT);
     }
 
-    ArrayList<Control> controls = new ArrayList<Control>();
     ArrayList<RawAttribute> attributes = new ArrayList<RawAttribute>();
 
     ArrayList<ByteString> ocValues = new ArrayList<ByteString>(3);
@@ -238,6 +289,30 @@
 
     information.addTaskAttributes(attributes);
 
+    return attributes;
+  }
+
+  /**
+   * Schedule a task for execution by writing an entry to the task backend.
+   *
+   * @param information to be scheduled
+   * @return String task ID assigned the new task
+   * @throws IOException if there is a stream communication problem
+   * @throws LDAPException if there is a problem getting information
+   *         out to the directory
+   * @throws ASN1Exception if there is a problem with the encoding
+   * @throws TaskClientException if there is a problem with the task entry
+   */
+  public synchronized TaskEntry schedule(TaskScheduleInformation information)
+          throws LDAPException, IOException, ASN1Exception, TaskClientException
+  {
+    LDAPReader reader = connection.getLDAPReader();
+    LDAPWriter writer = connection.getLDAPWriter();
+
+    ArrayList<Control> controls = new ArrayList<Control>();
+    ArrayList<RawAttribute> attributes = getTaskAttributes(information);
+
+    ByteString entryDN = ByteString.valueOf(getTaskDN(attributes));
     AddRequestProtocolOp addRequest = new AddRequestProtocolOp(entryDN,
                                                                attributes);
     LDAPMessage requestMessage =
@@ -271,7 +346,7 @@
               LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
               addResponse.getErrorMessage());
     }
-    return getTaskEntry(taskID);
+    return getTaskEntry(getTaskID(attributes));
   }
 
   /**
diff --git a/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java b/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java
index bd5ad99..2110b15 100644
--- a/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java
+++ b/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008 Sun Microsystems, Inc.
+ *      Copyright 2008-2010 Sun Microsystems, Inc.
  */
 
 package org.opends.server.tools.tasks;
@@ -65,7 +65,7 @@
    *
    * @return class of the tasks implementation
    */
-  Class getTaskClass();
+  Class<?> getTaskClass();
 
 
   /**
diff --git a/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInteraction.java b/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInteraction.java
new file mode 100644
index 0000000..acd57c0
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/tasks/TaskScheduleInteraction.java
@@ -0,0 +1,477 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.tasks;
+
+import static org.opends.messages.AdminToolMessages.*;
+import static org.opends.messages.ToolMessages.*;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.quicksetup.util.ProgressMessageFormatter;
+import org.opends.server.admin.client.cli.TaskScheduleArgs;
+import org.opends.server.backends.task.FailedDependencyAction;
+import org.opends.server.backends.task.RecurringTask;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.util.StaticUtils;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.MenuBuilder;
+import org.opends.server.util.cli.MenuResult;
+
+/**
+ * A class that is in charge of interacting with the user to ask about
+ * scheduling options for a task.
+ * <br>
+ * It takes as argument an {@link TaskScheduleArgs} object with the arguments
+ * provided by the user and updates the provided {@link TaskScheduleUserData}
+ * with the information provided by the user.
+ *
+ */
+public class TaskScheduleInteraction
+{
+  private boolean headerDisplayed;
+  private final TaskScheduleUserData uData;
+  private final TaskScheduleArgs args;
+  private final ConsoleApplication app;
+  private final Message taskName;
+  private final List<? extends TaskEntry> taskEntries;
+  private final ProgressMessageFormatter formatter;
+
+  /**
+   * The enumeration used by the menu displayed to ask the user about the
+   * type of scheduling (if any) to be done.
+   *
+   */
+  private enum ScheduleOption {
+    RUN_NOW(INFO_RUN_TASK_NOW.get()),
+    RUN_LATER(INFO_RUN_TASK_LATER.get()),
+    SCHEDULE_TASK(INFO_SCHEDULE_TASK.get());
+
+    private Message prompt;
+    private ScheduleOption(Message prompt)
+    {
+      this.prompt = prompt;
+    }
+    Message getPrompt()
+    {
+      return prompt;
+    }
+
+    /**
+     * The default option to be proposed to the user.
+     * @return the default option to be proposed to the user.
+     */
+    public static ScheduleOption defaultValue()
+    {
+      return RUN_NOW;
+    }
+  };
+
+  /**
+   * Default constructor.
+   * @param uData the task schedule user data.
+   * @param args the object with the arguments provided by the user.  The code
+   * assumes that the arguments have already been parsed.
+   * @param app the console application object used to prompt for data.
+   * @param formatter the formatter to be used to generated the messages.
+   * @param taskEntries the list of task entries defined in the server.
+   * @param taskName the name of the task to be used in the prompt messages.
+   */
+  public TaskScheduleInteraction(TaskScheduleUserData uData,
+      TaskScheduleArgs args, ConsoleApplication app,
+      ProgressMessageFormatter formatter, List<? extends TaskEntry> taskEntries,
+      Message taskName)
+  {
+    this.uData = uData;
+    this.args = args;
+    this.app = app;
+    this.taskName = taskName;
+    this.taskEntries = taskEntries;
+    this.formatter = formatter;
+  }
+
+  /**
+   * Executes the interaction with the user.
+   * @throws CLIException if there is an error prompting the user.
+   */
+  public void run() throws CLIException
+  {
+    headerDisplayed = false;
+
+    runStartNowOrSchedule();
+
+    runCompletionNotification();
+
+    runErrorNotification();
+
+    runDependency();
+
+    if (!uData.getDependencyIds().isEmpty())
+    {
+      runFailedDependencyAction();
+    }
+  }
+
+  private void runFailedDependencyAction() throws CLIException
+  {
+    if (args.dependencyArg.isPresent())
+    {
+      uData.setFailedDependencyAction(args.getFailedDependencyAction());
+    }
+    else
+    {
+      askForFailedDependencyAction();
+    }
+  }
+
+  private void askForFailedDependencyAction() throws CLIException
+  {
+    checkHeaderDisplay();
+
+    MenuBuilder<FailedDependencyAction> builder =
+      new MenuBuilder<FailedDependencyAction>(app);
+    builder.setPrompt(INFO_TASK_FAILED_DEPENDENCY_ACTION_PROMPT.get());
+    builder.addCancelOption(false);
+    for (FailedDependencyAction choice : FailedDependencyAction.values())
+    {
+      MenuResult<FailedDependencyAction> result = MenuResult.success(choice);
+
+      builder.addNumberedOption(choice.getDisplayName(), result);
+
+      if (choice.equals(FailedDependencyAction.defaultValue()))
+      {
+        builder.setDefault(choice.getDisplayName(), result);
+      }
+    }
+    MenuResult<FailedDependencyAction> m = builder.toMenu().run();
+    if (m.isSuccess())
+    {
+      uData.setFailedDependencyAction(m.getValue());
+    }
+    else
+    {
+      throw new CLIException(Message.EMPTY);
+    }
+
+  }
+
+  private void runDependency() throws CLIException
+  {
+    if (args.dependencyArg.isPresent())
+    {
+      uData.setDependencyIds(args.getDependencyIds());
+    }
+    else if (!taskEntries.isEmpty())
+    {
+      askForDependency();
+    }
+  }
+
+  private void askForDependency() throws CLIException
+  {
+    checkHeaderDisplay();
+
+    boolean hasDependencies =
+      app.confirmAction(INFO_TASK_HAS_DEPENDENCIES_PROMPT.get(), false);
+    if (hasDependencies)
+    {
+      printAvailableDependencyTaskMessage();
+      HashSet<String> dependencies = new HashSet<String>();
+      while (true)
+      {
+        String dependencyID =
+          app.readLineOfInput(INFO_TASK_DEPENDENCIES_PROMPT.get());
+        if (dependencyID != null && !dependencyID.isEmpty())
+        {
+          if (isTaskIDDefined(dependencyID))
+          {
+            dependencies.add(dependencyID);
+          }
+          else
+          {
+            printTaskIDNotDefinedMessage(dependencyID);
+          }
+        }
+        else
+        {
+          break;
+        }
+      }
+      uData.setDependencyIds(new ArrayList<String>(dependencies));
+    }
+    else
+    {
+      List<String> empty = Collections.emptyList();
+      uData.setDependencyIds(empty);
+    }
+  }
+
+  private void printAvailableDependencyTaskMessage()
+  {
+    StringBuilder sb = new StringBuilder();
+    String separator = formatter.getLineBreak().toString() +
+    formatter.getTab().toString();
+    for (TaskEntry entry : taskEntries)
+    {
+      sb.append(separator);
+      sb.append(entry.getId());
+    }
+    app.printlnProgress();
+    app.printProgress(INFO_AVAILABLE_DEFINED_TASKS.get(sb.toString()));
+    app.printlnProgress();
+    app.printlnProgress();
+
+  }
+
+  private void printTaskIDNotDefinedMessage(String dependencyID)
+  {
+    app.println();
+    app.println(ERR_DEPENDENCY_TASK_NOT_DEFINED.get(dependencyID));
+  }
+
+  private boolean isTaskIDDefined(String dependencyID)
+  {
+    boolean taskIDDefined = false;
+    for (TaskEntry entry : taskEntries)
+    {
+      if (dependencyID.equalsIgnoreCase(entry.getId()))
+      {
+        taskIDDefined = true;
+        break;
+      }
+    }
+    return taskIDDefined;
+  }
+
+  private void runErrorNotification() throws CLIException
+  {
+    if (args.errorNotificationArg.isPresent())
+    {
+      uData.setNotifyUponErrorEmailAddresses(
+          args.getNotifyUponErrorEmailAddresses());
+    }
+    else
+    {
+      askForErrorNotification();
+    }
+  }
+
+  private void askForErrorNotification() throws CLIException
+  {
+    List<String> addresses =
+      askForEmailNotification(INFO_HAS_ERROR_NOTIFICATION_PROMPT.get(),
+          INFO_ERROR_NOTIFICATION_PROMPT.get());
+    uData.setNotifyUponErrorEmailAddresses(addresses);
+  }
+
+  private List<String> askForEmailNotification(Message hasNotificationPrompt,
+      Message emailAddressPrompt) throws CLIException
+  {
+    checkHeaderDisplay();
+
+    List<String> addresses = new ArrayList<String>();
+    boolean hasNotification =
+      app.confirmAction(hasNotificationPrompt, false);
+    if (hasNotification)
+    {
+      HashSet<String> set = new HashSet<String>();
+      while (true)
+      {
+        String address =
+          app.readLineOfInput(emailAddressPrompt);
+        if (address != null && !address.isEmpty())
+        {
+          if (!StaticUtils.isEmailAddress(address)) {
+            app.println(ERR_INVALID_EMAIL_ADDRESS.get(address));
+          }
+          else
+          {
+            set.add(address);
+          }
+        }
+        else
+        {
+          break;
+        }
+      }
+      addresses.addAll(set);
+    }
+    return addresses;
+  }
+
+  private void runCompletionNotification() throws CLIException
+  {
+    if (args.completionNotificationArg.isPresent())
+    {
+      uData.setNotifyUponCompletionEmailAddresses(
+          args.getNotifyUponCompletionEmailAddresses());
+    }
+    else
+    {
+      askForCompletionNotification();
+    }
+  }
+
+  private void askForCompletionNotification() throws CLIException
+  {
+    List<String> addresses =
+      askForEmailNotification(INFO_HAS_COMPLETION_NOTIFICATION_PROMPT.get(),
+          INFO_COMPLETION_NOTIFICATION_PROMPT.get());
+    uData.setNotifyUponCompletionEmailAddresses(addresses);
+  }
+
+  private void runStartNowOrSchedule() throws CLIException
+  {
+    if (args.startArg.isPresent())
+    {
+      uData.setStartDate(args.getStartDateTime());
+      uData.setStartNow(args.isStartNow());
+    }
+    if (args.recurringArg.isPresent())
+    {
+      uData.setRecurringDateTime(args.getRecurringDateTime());
+      uData.setStartNow(false);
+    }
+    if (!args.startArg.isPresent() &&
+        !args.recurringArg.isPresent())
+    {
+      askToStartNowOrSchedule();
+    }
+  }
+
+  private void askToStartNowOrSchedule() throws CLIException
+  {
+    checkHeaderDisplay();
+
+    MenuBuilder<ScheduleOption> builder = new MenuBuilder<ScheduleOption>(app);
+    builder.setPrompt(INFO_TASK_SCHEDULE_PROMPT.get(taskName));
+    builder.addCancelOption(false);
+    for (ScheduleOption choice : ScheduleOption.values())
+    {
+      MenuResult<ScheduleOption> result = MenuResult.success(choice);
+      if (choice == ScheduleOption.defaultValue())
+      {
+        builder.setDefault(choice.getPrompt(), result);
+      }
+      builder.addNumberedOption(choice.getPrompt(), result);
+    }
+    MenuResult<ScheduleOption> m = builder.toMenu().run();
+    if (m.isSuccess())
+    {
+      switch (m.getValue())
+      {
+        case RUN_NOW:
+          uData.setStartNow(true);
+          break;
+        case RUN_LATER:
+          uData.setStartNow(false);
+          askForStartDate();
+          break;
+        case SCHEDULE_TASK:
+          uData.setStartNow(false);
+          askForTaskSchedule();
+          break;
+      }
+    }
+    else
+    {
+      throw new CLIException(Message.EMPTY);
+    }
+  }
+
+  private void askForStartDate() throws CLIException
+  {
+    checkHeaderDisplay();
+
+    Date startDate = null;
+    while (startDate == null)
+    {
+      String sDate = app.readInput(INFO_TASK_START_DATE_PROMPT.get(), null);
+      try {
+        startDate = StaticUtils.parseDateTimeString(sDate);
+        // Check that the provided date is not previous to the current date.
+        Date currentDate = new Date(System.currentTimeMillis());
+        if (currentDate.after(startDate))
+        {
+          app.printProgress(ERR_START_DATETIME_ALREADY_PASSED.get(sDate));
+          app.printlnProgress();
+          app.printlnProgress();
+          startDate = null;
+        }
+      } catch (ParseException pe) {
+        app.println(ERR_START_DATETIME_FORMAT.get());
+        app.println();
+      }
+    }
+    uData.setStartDate(startDate);
+  }
+
+  private void askForTaskSchedule() throws CLIException
+  {
+    checkHeaderDisplay();
+
+    String schedule = null;
+
+    while (schedule == null)
+    {
+      schedule = app.readInput(INFO_TASK_RECURRING_SCHEDULE_PROMPT.get(),
+          null);
+      try
+      {
+        RecurringTask.parseTaskTab(schedule);
+        app.printlnProgress();
+      }
+      catch (DirectoryException de)
+      {
+        schedule = null;
+        app.println(ERR_RECURRING_SCHEDULE_FORMAT_ERROR.get(
+            de.getMessageObject()));
+        app.println();
+      }
+    }
+    uData.setRecurringDateTime(schedule);
+  }
+
+  private void checkHeaderDisplay()
+  {
+    if (!headerDisplayed)
+    {
+      app.printlnProgress();
+      app.printProgress(INFO_TASK_SCHEDULE_PROMPT_HEADER.get());
+      app.printlnProgress();
+      headerDisplayed = true;
+    }
+    app.printlnProgress();
+
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/tasks/TaskScheduleUserData.java b/opends/src/server/org/opends/server/tools/tasks/TaskScheduleUserData.java
new file mode 100644
index 0000000..c68e989
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/tasks/TaskScheduleUserData.java
@@ -0,0 +1,303 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.tasks;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.opends.server.admin.client.cli.TaskScheduleArgs;
+import org.opends.server.backends.task.FailedDependencyAction;
+import org.opends.server.util.StaticUtils;
+import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.args.StringArgument;
+import org.opends.server.util.cli.CommandBuilder;
+
+/**
+ * A generic data structure that contains the data that the user provided to
+ * schedule a task.
+ * <br>
+ * The main difference with {@link TaskScheduleInformation} is that this class
+ * is completely agnostic of the execution.
+ */
+public class TaskScheduleUserData
+{
+  private boolean startNow;
+  private Date startDate;
+  private String recurringDateTime;
+  private final List<String> dependencyIds =
+    new ArrayList<String>();
+  private FailedDependencyAction failedDependencyAction;
+  private final List<String> notifyUponCompletionEmailAddresses =
+    new ArrayList<String>();
+  private final List<String> notifyUponErrorEmailAddresses =
+    new ArrayList<String>();
+
+  /**
+   * Whether the arguments provided by the user, indicate that the task should
+   * be executed immediately.
+   * @return {@code true} if the task must be executed immediately and
+   * {@code false} otherwise.
+   */
+  public boolean isStartNow()
+  {
+    return startNow;
+  }
+
+  /**
+   * Sets whether the arguments provided by the user, indicate that the task
+   * should be executed immediately.
+   * @param startNow {@code true} if the task must be executed immediately and
+   * {@code false} otherwise.
+   */
+  public void setStartNow(boolean startNow)
+  {
+    this.startNow = startNow;
+  }
+
+  /**
+   * Gets the date at which this task should be scheduled to start.
+   *
+   * @return date/time at which the task should be scheduled
+   */
+  public Date getStartDate()
+  {
+    return startDate;
+  }
+
+  /**
+   * Sets the date at which this task should be scheduled to start.
+   *
+   * @param startDate the date/time at which the task should be scheduled
+   */
+  public void setStartDate(Date startDate)
+  {
+    this.startDate = startDate;
+  }
+
+  /**
+   * Gets the date/time pattern for recurring task schedule.
+   *
+   * @return recurring date/time pattern at which the task
+   *         should be scheduled.
+   */
+  public String getRecurringDateTime()
+  {
+    return recurringDateTime;
+  }
+
+  /**
+   * Sets the date/time pattern for recurring task schedule.
+   *
+   * @param recurringDateTime recurring date/time pattern at which the task
+   *         should be scheduled.
+   */
+  public void setRecurringDateTime(String recurringDateTime)
+  {
+    this.recurringDateTime = recurringDateTime;
+  }
+
+  /**
+   * Gets a list of task IDs upon which this task is dependent.
+   *
+   * @return list of task IDs
+   */
+  public List<String> getDependencyIds()
+  {
+    return dependencyIds;
+  }
+
+  /**
+   * Sets the list of task IDs upon which this task is dependent.
+   *
+   * @param dependencyIds list of task IDs
+   */
+  public void setDependencyIds(List<String> dependencyIds)
+  {
+    this.dependencyIds.clear();
+    this.dependencyIds.addAll(dependencyIds);
+  }
+
+  /**
+   * Gets the action to take should one of the dependent task fail.
+   *
+   * @return action to take
+   */
+  public FailedDependencyAction getFailedDependencyAction()
+  {
+    return failedDependencyAction;
+  }
+
+  /**
+   * Sets the action to take should one of the dependent task fail.
+   *
+   * @param failedDependencyAction the action to take
+   */
+  public void setFailedDependencyAction(
+      FailedDependencyAction failedDependencyAction)
+  {
+    this.failedDependencyAction = failedDependencyAction;
+  }
+
+  /**
+   * Gets a list of email address to which an email will be sent when this
+   * task completes.
+   *
+   * @return list of email addresses
+   */
+  public List<String> getNotifyUponCompletionEmailAddresses()
+  {
+    return notifyUponCompletionEmailAddresses;
+  }
+
+  /**
+   * Sets the list of email address to which an email will be sent when this
+   * task completes.
+   *
+   * @param notifyUponCompletionEmailAddresses the list of email addresses
+   */
+  public void setNotifyUponCompletionEmailAddresses(
+      List<String> notifyUponCompletionEmailAddresses)
+  {
+    this.notifyUponCompletionEmailAddresses.clear();
+    this.notifyUponCompletionEmailAddresses.addAll(
+        notifyUponCompletionEmailAddresses);
+  }
+
+  /**
+   * Gets the list of email address to which an email will be sent if this
+   * task encounters an error during execution.
+   *
+   * @return list of email addresses
+   */
+  public List<String> getNotifyUponErrorEmailAddresses()
+  {
+    return notifyUponErrorEmailAddresses;
+  }
+
+  /**
+   * Sets the list of email address to which an email will be sent if this
+   * task encounters an error during execution.
+   *
+   * @param notifyUponErrorEmailAddresses the list of email addresses
+   */
+  public void setNotifyUponErrorEmailAddresses(
+      List<String> notifyUponErrorEmailAddresses)
+  {
+    this.notifyUponErrorEmailAddresses.clear();
+    this.notifyUponErrorEmailAddresses.addAll(notifyUponErrorEmailAddresses);
+  }
+
+
+  /**
+   * An static utility method that can be used to update the object used to
+   * display the equivalent command-line with the contents of a given
+   * task schedule object.
+   * @param commandBuilder the command builder.
+   * @param taskSchedule the task schedule.
+   */
+  public static void updateCommandBuilderWithTaskSchedule(
+      CommandBuilder commandBuilder,
+      TaskScheduleUserData taskSchedule)
+  {
+    TaskScheduleArgs argsToClone = new TaskScheduleArgs();
+    String sDate = null;
+    String recurringDateTime = null;
+    if (!taskSchedule.isStartNow())
+    {
+      Date date = taskSchedule.getStartDate();
+      if (date != null)
+      {
+        sDate = StaticUtils.formatDateTimeString(date);
+      }
+      recurringDateTime = taskSchedule.getRecurringDateTime();
+    }
+
+    String sFailedDependencyAction = null;
+    FailedDependencyAction fAction = taskSchedule.getFailedDependencyAction();
+    if (fAction != null)
+    {
+      sFailedDependencyAction = fAction.name();
+    }
+    String[] sValues = {sDate, recurringDateTime, sFailedDependencyAction};
+    StringArgument[] args = {argsToClone.startArg,
+        argsToClone.recurringArg, argsToClone.failedDependencyActionArg};
+    for (int i=0; i<sValues.length; i++)
+    {
+      if (sValues[i] != null)
+      {
+        commandBuilder.addArgument(getArgument(args[i],
+            Collections.singleton(sValues[i])));
+      }
+    }
+
+    List<?>[] values = {taskSchedule.getDependencyIds(),
+        taskSchedule.getNotifyUponCompletionEmailAddresses(),
+        taskSchedule.getNotifyUponErrorEmailAddresses()};
+    args = new StringArgument[]{argsToClone.dependencyArg,
+        argsToClone.completionNotificationArg,
+        argsToClone.errorNotificationArg};
+
+    for (int i=0; i<values.length; i++)
+    {
+      if (!values[i].isEmpty())
+      {
+        commandBuilder.addArgument(getArgument(args[i],
+            values[i]));
+      }
+    }
+  }
+
+  private static StringArgument getArgument(
+      StringArgument argToClone, Collection<?> values)
+  {
+    StringArgument arg;
+    try
+    {
+      arg = new StringArgument(argToClone.getName(),
+          argToClone.getShortIdentifier(), argToClone.getLongIdentifier(),
+          argToClone.isRequired(), argToClone.isMultiValued(),
+          argToClone.needsValue(),
+          argToClone.getValuePlaceholder(),
+          argToClone.getDefaultValue(),
+          argToClone.getPropertyName(),
+          argToClone.getDescription());
+    }
+    catch (ArgumentException e)
+    {
+      // This is a bug.
+      throw new RuntimeException("Unexpected error: "+e, e);
+    }
+    for (Object v : values)
+    {
+      arg.addValue(String.valueOf(v));
+    }
+    return arg;
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/tasks/TaskTool.java b/opends/src/server/org/opends/server/tools/tasks/TaskTool.java
index 929dd84..7c3e5af 100644
--- a/opends/src/server/org/opends/server/tools/tasks/TaskTool.java
+++ b/opends/src/server/org/opends/server/tools/tasks/TaskTool.java
@@ -27,6 +27,7 @@
 
 package org.opends.server.tools.tasks;
 
+import org.opends.server.util.args.Argument;
 import org.opends.server.util.args.BooleanArgument;
 import org.opends.server.util.args.LDAPConnectionArgumentParser;
 import org.opends.server.util.args.ArgumentException;
@@ -37,7 +38,6 @@
 import static org.opends.server.util.StaticUtils.wrapText;
 import static org.opends.server.util.StaticUtils.getExceptionMessage;
 import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH;
-import org.opends.server.util.StaticUtils;
 import org.opends.server.protocols.asn1.ASN1Exception;
 import org.opends.server.tools.LDAPConnection;
 import org.opends.server.tools.LDAPConnectionException;
@@ -46,6 +46,7 @@
 import org.opends.server.types.LDAPException;
 import org.opends.server.types.OpenDsException;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.admin.client.cli.TaskScheduleArgs;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.backends.task.FailedDependencyAction;
 import org.opends.messages.Message;
@@ -53,14 +54,10 @@
 import static org.opends.messages.TaskMessages.*;
 
 import java.io.PrintStream;
-import java.text.ParseException;
 import java.util.Date;
 import java.util.Set;
 import java.util.HashSet;
 import java.util.List;
-import java.util.LinkedList;
-import java.util.EnumSet;
-import java.util.Collections;
 import java.io.IOException;
 
 /**
@@ -75,9 +72,17 @@
    * this operation to run immediately as a task as opposed to running
    * the operation in the local VM.
    */
-  public static final String NOW = "0";
+  public static final String NOW = TaskScheduleArgs.NOW;
 
+  /**
+   * The error code used by the mixed-script to know if the java
+   * arguments for the off-line mode must be used.
+   */
   private static final int RUN_OFFLINE = 51;
+  /**
+   * The error code used by the mixed-script to know if the java
+   * arguments for the on-line mode must be used.
+   */
   private static final int RUN_ONLINE = 52;
 
   // Number of milliseconds this utility will wait before reloading
@@ -86,25 +91,9 @@
 
   private LDAPConnectionArgumentParser argParser;
 
-  // Argument for describing the task's start time
-  private StringArgument startArg;
+  private TaskScheduleArgs taskScheduleArgs;
 
-  // Argument to indicate a recurring task
-  private StringArgument recurringArg;
-
-  // Argument for specifying completion notifications
-  private StringArgument completionNotificationArg;
-
-  // Argument for specifying error notifications
-  private StringArgument errorNotificationArg;
-
-  // Argument for specifying dependency
-  private StringArgument dependencyArg;
-
-  // Argument for specifying a failed dependency action
-  private StringArgument failedDependencyActionArg;
-
-  // Argument used to know whether we must test if we must run in offline
+  // Argument used to know whether we must test if we must run in off-line
   // mode.
   private BooleanArgument testIfOfflineArg;
 
@@ -160,66 +149,17 @@
       argParser.addArgument(noPropertiesFileArgument);
       argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
 
-      startArg = new StringArgument(
-        OPTION_LONG_START_DATETIME,
-        OPTION_SHORT_START_DATETIME,
-        OPTION_LONG_START_DATETIME, false, false,
-        true, INFO_START_DATETIME_PLACEHOLDER.get(),
-        null, null,
-        INFO_DESCRIPTION_START_DATETIME.get());
-      argParser.addArgument(startArg, taskGroup);
+      taskScheduleArgs = new TaskScheduleArgs();
 
-      recurringArg = new StringArgument(
-        OPTION_LONG_RECURRING_TASK,
-        OPTION_SHORT_RECURRING_TASK,
-        OPTION_LONG_RECURRING_TASK, false, false,
-        true, INFO_RECURRING_TASK_PLACEHOLDER.get(),
-        null, null,
-        INFO_DESCRIPTION_RECURRING_TASK.get());
-      argParser.addArgument(recurringArg, taskGroup);
+      for (Argument arg : taskScheduleArgs.getArguments())
+      {
+        argParser.addArgument(arg, taskGroup);
+      }
 
-      completionNotificationArg = new StringArgument(
-        OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
-        OPTION_SHORT_COMPLETION_NOTIFICATION_EMAIL,
-        OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
-        false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
-        null, null, INFO_DESCRIPTION_TASK_COMPLETION_NOTIFICATION.get());
-      argParser.addArgument(completionNotificationArg, taskGroup);
-
-      errorNotificationArg = new StringArgument(
-        OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
-        OPTION_SHORT_ERROR_NOTIFICATION_EMAIL,
-        OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
-        false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
-        null, null, INFO_DESCRIPTION_TASK_ERROR_NOTIFICATION.get());
-      argParser.addArgument(errorNotificationArg, taskGroup);
-
-      dependencyArg = new StringArgument(
-        OPTION_LONG_DEPENDENCY,
-        OPTION_SHORT_DEPENDENCY,
-        OPTION_LONG_DEPENDENCY,
-        false, true, true, INFO_TASK_ID_PLACEHOLDER.get(),
-        null, null, INFO_DESCRIPTION_TASK_DEPENDENCY_ID.get());
-      argParser.addArgument(dependencyArg, taskGroup);
-
-      Set<FailedDependencyAction> fdaValSet =
-        EnumSet.allOf(FailedDependencyAction.class);
-      failedDependencyActionArg = new StringArgument(
-        OPTION_LONG_FAILED_DEPENDENCY_ACTION,
-        OPTION_SHORT_FAILED_DEPENDENCY_ACTION,
-        OPTION_LONG_FAILED_DEPENDENCY_ACTION,
-        false, true, true, INFO_ACTION_PLACEHOLDER.get(),
-        null, null, INFO_DESCRIPTION_TASK_FAILED_DEPENDENCY_ACTION.get(
-        StaticUtils.collectionToString(fdaValSet, ","),
-        FailedDependencyAction.defaultValue().name()));
-      argParser.addArgument(failedDependencyActionArg, taskGroup);
-
-      testIfOfflineArg = new BooleanArgument(
-        "testIfOffline", null, "testIfOffline",
-        INFO_DESCRIPTION_TEST_IF_OFFLINE.get());
+      testIfOfflineArg = new BooleanArgument("testIfOffline", null,
+          "testIfOffline", INFO_DESCRIPTION_TEST_IF_OFFLINE.get());
       testIfOfflineArg.setHidden(true);
       argParser.addArgument(testIfOfflineArg);
-
     } catch (ArgumentException e) {
       // should never happen
     }
@@ -238,81 +178,13 @@
    */
   protected void validateTaskArgs() throws ArgumentException, CLIException
   {
-    if ((startArg.isPresent() || recurringArg.isPresent()) &&
-            !processAsTask())
+    if (processAsTask())
     {
-      throw new ArgumentException(
-              ERR_TASK_TOOL_NO_VALID_LDAP_OPTIONS.get());
+      taskScheduleArgs.validateArgs();
     }
-
-    if (startArg.isPresent() && !NOW.equals(startArg.getValue())) {
-      try {
-        Date date = StaticUtils.parseDateTimeString(startArg.getValue());
-        // Check that the provided date is not previous to the current date.
-        Date currentDate = new Date(System.currentTimeMillis());
-        if (currentDate.after(date))
-        {
-          throw new CLIException(ERR_START_DATETIME_ALREADY_PASSED.get(
-              startArg.getValue()));
-        }
-      } catch (ParseException pe) {
-        throw new ArgumentException(ERR_START_DATETIME_FORMAT.get());
-      }
-    }
-
-    if (!processAsTask() && completionNotificationArg.isPresent()) {
-      throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
-              completionNotificationArg.getLongIdentifier()));
-    }
-
-    if (!processAsTask() && errorNotificationArg.isPresent()) {
-      throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
-              errorNotificationArg.getLongIdentifier()));
-    }
-
-    if (!processAsTask() && dependencyArg.isPresent()) {
-      throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
-              dependencyArg.getLongIdentifier()));
-    }
-
-    if (!processAsTask() && failedDependencyActionArg.isPresent()) {
-      throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
-              failedDependencyActionArg.getLongIdentifier()));
-    }
-
-    if (completionNotificationArg.isPresent()) {
-      LinkedList<String> addrs = completionNotificationArg.getValues();
-      for (String addr : addrs) {
-        if (!StaticUtils.isEmailAddress(addr)) {
-          throw new ArgumentException(ERR_TASKTOOL_INVALID_EMAIL_ADDRESS.get(
-                  addr, completionNotificationArg.getLongIdentifier()));
-        }
-      }
-    }
-
-    if (errorNotificationArg.isPresent()) {
-      LinkedList<String> addrs = errorNotificationArg.getValues();
-      for (String addr : addrs) {
-        if (!StaticUtils.isEmailAddress(addr)) {
-          throw new ArgumentException(ERR_TASKTOOL_INVALID_EMAIL_ADDRESS.get(
-                  addr, errorNotificationArg.getLongIdentifier()));
-        }
-      }
-    }
-
-    if (failedDependencyActionArg.isPresent()) {
-
-      if (!dependencyArg.isPresent()) {
-        throw new ArgumentException(ERR_TASKTOOL_FDA_WITH_NO_DEPENDENCY.get());
-      }
-
-      String fda = failedDependencyActionArg.getValue();
-      if (null == FailedDependencyAction.fromString(fda)) {
-        Set<FailedDependencyAction> fdaValSet =
-          EnumSet.allOf(FailedDependencyAction.class);
-        throw new ArgumentException(ERR_TASKTOOL_INVALID_FDA.get(fda,
-                        StaticUtils.collectionToString(fdaValSet, ",")));
-      }
+    else
+    {
+      taskScheduleArgs.validateArgsIfOffline();
     }
   }
 
@@ -320,79 +192,42 @@
    * {@inheritDoc}
    */
   public Date getStartDateTime() {
-    Date start = null;
-
-    // If the start time arg is present parse its value
-    if (startArg != null && startArg.isPresent()) {
-      if (NOW.equals(startArg.getValue())) {
-        start = new Date();
-      } else {
-        try {
-          start = StaticUtils.parseDateTimeString(startArg.getValue());
-        } catch (ParseException pe) {
-          // ignore; validated in validateTaskArgs()
-        }
-      }
-    }
-    return start;
+    return taskScheduleArgs.getStartDateTime();
   }
 
   /**
    * {@inheritDoc}
    */
   public String getRecurringDateTime() {
-    String pattern = null;
-
-    // If the recurring task arg is present parse its value
-    if (recurringArg != null && recurringArg.isPresent()) {
-      pattern = recurringArg.getValue();
-    }
-    return pattern;
+    return taskScheduleArgs.getRecurringDateTime();
   }
 
   /**
    * {@inheritDoc}
    */
   public List<String> getDependencyIds() {
-    if (dependencyArg.isPresent()) {
-      return dependencyArg.getValues();
-    } else {
-      return Collections.emptyList();
-    }
+    return taskScheduleArgs.getDependencyIds();
   }
 
   /**
    * {@inheritDoc}
    */
   public FailedDependencyAction getFailedDependencyAction() {
-    FailedDependencyAction fda = null;
-    if (failedDependencyActionArg.isPresent()) {
-      String fdaString = failedDependencyActionArg.getValue();
-      fda = FailedDependencyAction.fromString(fdaString);
-    }
-    return fda;
+    return taskScheduleArgs.getFailedDependencyAction();
   }
 
   /**
    * {@inheritDoc}
    */
   public List<String> getNotifyUponCompletionEmailAddresses() {
-    if (completionNotificationArg.isPresent()) {
-      return completionNotificationArg.getValues();
-    } else {
-      return Collections.emptyList();
-    }
+    return taskScheduleArgs.getNotifyUponCompletionEmailAddresses();
   }
 
   /**
    * {@inheritDoc}
    */
   public List<String> getNotifyUponErrorEmailAddresses() {
-    if (errorNotificationArg.isPresent()) {
-      return errorNotificationArg.getValues();
-    } else {
-      return Collections.emptyList();
-    }
+    return taskScheduleArgs.getNotifyUponErrorEmailAddresses();
   }
 
   /**
@@ -467,7 +302,7 @@
                           taskEntry.getScheduledStartTime()),
                   MAX_LINE_WIDTH));
         }
-        if (!startArg.isPresent()) {
+        if (!taskScheduleArgs.startArg.isPresent()) {
 
           // Poll the task printing log messages until finished
           String taskId = taskEntry.getId();
@@ -562,22 +397,6 @@
   }
 
   /**
-   * Indicates whether we must return if the command must be run in offline
-   * mode.
-   * @return <CODE>true</CODE> if we must return if the command must be run in
-   * offline mode and <CODE>false</CODE> otherwise.
-   */
-  private boolean testIfOffline()
-  {
-    boolean returnValue = false;
-    if (testIfOfflineArg != null)
-    {
-      returnValue = testIfOfflineArg.isPresent();
-    }
-    return returnValue;
-  }
-
-  /**
    * Returns {@code true} if the provided exception was caused by trying to
    * connect to the wrong port and {@code false} otherwise.
    * @param t the exception to be analyzed.
@@ -603,4 +422,21 @@
     }
     return isWrongPortException;
   }
+
+
+  /**
+   * Indicates whether we must return if the command must be run in off-line
+   * mode.
+   * @return <CODE>true</CODE> if we must return if the command must be run in
+   * off-line mode and <CODE>false</CODE> otherwise.
+   */
+  public boolean testIfOffline()
+  {
+    boolean returnValue = false;
+    if (testIfOfflineArg != null)
+    {
+      returnValue = testIfOfflineArg.isPresent();
+    }
+    return returnValue;
+  }
 }
diff --git a/opends/src/server/org/opends/server/util/StaticUtils.java b/opends/src/server/org/opends/server/util/StaticUtils.java
index fc1323a..f62d1f7 100644
--- a/opends/src/server/org/opends/server/util/StaticUtils.java
+++ b/opends/src/server/org/opends/server/util/StaticUtils.java
@@ -4121,11 +4121,23 @@
     {
       if (timeStr.endsWith("Z"))
       {
-        SimpleDateFormat dateFormat =
+        try
+        {
+          SimpleDateFormat dateFormat =
             new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME);
-        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-        dateFormat.setLenient(true);
-        dateTime = dateFormat.parse(timeStr);
+          dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+          dateFormat.setLenient(true);
+          dateTime = dateFormat.parse(timeStr);
+        }
+        catch (ParseException pe)
+        {
+          // Best effort: try with GMT time.
+          SimpleDateFormat dateFormat =
+            new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
+          dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+          dateFormat.setLenient(true);
+          dateTime = dateFormat.parse(timeStr);
+        }
       }
       else
       {
diff --git a/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java b/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java
index 9042e3f..e6d9cae 100644
--- a/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java
+++ b/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java
@@ -62,9 +62,6 @@
 import org.opends.admin.ads.util.ConnectionUtils;
 import org.opends.admin.ads.util.OpendsCertificateException;
 import org.opends.messages.Message;
-import org.opends.messages.MessageBuilder;
-import org.opends.quicksetup.util.PlainTextProgressMessageFormatter;
-import org.opends.quicksetup.util.ProgressMessageFormatter;
 import org.opends.quicksetup.util.Utils;
 import org.opends.server.protocols.ldap.LDAPResultCode;
 import org.opends.server.tools.ClientException;
@@ -1116,120 +1113,6 @@
     return pwd;
   }
 
-  /**
-   * The default period time used to write points in the output.
-   */
-  protected static final long DEFAULT_PERIOD_TIME = 3000;
-  /**
-   * Class used to add points periodically to the end of the output.
-   *
-   */
-  protected class PointAdder implements Runnable
-  {
-    private Thread t;
-    private boolean stopPointAdder;
-    private boolean pointAdderStopped;
-    private long periodTime = DEFAULT_PERIOD_TIME;
-    private boolean isError;
-    private ProgressMessageFormatter formatter;
-
-    /**
-     * Default constructor.
-     * Creates a PointAdder that writes to the standard output with the default
-     * period time.
-     */
-    public PointAdder()
-    {
-      this(DEFAULT_PERIOD_TIME, false, new PlainTextProgressMessageFormatter());
-    }
-
-    /**
-     * Default constructor.
-     * @param periodTime the time between printing two points.
-     * @param isError whether the points must be printed in error stream
-     * or output stream.
-     * @param formatter the text formatter.
-     */
-    public PointAdder(long periodTime, boolean isError,
-        ProgressMessageFormatter formatter)
-    {
-      this.periodTime = periodTime;
-      this.isError = isError;
-      this.formatter = formatter;
-    }
-
-    /**
-     * Starts the PointAdder: points are added at the end of the logs
-     * periodically.
-     */
-    public void start()
-    {
-      MessageBuilder mb = new MessageBuilder();
-      mb.append(formatter.getSpace());
-      for (int i=0; i< 5; i++)
-      {
-        mb.append(formatter.getFormattedPoint());
-      }
-      if (isError)
-      {
-        print(mb.toMessage());
-      }
-      else
-      {
-        printProgress(mb.toMessage());
-      }
-      t = new Thread(this);
-      t.start();
-    }
-
-    /**
-     * Stops the PointAdder: points are no longer added at the end of the logs
-     * periodically.
-     */
-    public synchronized void stop()
-    {
-      stopPointAdder = true;
-      while (!pointAdderStopped)
-      {
-        try
-        {
-          t.interrupt();
-          // To allow the thread to set the boolean.
-          Thread.sleep(100);
-        }
-        catch (Throwable t)
-        {
-        }
-      }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void run()
-    {
-      while (!stopPointAdder)
-      {
-        try
-        {
-          Thread.sleep(periodTime);
-          if (isError)
-          {
-            print(formatter.getFormattedPoint());
-          }
-          else
-          {
-            printProgress(formatter.getFormattedPoint());
-          }
-        }
-        catch (Throwable t)
-        {
-        }
-      }
-      pointAdderStopped = true;
-    }
-  }
-
   private OpendsCertificateException getCertificateRootException(Throwable t)
   {
     OpendsCertificateException oce = null;
@@ -1243,4 +1126,89 @@
     }
     return oce;
   }
+
+
+  /**
+   * Commodity method used to repeatidly ask the user to provide an
+   * integer value.
+   * @param prompt the prompt message.
+   * @param defaultValue the default value to be proposed to the user.
+   * @param logger the logger where the errors will be written.
+   * @return the value provided by the user.
+   */
+  protected int askInteger(Message prompt, int defaultValue,
+      Logger logger)
+  {
+    int newInt = -1;
+    while (newInt == -1)
+    {
+      try
+      {
+        newInt = readInteger(prompt, defaultValue);
+      }
+      catch (CLIException ce)
+      {
+        newInt = -1;
+        logger.log(Level.WARNING, "Error reading input: "+ce, ce);
+      }
+    }
+    return newInt;
+  }
+
+  /**
+   * Interactively retrieves an integer value from the console.
+   *
+   * @param prompt
+   *          The message prompt.
+   * @param defaultValue
+   *          The default value.
+   * @return Returns the value.
+   * @throws CLIException
+   *           If the value could not be retrieved for some reason.
+   */
+  public final int readInteger(Message prompt, final int defaultValue)
+  throws CLIException
+  {
+    ValidationCallback<Integer> callback = new ValidationCallback<Integer>()
+    {
+      public Integer validate(ConsoleApplication app, String input)
+          throws CLIException
+      {
+        String ninput = input.trim();
+        if (ninput.length() == 0)
+        {
+          return defaultValue;
+        }
+        else
+        {
+          try
+          {
+            int i = Integer.parseInt(ninput);
+            if (i < 1)
+            {
+              throw new NumberFormatException();
+            }
+            return i;
+          }
+          catch (NumberFormatException e)
+          {
+            // Try again...
+            app.println();
+            app.println(ERR_LDAP_CONN_BAD_INTEGER.get(ninput));
+            app.println();
+            return null;
+          }
+        }
+      }
+
+    };
+
+    if (defaultValue != -1) {
+      prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(),
+          String.valueOf(defaultValue));
+    }
+
+    return readValidatedInput(prompt, callback, CONFIRMATION_MAX_TRIES);
+  }
+
 }
diff --git a/opends/src/server/org/opends/server/util/cli/PointAdder.java b/opends/src/server/org/opends/server/util/cli/PointAdder.java
new file mode 100644
index 0000000..5d91a5e
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/PointAdder.java
@@ -0,0 +1,153 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2010 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.quicksetup.util.PlainTextProgressMessageFormatter;
+import org.opends.quicksetup.util.ProgressMessageFormatter;
+
+/**
+ * Class used to add points periodically to the end of the output.
+ *
+ */
+public class PointAdder implements Runnable
+{
+  private final ConsoleApplication app;
+  private Thread t;
+  private boolean stopPointAdder;
+  private boolean pointAdderStopped;
+  private long periodTime = DEFAULT_PERIOD_TIME;
+  private final boolean isError;
+  private final ProgressMessageFormatter formatter;
+
+
+  /**
+   * The default period time used to write points in the output.
+   */
+  public static final long DEFAULT_PERIOD_TIME = 3000;
+
+  /**
+   * Default constructor.
+   * @param app the console application to be used.
+   * Creates a PointAdder that writes to the standard output with the default
+   * period time.
+   */
+  public PointAdder(ConsoleApplication app)
+  {
+    this(app, DEFAULT_PERIOD_TIME, false,
+        new PlainTextProgressMessageFormatter());
+  }
+
+  /**
+   * Default constructor.
+   * @param app the console application to be used.
+   * @param periodTime the time between printing two points.
+   * @param isError whether the points must be printed in error stream
+   * or output stream.
+   * @param formatter the text formatter.
+   */
+  public PointAdder(ConsoleApplication app,
+      long periodTime, boolean isError,
+      ProgressMessageFormatter formatter)
+  {
+    this.app = app;
+    this.periodTime = periodTime;
+    this.isError = isError;
+    this.formatter = formatter;
+  }
+
+  /**
+   * Starts the PointAdder: points are added at the end of the logs
+   * periodically.
+   */
+  public void start()
+  {
+    MessageBuilder mb = new MessageBuilder();
+    mb.append(formatter.getSpace());
+    for (int i=0; i< 5; i++)
+    {
+      mb.append(formatter.getFormattedPoint());
+    }
+    if (isError)
+    {
+      app.print(mb.toMessage());
+    }
+    else
+    {
+      app.printProgress(mb.toMessage());
+    }
+    t = new Thread(this);
+    t.start();
+  }
+
+  /**
+   * Stops the PointAdder: points are no longer added at the end of the logs
+   * periodically.
+   */
+  public synchronized void stop()
+  {
+    stopPointAdder = true;
+    while (!pointAdderStopped)
+    {
+      try
+      {
+        t.interrupt();
+        // To allow the thread to set the boolean.
+        Thread.sleep(100);
+      }
+      catch (Throwable t)
+      {
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void run()
+  {
+    while (!stopPointAdder)
+    {
+      try
+      {
+        Thread.sleep(periodTime);
+        if (isError)
+        {
+          app.print(formatter.getFormattedPoint());
+        }
+        else
+        {
+          app.printProgress(formatter.getFormattedPoint());
+        }
+      }
+      catch (Throwable t)
+      {
+      }
+    }
+    pointAdderStopped = true;
+  }
+}

--
Gitblit v1.10.0