Index: main/plugin-repositoryapp/i18n/messages_en.xml
===================================================================
--- main/plugin-repositoryapp/i18n/messages_en.xml	(revision 32692)
+++ main/plugin-repositoryapp/i18n/messages_en.xml	(working copy)
@@ -27,6 +27,7 @@
     <message key="RIBBON_TABS_TAB_JCR_LABEL">JCR</message>
     <message key="RIBBON_TABS_TAB_JCR_GROUP_LABEL">JCR</message>
     <message key="RIBBON_TABS_TAB_JCR_GROUP_ACTIONS_LABEL">Actions</message>
+    <message key="RIBBON_TABS_TAB_JCR_GROUP_MAINTENANCE_LABEL">Maintenance</message>
     
     <!-- JCR view tool -->
     <message key="PLUGINS_REPOSITORYAPP_JCRVIEW_TITLE">JCR view</message>
@@ -204,4 +205,32 @@
 	<message key="PLUGINS_REPOSITORYAPP_ERROR_MSG_CONNECTION_STATUS">Impossible to get the connection status.</message>
     <message key="PLUGINS_REPOSITORYAPP_SAVE_PROPERTY_ERROR_TITLE">Error</message>
     <message key="PLUGINS_REPOSITORYAPP_SAVE_PROPERTY_ERROR">An error occurred: the edition of property failed</message>
+
+    <!-- maintenance -->
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_STARTGARBAGECOLLECTOR">Garbage Collector</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_STARTGARBAGECOLLECTOR_DESC">Start the garbage collector on the repository</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_STARTGARBAGECOLLECTOR_FOOTER"></message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_REINDEXING">Reindexing</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_REINDEXING_DESC">Reindex the repository</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_REINDEXING_FOOTER"></message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_CONSISTENCYCHECK">Consistency check</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_CONSISTENCYCHECK_DESC">Check the consistency of the repository</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_BUTTON_CONSISTENCYCHECK_FOOTER"></message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_CONFIRM_STARTING_TASK_TITLE">Confirm</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_CONFIRM_STARTING_TASK">The maintenance task is not running. Do you want to start it ?</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_PROGRESS_BAR_LABEL_PRE">Progress </message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_PROGRESS_BAR_LABEL_POST"> %</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_UNEXCEPTED_ERROR_MSG">An error occurred.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_IS_TASK_RUNNING_ERROR_LOG">Unable to get response from the server. Cannot get informations if a task is running.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_BAD_RESPONSE_LOG">Unable to get response from the server. The launch of the task has probably failed.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_ERROR_LOG">Task failed to launch.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_ERROR_ADVICE_LOG">Perhaps a task is currently running. Try reloading the page.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_GET_TASK_INFO_ERROR_LOG">Unable to get response from the server. The tracking of the task progress is currently not up to date.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE">Maintenance task</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_DESC">Tool to display the progress and messages of a maintenance task</message>
+
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_DATA_STORE_GARBAGE_COLLECTOR">Maintenance: Garbage Collector</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_REINDEXING">Maintenance: Reindexing</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_CONSISTENCY_CHECK">Maintenance: Consistency check</message>
+
 </catalogue>
Index: main/plugin-repositoryapp/i18n/messages_fr.xml
===================================================================
--- main/plugin-repositoryapp/i18n/messages_fr.xml	(revision 32692)
+++ main/plugin-repositoryapp/i18n/messages_fr.xml	(working copy)
@@ -27,6 +27,7 @@
     <message key="RIBBON_TABS_TAB_JCR_LABEL">JCR</message>
     <message key="RIBBON_TABS_TAB_JCR_GROUP_LABEL">JCR</message>
     <message key="RIBBON_TABS_TAB_JCR_GROUP_ACTIONS_LABEL">Actions</message>
+    <message key="RIBBON_TABS_TAB_JCR_GROUP_MAINTENANCE_LABEL">Maintenance</message>
     
     <!-- JCR view tool -->
     <message key="PLUGINS_REPOSITORYAPP_JCRVIEW_TITLE">Vue JCR</message>
@@ -204,4 +205,32 @@
 	<message key="PLUGINS_REPOSITORYAPP_ERROR_MSG_CONNECTION_STATUS">Impossible de récupérer le statut de la connexion.</message>
     <message key="PLUGINS_REPOSITORYAPP_SAVE_PROPERTY_ERROR_TITLE">Erreur</message>
     <message key="PLUGINS_REPOSITORYAPP_SAVE_PROPERTY_ERROR">Une erreur est survenue : la propriété n'a pas été modifiée.</message>
+
+    <!-- maintenance -->
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_STARTGARBAGECOLLECTOR">Ramasse miettes</message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_STARTGARBAGECOLLECTOR_DESC">Déclenche le ramasse miettes sur le repository</message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_STARTGARBAGECOLLECTOR_FOOTER"></message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_REINDEXING">Ré-indexation</message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_REINDEXING_DESC">Ré-indexe le repository</message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_REINDEXING_FOOTER"></message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_CONSISTENCYCHECK">Vérification de la cohérence</message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_CONSISTENCYCHECK_DESC">Vérifie la cohérence des données du repository</message>
+    <message key="PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_CONSISTENCYCHECK_FOOTER"></message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_CONFIRM_STARTING_TASK_TITLE">Confirmation</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_CONFIRM_STARTING_TASK">La tache de maintenance n'est pas encore lancée. Souhaitez-vous la démarrer ?</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_PROGRESS_BAR_LABEL_PRE">Avancement </message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_PROGRESS_BAR_LABEL_POST"> %</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_UNEXCEPTED_ERROR_MSG">Une erreur s'est produite.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_IS_TASK_RUNNING_ERROR_LOG">La récupération des informations serveur a échouée. Impossible de déterminer si une tâche de maintenance est déjà en cours.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_BAD_RESPONSE_LOG">Impossible de récupérer les informations serveur. Le lancement de la tâche de maintenance a probablement échoué.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_ERROR_LOG">Le lancement de la tâche de maintenance a échoué.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_ERROR_ADVICE_LOG">Il est possible qu'une tâche soit déjà en cours d'éxecution. Veuillez essayer de recharger la page.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_GET_TASK_INFO_ERROR_LOG">Impossible de récupérer les informations serveur. Les informations relative à la progression de la tâche et le panneau de rapport ne sont plus à jour pour l'instant.</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE">Tache de maintenance</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_DESC">Outil pour afficher la progression et les messages d'une tache de maintenance</message>
+
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_DATA_STORE_GARBAGE_COLLECTOR">Maintenance: Ramasse miettes</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_REINDEXING">Maintenance: Ré-indexation</message>
+    <message key="PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_CONSISTENCY_CHECK">Maintenance: Vérification de cohérence</message>
+
 </catalogue>
Index: main/plugin-repositoryapp/plugin.xml
===================================================================
--- main/plugin-repositoryapp/plugin.xml	(revision 32692)
+++ main/plugin-repositoryapp/plugin.xml	(working copy)
@@ -626,4 +626,90 @@
         </extensions>
     </feature>
     
