mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
18.06.2010 6b3ef14a652f6be0d559365d2fd2c78a61524fec
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.
7 files added
27 files modified
4272 ■■■■ changed files
opends/resource/bin/_client-script.bat 4 ●●●● patch | view | raw | blame | history
opends/resource/bin/_client-script.sh 4 ●●●● patch | view | raw | blame | history
opends/resource/bin/_mixed-script.bat 4 ●●●● patch | view | raw | blame | history
opends/resource/bin/_mixed-script.sh 6 ●●●● patch | view | raw | blame | history
opends/resource/bin/_server-script.bat 4 ●●●● patch | view | raw | blame | history
opends/resource/bin/_server-script.sh 4 ●●●● patch | view | raw | blame | history
opends/resource/bin/dsreplication 17 ●●●● patch | view | raw | blame | history
opends/resource/bin/dsreplication.bat 17 ●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromDirContext.java 25 ●●●● patch | view | raw | blame | history
opends/src/guitools/org/opends/guitools/controlpanel/util/ConfigFromFile.java 3 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/admin_tool.properties 71 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/backend.properties 14 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/tools.properties 19 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/utility.properties 2 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/UserData.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/cli/TaskScheduleArgs.java 383 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/RecurringTask.java 109 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/PurgeConflictsHistoricalTask.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/LocalPurgeHistorical.java 226 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalScheduleInformation.java 157 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalUserData.java 313 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliArgumentParser.java 231 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java 1049 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliReturnCode.java 46 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/StatusReplicationUserData.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskClient.java 121 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskScheduleInteraction.java 477 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskScheduleUserData.java 303 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskTool.java 260 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/StaticUtils.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/cli/ConsoleApplication.java 202 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/cli/PointAdder.java 153 ●●●●● patch | view | raw | blame | history
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
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}" "${@}"
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
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
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
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" "${@}"
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_DIR=`dirname "${0}"`
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" "${@}"
SCRIPT_DIR=`dirname "${0}"`
"${SCRIPT_DIR}/../lib/_client-script.sh" "${@}"
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" %*
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");
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());
                  }
                }
              }
opends/src/messages/messages/admin_tool.properties
@@ -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
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
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
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
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"
opends/src/server/org/opends/server/admin/client/cli/TaskScheduleArgs.java
New file
@@ -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();
    }
  }
}
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,63 +469,130 @@
   * @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) {
      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()) {
      String token = st.nextToken();
      switch (taskTabToken) {
        case MINUTE:
          try {
            minutesArray = parseTaskTabField(token, 0, 59);
            arrays[MINUTE_INDEX] = parseTaskTabField(token, 0, 59);
          } catch (IllegalArgumentException e) {
            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) {
            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) {
            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) {
            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) {
            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;
      }
    }
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -5680,6 +5680,8 @@
         attrs, null);
     int count = 0;
     if (task != null)
     task.setProgressStats(lastChangeNumberPurgedFromHist, count);
     LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
@@ -5731,6 +5733,7 @@
       }
       else
       {
         if (task != null)
         task.setProgressStats(lastChangeNumberPurgedFromHist, count);
       }
     }
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;
opends/src/server/org/opends/server/tools/dsreplication/LocalPurgeHistorical.java
New file
@@ -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;
  }
}
opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalScheduleInformation.java
New file
@@ -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";
  }
}
opends/src/server/org/opends/server/tools/dsreplication/PurgeHistoricalUserData.java
New file
@@ -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;
  }
}
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);
  }
}
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,10 +9873,61 @@
      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.
      updateCommandBuilderWithConsoleInteraction(commandBuilder, ci);
    }
    if (subcommandName.equals(
        ReplicationCliArgumentParser.DISABLE_REPLICATION_SUBCMD_NAME))
    {
      DisableReplicationUserData disableData =
        (DisableReplicationUserData)uData;
      if (disableData.disableAll())
      {
        commandBuilder.addArgument(new BooleanArgument(
            argParser.disableAllArg.getName(),
            argParser.disableAllArg.getShortIdentifier(),
            argParser.disableAllArg.getLongIdentifier(),
            INFO_DESCRIPTION_DISABLE_ALL.get()));
      }
      else if (disableData.disableReplicationServer())
      {
        commandBuilder.addArgument(new BooleanArgument(
            argParser.disableReplicationServerArg.getName(),
            argParser.disableReplicationServerArg.getShortIdentifier(),
            argParser.disableReplicationServerArg.getLongIdentifier(),
            INFO_DESCRIPTION_DISABLE_REPLICATION_SERVER.get()));
      }
    }
    addGlobalArguments(commandBuilder, uData);
    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();
@@ -9036,31 +9968,40 @@
      }
    }
    if (subcommandName.equals(
        ReplicationCliArgumentParser.DISABLE_REPLICATION_SUBCMD_NAME))
  private void updateCommandBuilder(CommandBuilder commandBuilder,
      PurgeHistoricalUserData uData) throws ArgumentException
    {
      DisableReplicationUserData disableData =
        (DisableReplicationUserData)uData;
      if (disableData.disableAll())
    if (uData.isOnline())
      {
        commandBuilder.addArgument(new BooleanArgument(
            argParser.disableAllArg.getName(),
            argParser.disableAllArg.getShortIdentifier(),
            argParser.disableAllArg.getLongIdentifier(),
            INFO_DESCRIPTION_DISABLE_ALL.get()));
      }
      else if (disableData.disableReplicationServer())
      updateCommandBuilderWithConsoleInteraction(commandBuilder, ci);
      if (uData.getTaskSchedule() != null)
      {
        commandBuilder.addArgument(new BooleanArgument(
            argParser.disableReplicationServerArg.getName(),
            argParser.disableReplicationServerArg.getShortIdentifier(),
            argParser.disableReplicationServerArg.getLongIdentifier(),
            INFO_DESCRIPTION_DISABLE_REPLICATION_SERVER.get()));
        updateCommandBuilderWithTaskSchedule(commandBuilder,
            uData.getTaskSchedule());
      }
    }
    addGlobalArguments(commandBuilder, uData);
    return commandBuilder;
    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,
@@ -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;
  }
}
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;
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;
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));
  }
  /**
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();
  /**
opends/src/server/org/opends/server/tools/tasks/TaskScheduleInteraction.java
New file
@@ -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();
  }
}
opends/src/server/org/opends/server/tools/tasks/TaskScheduleUserData.java
New file
@@ -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;
  }
}
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))
    else
        {
          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, ",")));
      }
      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;
  }
}
opends/src/server/org/opends/server/util/StaticUtils.java
@@ -4121,12 +4121,24 @@
    {
      if (timeStr.endsWith("Z"))
      {
        try
        {
        SimpleDateFormat dateFormat =
            new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME);
        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
      {
        SimpleDateFormat dateFormat =
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);
  }
}
opends/src/server/org/opends/server/util/cli/PointAdder.java
New file
@@ -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;
  }
}