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