+    <feature name="repository.maintenance.components">
+        <components>
+            <component role="org.ametys.workspaces.repository.maintenance.MaintenanceTaskComponent"
+                       class="org.ametys.workspaces.repository.maintenance.MaintenanceTaskComponent"/>
+
+            <component role="org.ametys.workspaces.repository.maintenance.MaintenanceTaskManager"
+                       class="org.ametys.workspaces.repository.maintenance.MaintenanceTaskManager"/>
+        </components>
+
+        <extensions>
+            <extension id="org.ametys.repository.maintenance.GarbageCollector"
+                       point="org.ametys.core.ui.RibbonControlsManager"
+                       class="org.ametys.core.ui.StaticClientSideElement">
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.repository.maintenance.MaintenanceActions.startGarbageCollector</action>
+                    
+                    <label i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_STARTGARBAGECOLLECTOR</label>
+                    <description i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_STARTGARBAGECOLLECTOR_DESC</description>
+                    <description-footer i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_STARTGARBAGECOLLECTOR_FOOTER</description-footer>
+                    <icon-small file="true">img/console/console-result-16.png</icon-small>
+                    <icon-medium file="true">img/console/console-result-32.png</icon-medium>
+                    <icon-large file="true">img/console/console-result-48.png</icon-large>
+                </class>
+                <scripts>
+                    <file>js/Ametys/repository/maintenance/MaintenanceActions.js</file>
+                </scripts>
+            </extension>
+
+            <extension id="org.ametys.repository.maintenance.Reindexing"
+                       point="org.ametys.core.ui.RibbonControlsManager"
+                       class="org.ametys.core.ui.StaticClientSideElement">
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.repository.maintenance.MaintenanceActions.reindexing</action>
+                    
+                    <label i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_REINDEXING</label>
+                    <description i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_REINDEXING_DESC</description>
+                    <description-footer i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_REINDEXING_FOOTER</description-footer>
+                    <icon-small file="true">img/console/console-result-16.png</icon-small>
+                    <icon-medium file="true">img/console/console-result-32.png</icon-medium>
+                    <icon-large file="true">img/console/console-result-48.png</icon-large>
+                </class>
+                <scripts>
+                    <file>js/Ametys/repository/maintenance/MaintenanceActions.js</file>
+                </scripts>
+            </extension>
+
+            <extension id="org.ametys.repository.maintenance.ConsistencyCheck"
+                       point="org.ametys.core.ui.RibbonControlsManager"
+                       class="org.ametys.core.ui.StaticClientSideElement">
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.repository.maintenance.MaintenanceActions.consistencyCheck</action>
+                    
+                    <label i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_CONSISTENCYCHECK</label>
+                    <description i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_CONSISTENCYCHECK_DESC</description>
+                    <description-footer i18n="true">PLUGINS_REPOSITORYAPP_BUTTON_MAINTENANCE_CONSISTENCYCHECK_FOOTER</description-footer>
+                    <icon-small file="true">img/console/console-result-16.png</icon-small>
+                    <icon-medium file="true">img/console/console-result-32.png</icon-medium>
+                    <icon-large file="true">img/console/console-result-48.png</icon-large>
+                </class>
+                <scripts>
+                    <file>js/Ametys/repository/maintenance/MaintenanceActions.js</file>
+                </scripts>
+            </extension>
+
+            <extension id="org.ametys.repository.maintenance.MaintenanceTask"
+                       point="org.ametys.core.ui.UIToolsFactoriesManager"
+                       class="org.ametys.core.ui.StaticClientSideElement">
+                <class name="Ametys.tool.factory.BasicToolFactory">
+                    <role>uitool-repository-maintenancetask</role>
+                    <toolClass>Ametys.repository.maintenance.MaintenanceTaskTool</toolClass>
+                    
+                    <title i18n="true">PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE</title>
+                    <description i18n="true">PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_DESC</description>
+                    
+                    <icon-small file="true">img/console/console-result-16.png</icon-small>
+                    <icon-medium file="true">img/console/console-result-32.png</icon-medium>
+                    <icon-large file="true">img/console/console-result-48.png</icon-large>
+                    
+                    <default-location></default-location>
+                </class>
+                <scripts>
+                    <file>js/Ametys/repository/maintenance/MaintenanceTaskTool.js</file>
+                </scripts>
+            </extension>
+        </extensions>
+    </feature>
 </plugin>
Index: main/plugin-repositoryapp/resources/js/Ametys/repository/maintenance/MaintenanceActions.js
===================================================================
--- main/plugin-repositoryapp/resources/js/Ametys/repository/maintenance/MaintenanceActions.js	(revision 0)
+++ main/plugin-repositoryapp/resources/js/Ametys/repository/maintenance/MaintenanceActions.js	(revision 0)
@@ -0,0 +1,152 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+ /**
+ * Class managing the "add node" action.
+ * @private
+ */
+Ext.define('Ametys.repository.maintenance.MaintenanceActions',
+{
+	singleton: true,
+
+	/**
+	 * Action to start the Garbage Collector task
+	 */
+	startGarbageCollector: function(controller)
+	{
+		var taskName = "DATA_STORE_GARBAGE_COLLECTOR";
+		this.act(taskName);
+	},
+
+	/**
+	 * Action to start the Reindexing task
+	 */
+	reindexing: function(controller)
+	{
+		var taskName = "REINDEXING";
+		this.act(taskName);
+	},
+
+	/**
+	 * Action to start the Consistency Check task
+	 */
+	consistencyCheck: function(controller)
+	{
+		var taskName = "CONSISTENCY_CHECK";
+		this.act(taskName);
+	},
+
+	/**
+	 * @private
+	 * Start a maintenance task after asking confirmation
+	 * @param {String} task The task name
+	 */
+	act: function(taskName)
+	{
+		// Check if the task is already running
+		Ametys.data.ServerComm.callMethod({
+			role: "org.ametys.workspaces.repository.maintenance.MaintenanceTaskComponent",
+			methodName: "isRunning",
+			parameters: [taskName],
+			callback: {
+				scope: this,
+				handler: this._isRunningCb,
+				arguments: {taskName: taskName}
+			},
+			errorMessage: {
+				msg: "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_IS_TASK_RUNNING_ERROR_LOG'/>",
+				category: 'Ametys.repository.maintenance.MaintenanceActions'
+			},
+			waitMessage: true
+		});
+	},
+
+	/**
+	 * Callback after starting a task
+	 * @param {Object} response The response from the server
+	 * @param {Object} args The arguments of the callMethod function
+	 */
+	_isRunningCb: function(response, args)
+	{
+		var taskName = args.taskName;
+		if (response.running == "true")
+		{
+			this._openTool(taskName);
+			return;
+		}
+
+		var me = this;
+
+		Ametys.Msg.confirm ("<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_CONFIRM_STARTING_TASK_TITLE'/>", 
+		    "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_CONFIRM_STARTING_TASK'/>",
+		    function (answer)
+		    {
+				if (answer == 'yes')
+				{
+					// Start the tasks
+					Ametys.data.ServerComm.callMethod({
+						role: "org.ametys.workspaces.repository.maintenance.MaintenanceTaskComponent",
+						methodName: "startTask",
+						parameters: [taskName],
+						callback: {
+							scope: me,
+							handler: me._startTaskCb,
+							arguments: {taskName: taskName}
+						},
+						errorMessage: {
+							msg: "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_BAD_RESPONSE_LOG'/>",
+							category: 'Ametys.repository.maintenance.MaintenanceActions'
+						},
+						waitMessage: true
+					});
+				}
+		    }
+	    );
+	},
+
+	/**
+	 * Callback after starting a task
+	 * @param {Object} response The response from the server
+	 * @param {Boolean} response.launched Is the task launched
+	 * @param {Boolean} response.running Is the task running
+	 * @param {Object} args The arguments of the callMethod function
+	 */
+	_startTaskCb: function(response, args)
+	{
+		if (!response.launched || !response.running)
+		{
+            Ext.Msg.show({
+                msg: "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_LAUNCH_TASK_BAD_RESPONSE_LOG'/>",
+                buttons: Ext.Msg.OK,
+                icon: Ext.MessageBox.ERROR
+            });
+
+			return;
+		}
+
+		this._openTool(args.taskName);
+	},
+
+	/**
+	 * @private
+	 * Open the tool corresponding to the task
+	 * @param {String} taskName the name of the task
+	 */
+	_openTool: function(taskName)
+	{
+		Ametys.tool.ToolsManager.openTool('uitool-repository-maintenancetask', {'taskName': taskName, 'id': taskName});
+	}
+});
\ No newline at end of file
Index: main/plugin-repositoryapp/resources/js/Ametys/repository/maintenance/MaintenanceTaskTool.js
===================================================================
--- main/plugin-repositoryapp/resources/js/Ametys/repository/maintenance/MaintenanceTaskTool.js	(revision 0)
+++ main/plugin-repositoryapp/resources/js/Ametys/repository/maintenance/MaintenanceTaskTool.js	(revision 0)
@@ -0,0 +1,162 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+ 
+Ext.define('Ametys.repository.maintenance.MaintenanceTaskTool', 
+    {
+        extend: "Ametys.tool.Tool",
+        
+        constructor: function(config)
+        {
+            this.callParent(arguments);
+
+            var me = this;
+            // The task that will be handled by the Ext.TaskManager
+            this._taskInfoTask = {
+                run: me._getTaskInfo,
+                scope: me,
+                interval: 1000
+            };
+
+            this._currentReportPart = 0;
+            this._isTaskRunning = false;
+        },
+
+        setParams: function(params)
+        {
+            this._taskName = params.taskName;
+            this._setToolTitle(this._taskName);
+        },
+
+        createPanel: function()
+        {
+            this._panel = Ext.create('Ext.panel.Panel', {
+                cls: 'ametys-repository-maintenancetask-tool',
+                split: true,
+                scrollable: true,
+
+                items: [
+                    {
+                        xtype: "progressbar",
+                        itemId: "maintenance-task-progressbar"
+                    },
+                    {
+                        xtype: "panel",
+                        itemId: "maintenance-task-log"
+                    }
+                ],
+
+                listeners: {
+                    "afterrender": Ext.bind(this._afterRender, this)
+                }
+            });
+            
+            return this._panel;
+        },
+        
+        getMBSelectionInteraction: function() 
+        {
+            return Ametys.tool.Tool.MB_TYPE_NOSELECTION;
+        },
+
+        _setToolTitle: function(taskName)
+        {
+            if (taskName == "DATA_STORE_GARBAGE_COLLECTOR")
+            {
+                this.setTitle("<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_DATA_STORE_GARBAGE_COLLECTOR'/>");
+            }
+            if (taskName == "REINDEXING")
+            {
+                this.setTitle("<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_REINDEXING'/>");
+            }
+            if (taskName == "CONSISTENCY_CHECK")
+            {
+                this.setTitle("<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_TOOL_TITLE_CONSISTENCY_CHECK'/>");
+            }            
+        },
+
+        /**
+         * @private
+         */
+        _afterRender: function()
+        {
+            this._isTaskRunning = true;
+            Ext.TaskManager.start(this._taskInfoTask);
+        },
+
+        _getTaskInfo: function()
+        {
+            if (!this._isTaskRunning)
+            {
+                return;
+            }
+
+            Ametys.data.ServerComm.callMethod({
+                role: "org.ametys.workspaces.repository.maintenance.MaintenanceTaskComponent",
+                methodName: "getInformations",
+                parameters: [this._taskName, this._currentReportPart],
+                callback: {
+                    scope: this,
+                    handler: this._getTaskInfoCb
+                },
+                errorMessage: {
+                    msg: "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_GET_TASK_INFO_ERROR_LOG'/>",
+                    category: 'Ametys.repository.maintenance.MaintenanceActions'
+                },
+                waitMessage: false
+            });
+        },
+
+        /**
+         * Callback for the get informations on the current task
+         * @param {Object} response the response from the server
+         * @param {Boolean} response.isRunning Is the task still running
+         * @param {Boolean} response.repositoryState Is the repository running
+         * @param {Object} response.progress the current progress of the task
+         * @param {Object[]} response.report the logs of the tasks
+         * @param {Integer} response.lastPart the last part index of the logs
+         */
+        _getTaskInfoCb: function(response)
+        {
+            if (!response.isRunning)
+            {
+                this._isTaskRunning = false;
+                Ext.TaskManager.stop(this._taskInfoTask);
+            }
+            
+            // Update progress bar
+            var progress = response.progress;
+            if (progress != null)
+            {
+                var progressBar = this._panel.down("#maintenance-task-progressbar");
+                var progressLabel = "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_PROGRESS_BAR_LABEL_PRE' />" + Math.round(100 * progress.percentage) + "<i18n:text i18n:key='PLUGINS_REPOSITORYAPP_MAINTENANCE_PROGRESS_BAR_LABEL_POST' />";
+                progressBar.updateProgress(progress.percentage, progressLabel, true);
+            }
+
+            // Update report panel
+            var report = response.report;
+            var logPanel = this._panel.down("#maintenance-task-log");
+            for (var entry in report)
+            {
+                logPanel.add({
+                    border: false,
+                    html: report[entry].message,
+                    cls: 'report-' + (report[entry].type || "info")
+                });
+            }
+            this._currentReportPart = response.lastPart;
+        }
+    }
+);
Index: main/workspace-repository/sitemap.xmap
===================================================================
--- main/workspace-repository/sitemap.xmap	(revision 32692)
+++ main/workspace-repository/sitemap.xmap	(working copy)
@@ -85,6 +85,15 @@
                 <map:act type="authenticate"/>
             </map:match>
             
+            <!-- FIXME the connection test to the repository should be executed before this match, but is bypassed until the connect action let Maintenance Tasks go through -->
+            <!-- Direct plugins url -->
+            <map:match pattern="plugins/*/**">
+                <map:act type="setter">
+                    <map:parameter name="pluginName" value="{1}"/>
+                </map:act>
+                <map:mount check-reload="yes" src="plugin:{1}://" uri-prefix="plugins/{1}"/>
+            </map:match>
+            
             <!-- All pipelines require a connection to the repository: ensure the connection exists. -->
             <map:match pattern="**">
                 <map:act type="connect">
@@ -149,14 +158,6 @@
                 <map:serialize type="xml"/>
             </map:match>
             
-            <!-- Direct plugins url -->
-            <map:match pattern="plugins/*/**">
-                <map:act type="setter">
-                    <map:parameter name="pluginName" value="{1}"/>
-                </map:act>
-                <map:mount check-reload="yes" src="plugin:{1}://" uri-prefix="plugins/{1}"/>
-            </map:match>
-            
             <!-- ERRORS -->
             <map:handle-errors>
                 <map:select type="exception">
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/AbstractMaintenanceTask.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/AbstractMaintenanceTask.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/AbstractMaintenanceTask.java	(revision 0)
@@ -0,0 +1,190 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.joda.time.Duration;
+import org.joda.time.format.PeriodFormatter;
+import org.joda.time.format.PeriodFormatterBuilder;
+
+import org.ametys.plugins.repositoryapp.RepositoryProvider;
+import org.ametys.workspaces.repository.maintenance.TaskReport.TaskReportMessage;
+
+/**
+ * Jackrabbit maintenance tasks implementations should extends this class.
+ */
+public abstract class AbstractMaintenanceTask
+{
+    /** TaskProgress */
+    protected TaskProgress _progress;
+
+    /** task report */
+    protected TaskReport _report;
+
+    /** The repository config */
+    protected RepositoryConfig _repositoryConfig;
+
+    /** The repository provider. */
+    protected RepositoryProvider _repositoryProvider;
+
+    private boolean _isFinished;
+
+    /**
+     * Execute the task
+     * @param repositoryConfig The RepositoryConfig object, used to create new Repository instance.
+     * @throws RepositoryException
+     */
+    public void execute(RepositoryConfig repositoryConfig) throws RepositoryException 
+    {
+        _report = new TaskReport();
+        _repositoryConfig = repositoryConfig;
+
+        long startTime = System.currentTimeMillis();
+
+        _report.add(DateFormat.getTimeInstance().format(new Date()) + " : Executing task");
+
+        try
+        {
+            // task specific initialization.
+            initialize();
+
+            // Mark the task as running.
+            setRunning();
+
+            // Do the task.
+            apply();
+
+            // Mark the task as finished.
+            setFinished();
+        }
+        catch (RepositoryException e)
+        {
+            setInErrorState(e);
+            throw e;
+        }
+        finally
+        {
+            close();
+
+            _report.add(DateFormat.getTimeInstance().format(new Date()) + " : End of the task");
+
+            long elapsedTime = System.currentTimeMillis() - startTime;
+            _report.add("Done in " + _getFormattedDuration(elapsedTime));
+        }
+    }
+
+    /**
+     * Initialize the tasks.
+     * This method can also create the {@link TaskProgress} object bounded to the task.
+     * @throws RepositoryException 
+     */
+    protected void initialize() throws RepositoryException
+    {
+        return;
+    }
+
+    /**
+     * Apply the tasks (within the execute method()).
+     * @throws RepositoryException 
+     */
+    protected abstract void apply() throws RepositoryException;
+
+    /**
+     * Close the tasks
+     * @throws RepositoryException 
+     */
+    protected void close() throws RepositoryException
+    {
+        return;
+    }
+
+    /**
+     * Get progress information.
+     * @return a map containing informations on the progress status.
+     */
+    public Map<String, String> getProgressInfo() 
+    {
+        if (_progress != null)
+        {
+            return _progress.getProgressInfo();
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Get the {@link TaskReportMessage} objects starting from a given part.
+     * @param list the list of {@link TaskReportMessage} to be populated
+     * @param fromPart 
+     * @return the last report part number
+     */
+    public int getReportMessage(List<TaskReportMessage> list, int fromPart)
+    {
+        return _report.getMessages(list, fromPart);
+    }
+
+    /**
+     * Is the execution of the task finished?
+     * @return true if finished
+     */
+    public boolean isFinished()
+    {
+        return _isFinished;
+    }
+
+    private synchronized void setRunning()
+    {
+        if (_progress != null)
+        {
+            _progress.setRunning();
+        }
+    }
+
+    private synchronized void setFinished()
+    {
+        if (_progress != null)
+        {
+            _progress.setFinished();
+        }
+
+        _isFinished = true;
+    }
+
+    private synchronized void setInErrorState(Exception e)
+    {
+        _report.addException(e);
+
+        if (_progress != null)
+        {
+            _progress.setInErrorState(e);
+        }
+
+        _isFinished = true;
+    }
+
+    /**
+     * Transforms millisecond to a formatted string representing a duration.
+     * @param elapsedTime milleseconds corresponding to the duration.
+     * @return Pretty formatted string.
+     */
+    protected String _getFormattedDuration(long elapsedTime)
+    {
+        Duration duration = new Duration(elapsedTime);
+        PeriodFormatter formatter = new PeriodFormatterBuilder()
+                .appendHours()
+                .appendSuffix("h")
+                .appendMinutes()
+                .appendSuffix("m")
+                .appendSeconds()
+                .appendSuffix("s")
+                .toFormatter();
+        return formatter.print(duration.toPeriod());
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/ConsistencyCheckTask.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/ConsistencyCheckTask.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/ConsistencyCheckTask.java	(revision 0)
@@ -0,0 +1,191 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
+import org.apache.jackrabbit.core.persistence.PersistenceManager;
+import org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyCheckListener;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.core.persistence.check.ReportItem;
+import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
+
+import org.ametys.workspaces.repository.maintenance.TaskReport.TaskReportMessageType;
+
+/**
+ * DataStoreGarbageCollectorTask
+ */
+public class ConsistencyCheckTask extends AbstractMaintenanceTask implements ConsistencyCheckListener
+{
+    /** The JackRabbit RepositoryImpl Context */
+    protected RepositoryContext _repositoryContext;
+
+    /** The JCR Session bound to this task. */
+    protected Session _session;
+
+    private IterablePersistenceManager[] _pmList;
+
+    @Override
+    protected void initialize() throws RepositoryException
+    {
+        // Create the repository and log in the session.
+        _repositoryContext = RepositoryContext.create(_repositoryConfig);
+        _session = _repositoryContext.getRepository().login(new SimpleCredentials("__MAINTENANCE_TASK__", "".toCharArray()));
+
+        // Workaround to get the list of the PersistenceManager
+        ArrayList<PersistenceManager> pmList = new ArrayList<PersistenceManager>();
+
+        // PM of version manager
+        InternalVersionManagerImpl vm = _repositoryContext.getInternalVersionManager();
+        pmList.add(vm.getPersistenceManager());
+
+        // PMs of workspaces.
+        String[] wspNames = _repositoryContext.getWorkspaceManager().getWorkspaceNames();
+        for (int i = 0; i < wspNames.length; i++)
+        {
+            pmList.add(getPM(wspNames[i]));
+        }
+
+        // Filtering on IterablePersistenceManager
+        _pmList = new IterablePersistenceManager[pmList.size()];
+        for (int i = 0; i < pmList.size(); i++)
+        {
+            PersistenceManager pm = pmList.get(i);
+            if (!(pm instanceof IterablePersistenceManager))
+            {
+                _pmList = null;
+                break;
+            }
+            _pmList[i] = (IterablePersistenceManager) pm;
+        }
+
+        // Initialize the task progress object.
+        int count = 0; // number of item that will be scanned.
+
+        try
+        {
+            for (IterablePersistenceManager pm : _pmList)
+            {
+                count += pm.getAllNodeIds(null, 0).size();
+            }
+            _progress = new TaskProgress(count);
+        }
+        catch (Exception e)
+        {
+            _progress = new TaskProgress(0);
+            _progress.setInErrorState(e);
+            _report.addException(e);
+        }
+    }
+
+    @Override
+    protected void apply() throws RepositoryException
+    {
+        for (PersistenceManager pm : _pmList)
+        {
+            // Do PM consistency check
+            if (pm instanceof AbstractBundlePersistenceManager)
+            {
+                // Perform the check
+                // null -> all uuids, true -> recursive, false -> nofix, null, 
+                // lost+found -> null, this -> listener 
+                ConsistencyReport report = ((AbstractBundlePersistenceManager) pm).check(null, true, false, null, this);
+
+                _report.add("Consistency check done for persistence manager : '" + pm.toString() + "' in " + (report.getElapsedTimeMs() / 1000f) + " s.");
+                _report.add(report.getNodeCount() + " nodes were checked.");
+                _report.add(report.getItems().isEmpty() ? "No consistency problems where reported." : report.getItems().size() + " consistency problems where reported."); 
+            }
+        }
+    }
+
+    @Override
+    protected void close()
+    {
+        if (_session != null)
+        {
+            _session.logout();
+        }
+
+        if (_repositoryContext != null && _repositoryContext.getRepository() != null)
+        {
+            _repositoryContext.getRepository().shutdown();
+        }
+        
+        if (_progress != null)
+        {
+            _progress.progressRelativePercentage(100);
+        }
+    }
+
+    /**
+     * Retrieves JackRabbit Persistence Manager for currently opened repository. This method uses
+     * Privileged access and will fail with security exception if used in environment with enabled security manager.
+     * 
+     * @return Persistence manager used by repository.
+     */
+    private PersistenceManager getPM(String workspaceName)
+    {
+        try
+        {
+            Object workspaceInfo = findAndInvokeMethod(_repositoryContext.getRepository(), "getWorkspaceInfo", new Object[] {workspaceName});
+            return (PersistenceManager) (findAndInvokeMethod(workspaceInfo, "getPersistenceManager", null));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Object findAndInvokeMethod(Object obj, String name, Object[] parameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
+    {
+        Method m = null;
+        Method[] ms = obj.getClass().getDeclaredMethods();
+        for (int i = 0; i < ms.length; i++)
+        {
+            final Method x = ms[i];
+            if (x.getName().equals(name))
+            {
+                m = x;
+                break;
+            }
+        }
+        m.setAccessible(true);
+        return m.invoke(obj, parameters);
+    }
+
+    // Listener methods 
+
+    @Override
+    public void startCheck(String id)
+    {
+        if (_progress != null)
+        {
+            _progress.progress();
+        }
+    }
+
+    @Override
+    public void report(ReportItem item)
+    {
+        _report.add(TaskReportMessageType.WARN, item.toString());
+    }
+
+    @Override
+    public void error(String id, String message)
+    {
+        _report.add(TaskReportMessageType.ERROR, "error during the consistency check -> id : [ " + id + "]\n" + message);
+    }
+
+    @Override
+    public void info(String id, String message)
+    {
+        _report.add("error during the consistency check -> id : [ " + id + "]\n" + message);
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/DataStoreGarbageCollectorTask.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/DataStoreGarbageCollectorTask.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/DataStoreGarbageCollectorTask.java	(revision 0)
@@ -0,0 +1,327 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.sql.DataSource;
+
+import org.apache.jackrabbit.api.management.MarkEventListener;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.FileDataStore;
+import org.apache.jackrabbit.core.data.db.DbDataStore;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
+import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
+import org.apache.jackrabbit.core.persistence.PersistenceManager;
+import org.apache.jackrabbit.core.util.db.ConnectionFactory;
+import org.apache.jackrabbit.core.util.db.ConnectionHelper;
+import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
+
+import org.ametys.workspaces.repository.maintenance.TaskReport.TaskReportMessageType;
+
+/**
+ * DataStoreGarbageCollectorTask
+ */
+public class DataStoreGarbageCollectorTask extends AbstractMaintenanceTask implements MarkEventListener
+{
+    private static final int SYSTEM_GC_CALLS = 3;
+
+    /** The JackRabbit RepositoryImpl Context */
+    protected RepositoryContext _repositoryContext;
+
+    /** The JCR Session bound to this task. */
+    protected Session _session;
+
+    private GarbageCollector _garbageCollector;
+    private IterablePersistenceManager[] _pmList;
+
+    /** Internal counter for scanned node by the GC */
+    private int _scannedNodesCount;
+
+    @Override
+    protected void initialize() throws RepositoryException
+    {
+        // Create the repository and log in the session.
+        _repositoryContext = RepositoryContext.create(_repositoryConfig);
+        _session = _repositoryContext.getRepository().login(new SimpleCredentials("__MAINTENANCE_TASK__", "".toCharArray()));
+
+        // Create the GC Object
+        this._garbageCollector = _repositoryContext.getRepository().createDataStoreGarbageCollector();
+
+        // Workaround to get the list of the PersistenceManager
+        ArrayList<PersistenceManager> pmList = new ArrayList<PersistenceManager>();
+        InternalVersionManagerImpl vm = _repositoryContext.getInternalVersionManager();
+        pmList.add(vm.getPersistenceManager());
+
+        String[] wspNames = _repositoryContext.getWorkspaceManager().getWorkspaceNames();
+        for (int i = 0; i < wspNames.length; i++)
+        {
+            pmList.add(getPM(wspNames[i]));
+        }
+
+        _pmList = new IterablePersistenceManager[pmList.size()];
+        for (int i = 0; i < pmList.size(); i++)
+        {
+            PersistenceManager pm = pmList.get(i);
+            if (!(pm instanceof IterablePersistenceManager))
+            {
+                _pmList = null;
+                break;
+            }
+            _pmList[i] = (IterablePersistenceManager) pm;
+        }
+
+        // Initialize the task progress object.
+        int count = 0; // number of item that will be scanned.
+
+        try
+        {
+            for (IterablePersistenceManager pm : _pmList)
+            {
+                count += pm.getAllNodeIds(null, 0).size();
+            }
+
+            // When scan is finished, progress is set at 70%.
+            float total = (10f / 7) * count;
+
+            _progress = new TaskProgress(Math.max(total, 1));
+        }
+        catch (Exception e)
+        {
+            _progress = new TaskProgress(0);
+            _progress.setInErrorState(e);
+            _report.addException(e);
+        }
+    }
+
+    @Override
+    protected void apply() throws RepositoryException
+    {
+        // call System.gc() a few times before running the data store garbage collection. 
+        // Please note System.gc() does not guarantee all objects are garbage collected.
+        for (int i = 0; i < SYSTEM_GC_CALLS; i++)
+        {
+            System.gc();
+        }
+
+        // Log some informations about the ds.
+        int startSize = reportDataStoreInfo(_garbageCollector.getDataStore());
+
+        // Sleep
+        if (_garbageCollector.getDataStore() instanceof FileDataStore)
+        {
+            // make sure the file is old (access time resolution is 2 seconds)
+            try
+            {
+                Thread.sleep(2000);
+            }
+            catch (InterruptedException e)
+            {
+                _report.addException(e);
+                throw new RuntimeException(e); 
+            }
+        }
+
+        // GC Management
+        _garbageCollector.setMarkEventListener(this);
+        _garbageCollector.setPersistenceManagerScan(true);
+        // gc.setSleepBetweenNodes(0);
+
+        // Run GC
+        try
+        {
+            _scannedNodesCount = 0;
+            _report.add("Scanning the repository nodes...");
+            _garbageCollector.mark();
+            _report.add(_scannedNodesCount + " nodes scanned.");
+
+            // Only for tests purpose (decomment)
+            // _garbageCollector.getDataStore().clearInUse();
+
+            _report.add("Deleting unused items... Please be patient.");
+            int deleted = _garbageCollector.sweep();
+            _report.add(deleted + " unused items deleted.");
+            if (_progress != null)
+            {
+                _progress.progressRelativePercentage(50);
+            }
+
+            _report.add("Finalizing the process...");
+
+            // Compressing derby DATASTORE table.
+            // A Workaround is used to detect that we are using derby for the datastore
+            // because we are in fact using the DbDataStore. In reality, we should simply use _garbageCollector.getDataStore() instanceof DerbyDataStore
+            // See REPOSITORY-209
+            if (_garbageCollector.getDataStore() instanceof DbDataStore && "derby".equals(((DbDataStore) _garbageCollector.getDataStore()).getDatabaseType()))
+            {
+                _report.add("Reclaiming unused space, this may take several minutes depending on the size of the data store.");
+                _report.add("Please be patient.");
+
+                DbDataStore ds = (DbDataStore) _garbageCollector.getDataStore();
+                derbyCompressTable(ds);
+                // TODO try SYSCS_DIAG.SPACE_TABLE to estimate the amount of unused space in a table or index
+                // derby 10.6+ only ?
+            }
+        }
+        finally
+        {
+            _garbageCollector.close();
+        }
+
+        // Log some informations about the ds.
+        int finalSize = reportDataStoreInfo(_garbageCollector.getDataStore());
+        int freedSize = startSize - finalSize;
+        _report.add("Size of cleared data : " + Math.round(freedSize / 1024f) + "Ko");
+        _report.add("The total released space on your disk can be different depending on the type of the data store used by your repository.");
+    }
+
+    @Override
+    protected void close()
+    {
+        if (_session != null)
+        {
+            _session.logout();
+        }
+
+        if (_repositoryContext != null && _repositoryContext.getRepository() != null)
+        {
+            _repositoryContext.getRepository().shutdown();
+        }
+
+        if (_progress != null)
+        {
+            _progress.progressRelativePercentage(100);
+        }
+    }
+
+    private int reportDataStoreInfo(DataStore ds) throws DataStoreException
+    {
+        int count = 0;
+        int total = 0;
+        Iterator<DataIdentifier> it = ds.getAllIdentifiers();
+        while (it.hasNext())
+        {
+            count++;
+            DataIdentifier id = it.next();
+            total += ds.getRecord(id).getLength();
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("Datastore item count : ").append(count).append(" ");
+        sb.append("[total size : ").append(Math.round(total / 1024f)).append("Ko]");
+        _report.add(sb.toString());
+
+        return total;
+    }
+
+    /**
+     * Reclaiming unused space. This is derby specific.
+     * By default, Derby does not return unused space to the operating system
+     * when updating or deleting data.
+     * @param ds
+     * @throws RepositoryException 
+     */
+    private void derbyCompressTable(DbDataStore ds) throws RepositoryException
+    {
+        DataSource dataSource = null;
+
+        try
+        {
+            ConnectionFactory cf = _repositoryConfig.getConnectionFactory();
+            if (ds.getDataSourceName() == null || "".equals(ds.getDataSourceName()))
+            {
+                dataSource = cf.getDataSource(ds.getDriver(), ds.getUrl(), ds.getUser(), ds.getPassword());
+            }
+            else
+            {
+                dataSource = cf.getDataSource(ds.getDataSourceName());
+            }
+        }
+        catch (SQLException e)
+        {
+            _report.addException(e);
+            throw new RuntimeException(e);
+        }
+
+        if (dataSource != null)
+        {
+            ConnectionHelper conHelper = new ConnectionHelper(dataSource, false);
+            //            String sql = "CALL SYSCS_UTIL.SYSCS_COMPRESS_TABLE(CURRENT SCHEMA, ?, 1)";
+            String sql = "CALL SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(CURRENT SCHEMA, ?, 1, 1, 1)";
+            String prefix = ds.getTablePrefix();
+            String schemaObjPrefix = ds.getSchemaObjectPrefix();
+            String table = prefix + schemaObjPrefix + "DATASTORE";
+
+            try
+            {
+                conHelper.query(sql, table);
+            }
+            catch (SQLException e)
+            {
+                _report.addException(e);
+                throw new RuntimeException(e);
+            }
+        }
+        else
+        {
+            _report.add(TaskReportMessageType.ERROR, "Unable to compress the Derby datastore, unused space has not been freed up.");
+        }
+
+    }
+
+    @Override
+    public void beforeScanning(Node n) throws RepositoryException
+    {
+        _scannedNodesCount++;
+        if (_progress != null)
+        {
+            _progress.progress();
+        }
+    }
+
+    /**
+     * Retrieves JackRabbit Persistence Manager for currently opened repository. This method uses
+     * Privileged access and will fail with security exception if used in environment with enabled security manager.
+     * 
+     * @return Persistence manager used by repository.
+     * @throws RepositoryException
+     */
+    private PersistenceManager getPM(String workspaceName) throws RepositoryException
+    {
+        try
+        {
+            Object workspaceInfo = findAndInvokeMethod(_repositoryContext.getRepository(), "getWorkspaceInfo", new Object[] {workspaceName});
+            return (PersistenceManager) (findAndInvokeMethod(workspaceInfo, "getPersistenceManager", null));
+        }
+        catch (Exception e)
+        {
+            throw new RepositoryException(e);
+        }
+    }
+
+    private static Object findAndInvokeMethod(Object obj, String name, Object[] parameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
+    {
+        Method m = null;
+        Method[] ms = obj.getClass().getDeclaredMethods();
+        for (int i = 0; i < ms.length; i++)
+        {
+            final Method x = ms[i];
+            if (x.getName().equals(name))
+            {
+                m = x;
+                break;
+            }
+        }
+        m.setAccessible(true);
+        return m.invoke(obj, parameters);
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/MaintenanceTaskComponent.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/MaintenanceTaskComponent.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/MaintenanceTaskComponent.java	(revision 0)
@@ -0,0 +1,133 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.workspaces.repository.maintenance;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+
+import org.ametys.core.ui.Callable;
+import org.ametys.workspaces.repository.maintenance.MaintenanceTaskManager.MaintenanceTaskType;
+import org.ametys.workspaces.repository.maintenance.TaskReport.TaskReportMessage;
+
+/**
+ * Component to launch and follow the maintenance tasks on the repository
+ */
+public class MaintenanceTaskComponent extends AbstractLogEnabled implements Component, Serviceable
+{
+    /** The maintenance task manager */
+    private MaintenanceTaskManager _taskManager;
+
+    @Override
+    public void service(ServiceManager smanager) throws ServiceException
+    {
+        _taskManager = (MaintenanceTaskManager) smanager.lookup(MaintenanceTaskManager.ROLE);
+    }
+
+    /**
+     * Start a Maintenance Task
+     * @param taskName the name of the task
+     * @return a JSon object
+     */
+    @Callable
+    public Map<String, Object> startTask(String taskName)
+    {
+        Map<String, Object> result = new HashMap<>();
+
+        try
+        {
+            boolean launched = _taskManager.launch(MaintenanceTaskType.valueOf(taskName));
+            result.put("launched", launched);
+        }
+        catch (Exception e)
+        {
+            getLogger().error(e.getMessage(), e);
+            result.put("launched", false);
+        }
+
+        result.put("running", _taskManager.isTaskRunning());
+        result.put("repository-state", _taskManager.isRepositoryStarted());
+
+        return result;
+    }
+
+    /**
+     * Check if a task is running or not
+     * @param taskName The task name
+     * @return true if the task is running
+     */
+    @Callable
+    public Map<String, Object> isRunning(String taskName)
+    {
+        Map<String, Object> result = new HashMap<>();
+
+        String isRunning = Boolean.toString(_taskManager.isTaskRunning());
+
+        result.put("running", isRunning);
+        result.put("repository-state", Boolean.toString(_taskManager.isRepositoryStarted()));
+
+        return result;
+    }
+
+    /**
+     * Get the informations of a running task
+     * @param taskName the task name
+     * @param fromPart The starting point from which we want the informations
+     * @return the informations
+     */
+    @Callable
+    public Map<String, Object> getInformations(String taskName, Integer fromPart)
+    {
+        Map<String, Object> result = new HashMap<>();
+
+        // Is task finished ?
+        result.put("isRunning", _taskManager.isTaskRunning());
+
+        // report to json
+        List<TaskReportMessage> report = new ArrayList<TaskReportMessage>();
+        Integer lastPart = _taskManager.getReportMessage(report, fromPart);
+        if (lastPart != null)
+        {
+            List<Map<String, Object>> listReports = new ArrayList<>();
+
+            for (TaskReportMessage reportMessage : report)
+            {
+                Map<String, Object> entry = new HashMap<>();
+                entry.put("type", reportMessage.getTaskReportMessageType().name().toLowerCase());
+                entry.put("message", reportMessage.getMessage());
+                listReports.add(entry);
+            }
+
+            result.put("report", listReports);
+            result.put("lastPart", lastPart);
+        }
+
+        // progress to json
+        result.put("progress", _taskManager.getProgressInfo());
+
+        // repository state to json
+        result.put("respositoryState", _taskManager.isRepositoryStarted());
+
+        return result;
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/MaintenanceTaskManager.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/MaintenanceTaskManager.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/MaintenanceTaskManager.java	(revision 0)
@@ -0,0 +1,272 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.cocoon.Constants;
+import org.apache.cocoon.environment.Context;
+import org.apache.jackrabbit.core.config.ConfigurationException;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+
+import org.ametys.plugins.repositoryapp.RepositoryProvider;
+import org.ametys.runtime.config.Config;
+import org.ametys.workspaces.repository.maintenance.TaskReport.TaskReportMessage;
+
+/**
+ * The MaintenanceTaskManager Component
+ */
+public class MaintenanceTaskManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable
+{
+    /** The avalon role. */
+    public static final String ROLE = MaintenanceTaskManager.class.getName();
+
+    /** The maintenance running task */
+    protected AbstractMaintenanceTask _runningTask;
+
+    /** The maintenance running task type */
+    protected MaintenanceTaskType _runningTaskType;
+
+    private Context _context;
+
+    /** The repository provider. */
+    private RepositoryProvider _repositoryProvider;
+
+    private final ExecutorService _executor = Executors.newFixedThreadPool(1);
+    private FutureTask<Void> _future;
+
+    private boolean _repositoryShutdown;
+
+    /** Task types */
+    public enum MaintenanceTaskType
+    {
+        /** data store GC */
+        DATA_STORE_GARBAGE_COLLECTOR,
+        /** reindexing task */
+        REINDEXING,
+        /** consistency check */
+        CONSISTENCY_CHECK;
+    }
+
+    @Override
+    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
+    {
+        _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
+    }
+
+    @Override
+    public void service(ServiceManager manager) throws ServiceException
+    {
+        _repositoryProvider = (RepositoryProvider) manager.lookup(RepositoryProvider.ROLE);
+    }
+
+    /**
+     * Launch a maintenance task
+     * @param type
+     * @return true if a task has been launched
+     */
+    public boolean launch(MaintenanceTaskType type)
+    {
+        boolean launched = false;
+        if (!isTaskRunning())
+        {
+            try
+            {
+                run(type);
+                launched = true;
+            }
+            catch (ConfigurationException e)
+            {
+                getLogger().error("Unable to get the configuration of the repository.", e);
+            }
+            catch (RepositoryException e)
+            {
+                getLogger().error("Unable to launch the task.", e);
+            }
+        }
+        return launched;
+    }
+
+    private void run(final MaintenanceTaskType type) throws ConfigurationException, RepositoryException
+    {
+        _runningTaskType = type;
+
+        // Shutdown repository
+        shutdownRepository();
+
+        final Logger logger = getLogger();
+        final RepositoryConfig repositoryConfig = _getRepositoryConfig();
+        _future = new FutureTask<Void>(
+                new Callable<Void>()
+                {
+                    public Void call()
+                    {
+                        try
+                        {
+                            _runningTask = _createTask(type);
+                            _runningTask.execute(repositoryConfig);
+                        }
+                        catch (RepositoryException e)
+                        {
+                            logger.error(e.getMessage(), e);
+                        }
+                        finally
+                        {
+                            restartRepository();
+                            _runningTaskType = null;
+                        }
+
+                        return null;
+                    }
+                });
+
+        _executor.execute(_future);
+    }
+
+    /**
+     * Retrieve the repository config object
+     * @return RepositoryConfig
+     * @throws ConfigurationException 
+     */
+    private RepositoryConfig _getRepositoryConfig() throws ConfigurationException
+    {
+        String home = Config.getInstance().getValueAsString("org.ametys.plugins.repository.home");
+        String config = _context.getRealPath("/WEB-INF/param/repository.xml");
+
+        File homeFile = new File(home);
+        if (!homeFile.isAbsolute())
+        {
+            // No : consider it relative to context path
+            homeFile = new File(_context.getRealPath("/" + home));
+        }
+
+        return RepositoryConfig.create(config, homeFile.getAbsolutePath());
+    }
+
+    /**
+     * Shutdown the Ametys repository instance
+     * @throws RepositoryException 
+     */
+    protected void shutdownRepository() throws RepositoryException
+    {
+        if (_repositoryProvider.isJndi())
+        {
+            throw new RepositoryException("JNDI Repository instance are currently not allowed.");
+        }
+
+        // FIXME Shutdown the repository
+        _repositoryProvider.disconnect();
+        _repositoryShutdown = true;
+
+        if (getLogger().isInfoEnabled())
+        {
+            getLogger().info("Repository instance has been shutdown in order to run an automated maintenance task.");
+        }
+    }
+
+    /**
+     * Re-create the Ametys repository instance
+     */
+    protected void restartRepository()
+    {
+        // FIXME @see #shutdownRepository()
+        // _repositoryShutdown = true;
+    }
+
+    /**
+     * Initialize the tasks.
+     * @param type MaintenanceTaskType
+     * @return the task.
+     * @throws RepositoryException
+     */
+    protected AbstractMaintenanceTask _createTask(MaintenanceTaskType type) throws RepositoryException
+    {
+        switch(type)
+        {
+            case DATA_STORE_GARBAGE_COLLECTOR:
+                return new DataStoreGarbageCollectorTask();
+            case REINDEXING:
+                return new ReindexingTask();
+            case CONSISTENCY_CHECK:
+                return new ConsistencyCheckTask();
+            default:
+                throw new IllegalArgumentException("This type of maintenance task is not allowed : " + type);
+        }
+    }
+
+    /**
+     * Indicates if a maintenance task is running.
+     * @return true if a task is running.
+     */
+    public boolean isTaskRunning()
+    {
+        return _runningTaskType != null;
+    }
+
+    /**
+     * Get progress information.
+     * @return a map containing informations on the progress status.
+     */
+    public Map<String, String> getProgressInfo()
+    {
+        if (_runningTask != null)
+        {
+            return _runningTask.getProgressInfo();
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the {@link TaskReportMessage} objects starting from a given part.
+     * @param list the list of {@link TaskReportMessage} to be populated
+     * @param fromPart 
+     * @return the last report part number
+     */
+    public Integer getReportMessage(List<TaskReportMessage> list, int fromPart)
+    {
+        if (_runningTask != null)
+        {
+            return _runningTask.getReportMessage(list, fromPart);
+        }
+
+        return null;
+    }
+
+    /**
+     * Retrieves the type of the running task
+     * @return the type of the running task or null.
+     */
+    public String getRunningTaskType()
+    {
+        if (_runningTaskType != null)
+        {
+            return _runningTaskType.name();
+        }
+
+        return null;
+    }
+
+    /**
+     * Indicates is the Ametys repository is started or has been shut down.
+     * @return true is the repository is started
+     */
+    public boolean isRepositoryStarted()
+    {
+        return !_repositoryShutdown;
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/ReindexingTask.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/ReindexingTask.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/ReindexingTask.java	(revision 0)
@@ -0,0 +1,113 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+
+/**
+ * ReindexingTask
+ */
+public class ReindexingTask extends AbstractMaintenanceTask
+{
+    private static final String _INDEX_FOLDER_RELATIVE_PATH = "repository" + File.separator + "index";
+    private static final String _WORKSPACES_FOLDER_RELATIVE_PATH = "workspaces";
+
+    /** The JackRabbit RepositoryImpl */
+    protected RepositoryImpl _repository;
+
+    private String[] _workspaceFolders;
+
+    @Override
+    protected void initialize() throws RepositoryException
+    {
+        File file = new File(_repositoryConfig.getHomeDir() + File.separator + _WORKSPACES_FOLDER_RELATIVE_PATH);
+        _workspaceFolders = file.list(new FilenameFilter()
+        {
+            @Override
+            public boolean accept(File dir, String name)
+            {
+                return dir.isDirectory();
+            }
+        });
+
+
+        // Initialize the task progress object.
+        // deleting workspace index folders + repository root index folder = 60%
+        // reindexing = 40%
+        _progress = new TaskProgress(5f / 3 * (_workspaceFolders.length + 1));
+    }
+
+    @Override
+    protected void apply() throws RepositoryException
+    {
+        // Deleting repository root index folder
+        try
+        {
+            deleteIndexFolder(_INDEX_FOLDER_RELATIVE_PATH);
+            _report.add("Successfully deleted root repository index folder");
+        }
+        catch (IOException e)
+        {
+            _report.addException(e);
+        }
+
+        // Deleting workspace index folders
+        for (String folder : _workspaceFolders)
+        {
+            try
+            {
+                deleteIndexFolder(_WORKSPACES_FOLDER_RELATIVE_PATH  + File.separator + folder + File.separator + "index");
+                _report.add("Successfully deleted index folder of workspace '" + folder + "'");
+            }
+            catch (IOException e)
+            {
+                _report.addException(e);
+            }
+        }
+
+        // Creates a repository instance to perform the re-indexing process.
+        _report.add("Starting repository to launch the re-indexing process");
+        _repository = RepositoryImpl.create(_repositoryConfig);
+        _report.add("Repository restarted successfully, reindexing process has ended.");
+        setProgressTo(90);
+
+        // logout
+        _report.add("Shuting down repository");
+        _repository.shutdown();
+        _repository = null;
+        setProgressTo(100);
+    }
+
+    @Override
+    protected void close()
+    {
+        // shutdown properly if an exception has been thrown
+        if (_repository != null)
+        {
+            _repository.shutdown();
+        }
+    }
+
+    private void deleteIndexFolder(String relativePath) throws IOException
+    {
+        File dir = new File(_repositoryConfig.getHomeDir() + File.separator + relativePath);
+        FileUtils.deleteDirectory(dir);
+        if (_progress != null)
+        {
+            _progress.progress();
+        }
+    }
+
+    private void setProgressTo(int percentage)
+    {
+        if (_progress != null)
+        {
+            _progress.progressTotalPercentage(percentage);
+        }
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/TaskProgress.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/TaskProgress.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/TaskProgress.java	(revision 0)
@@ -0,0 +1,249 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TaskProgress
+ */
+public class TaskProgress
+{
+    private final float _total;
+    private float _progress;
+    private State _state;
+
+    private Exception _exception;
+
+    /**
+     * Progress state
+     */
+    public enum State 
+    {
+        /** NEW */
+        NEW,
+        /** RUNNING */
+        RUNNING,
+        /** FINISHED */
+        FINISHED,
+        /** ERROR_STATE */
+        ERROR_STATE
+    }
+
+    /**
+     * Ctor
+     * @param total
+     */
+    public TaskProgress(float total)
+    {
+        this._total = total;
+        this._progress = 0f;
+        this._state = State.NEW;
+    }
+
+    /**
+     * Overriden Ctor
+     * @param total
+     * @param start 
+     */
+    public TaskProgress(float total, float start)
+    {
+        this(total);
+        this._progress = start;
+    }
+
+    /**
+     * Unit progress
+     */
+    public void progress()
+    {
+        progress(1);
+    }
+
+    /**
+     * Quantitative progress
+     * @param quantity 
+     */
+    public synchronized void progress(float quantity)
+    {
+        _progress += quantity;
+    }
+
+    /**
+     * Progress by a percentage relative to the range [current progress; total]
+     * @param percentage must be in the [0; 100] range
+     */
+    public void progressRelativePercentage(int percentage)
+    {
+        if (percentage < 0 || percentage > 100)
+        {
+            throw new IllegalArgumentException("percentage must be in the [0; 100] range.");
+        }
+
+        synchronized (this)
+        {
+            if (percentage == 100)
+            {
+                _progress = _total;
+            }
+            else
+            {
+                _progress += (_total - _progress) * percentage / 100f;
+            }
+        }
+    }
+
+    /**
+     * Progress by a given percentage
+     * @param percentage must be in the [0; 100] range
+     */
+    public void progressTotalPercentage(int percentage)
+    {
+        if (percentage < 0 || percentage > 100)
+        {
+            throw new IllegalArgumentException("percentage must be in the [0; 100] range.");
+        }
+
+        synchronized (this)
+        {
+            if (percentage == 100)
+            {
+                _progress = _total;
+            }
+            else
+            {
+                _progress = _total * percentage / 100f;
+            }
+        }
+    }
+
+    /**
+     * getProgressPercentage
+     * @return int
+     */
+    public synchronized float getProgressPercentage()
+    {
+        return Math.min(1f, _progress / _total);
+    }
+
+    /**
+     * setRunning
+     */
+    public synchronized void setRunning()
+    {
+        _state = State.RUNNING;
+    }
+
+    /**
+     * setFinished
+     */
+    public synchronized void setFinished()
+    {
+        _state = State.FINISHED;
+    }
+
+    /**
+     * setInErrorState
+     */
+    public void setInErrorState()
+    {
+        setInErrorState(null);
+    }
+
+    /**
+     * setInErrorState
+     * @param e
+     */
+    public synchronized void setInErrorState(Exception e)
+    {
+        _exception = e;
+        _state = State.ERROR_STATE;
+    }
+
+    /**
+     * isNotStarted
+     * @return boolean
+     */
+    public boolean isNotStarted()
+    {
+        return State.NEW.equals(_state);
+    }
+
+    /**
+     * isRunning
+     * @return boolean
+     */
+    public boolean isRunning()
+    {
+        return State.RUNNING.equals(_state);
+    }
+
+    /**
+     * isFinished
+     * @return boolean
+     */
+    public boolean isFinished()
+    {
+        return State.FINISHED.equals(_state);
+    }
+
+    /**
+     * isInErrorState
+     * @return boolean
+     */
+    public boolean isInErrorState()
+    {
+        return State.ERROR_STATE.equals(_state);
+    }
+
+    /**
+     * getException
+     * @return Exception
+     */
+    public Exception getException()
+    {
+        return _exception;
+    }
+
+    /**
+     * getState
+     * @return State
+     */
+    public State getState()
+    {
+        return _state;
+    }
+
+
+    /**
+     * Returns informations this progress object.
+     * @return a map containing informations on the progress status
+     */
+    public Map<String, String> getProgressInfo()
+    {
+        Map<String, String> infos = new HashMap<String, String>();
+        synchronized (this)
+        {
+            infos.put("progress", String.valueOf(_progress));
+            infos.put("total", String.valueOf(_total));
+            infos.put("percentage", String.valueOf(getProgressPercentage()));
+            infos.put("state", _state.name());
+            if (_exception != null)
+            {
+                infos.put("exception", _exception.getLocalizedMessage());
+            }
+        }
+        return infos;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder("Progress : ");
+        synchronized (this)
+        {
+            sb.append(getProgressPercentage()).append(" ");
+            sb.append("[").append(_progress).append("/").append(_total).append("]");
+        }
+        return sb.toString();
+    }
+}
Index: main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/TaskReport.java
===================================================================
--- main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/TaskReport.java	(revision 0)
+++ main/workspace-repository/src/org/ametys/workspaces/repository/maintenance/TaskReport.java	(revision 0)
@@ -0,0 +1,137 @@
+package org.ametys.workspaces.repository.maintenance;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TaskReport
+ */
+public class TaskReport
+{
+    private final List<List<TaskReportMessage>> _reportList = new ArrayList<List<TaskReportMessage>>();
+
+    private final List<TaskReportMessage> _currentList = new ArrayList<TaskReportMessage>();
+
+    /** Task Report message type */
+    public enum TaskReportMessageType
+    {
+        /** INFO */
+        INFO,
+        /** WARN */
+        WARN,
+        /** ERROR */
+        ERROR;
+    }
+
+    /**
+     * Add an INFO message to the report
+     * 
+     * @param msg
+     */
+    public void add(String msg)
+    {
+        add(null, msg);
+    }
+
+    /**
+     * Add an ERROR message to the report, containing the message of the
+     * exception.
+     * 
+     * @param e the exception.
+     */
+    public void addException(Exception e)
+    {
+        add(TaskReportMessageType.ERROR, e.getLocalizedMessage());
+    }
+
+    /**
+     * An an message of the specified type to the report
+     * 
+     * @param type The message type
+     * @param msg
+     */
+    public void add(TaskReportMessageType type, String msg)
+    {
+        TaskReportMessage trm = new TaskReportMessage(type, msg);
+        synchronized (this)
+        {
+            _currentList.add(trm);
+        }
+    }
+
+    /**
+     * Get the {@link TaskReportMessage} objects starting from a given part.
+     * @param list the list of {@link TaskReportMessage} to be populated
+     * @param fromPart 
+     * @return the last report part number
+     */
+    public synchronized int getMessages(List<TaskReportMessage> list, int fromPart)
+    {
+        manageReportList();
+        List<List<TaskReportMessage>> subReportList = _reportList.subList(fromPart, _reportList.size());
+        for (List<TaskReportMessage> subReportListPart : subReportList)
+        {
+            list.addAll(subReportListPart);
+        }
+
+        return _reportList.size();
+    }
+
+    private synchronized void manageReportList()
+    {
+        if (_currentList.isEmpty())
+        {
+            return;
+        }
+
+        _reportList.add(new ArrayList<TaskReportMessage>(_currentList));
+        _currentList.clear();
+    }
+
+    /**
+     * Inner TaskReportMessage class
+     */
+    public class TaskReportMessage
+    {
+        private TaskReportMessageType _type;
+        private String _message;
+
+        /**
+         * Ctor
+         * 
+         * @param type
+         * @param message
+         */
+        public TaskReportMessage(TaskReportMessageType type, String message)
+        {
+            _type = type == null ? TaskReportMessageType.INFO : type;
+            _message = message;
+        }
+
+        /**
+         * Retrieves the type of the report message
+         * @return the type of the report message
+         */
+        public TaskReportMessageType getTaskReportMessageType()
+        {
+            return _type;
+        }
+
+        /**
+         * Retrieves the content of the report message
+         * @return the message
+         */
+        public String getMessage()
+        {
+            return _message;
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.append(_type).append(" ").append(_message);
+            return sb.toString();
+        }
+    }
+}
