Index: test/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginTest.java
===================================================================
--- test/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginTest.java	(revision 0)
+++ test/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginTest.java	(revision 0)
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2007, XpertNet SARL, and individual contributors as indicated
+ * by the contributors.txt.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.plugin.scheduler;
+
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWiki;
+import com.xpn.xwiki.XWikiConfig;
+import com.xpn.xwiki.user.api.XWikiRightService;
+import com.xpn.xwiki.doc.XWikiDocument;
+import org.jmock.Mock;
+import org.quartz.Trigger;
+import org.quartz.SchedulerException;
+
+/**
+ * Unit tests for the {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin} class.
+ *
+ * @version $Id: $
+ */
+public class SchedulerPluginTest extends org.jmock.cglib.MockObjectTestCase
+{
+    private SchedulerPlugin schedulerPlugin;
+
+    private XWikiContext context;
+
+    private Mock mockXWiki;
+
+    public void setUp()
+    {
+        context = new XWikiContext();
+
+        mockXWiki = createXWiki();
+
+        context.setWiki((XWiki) mockXWiki.proxy());
+        this.schedulerPlugin =
+            new SchedulerPlugin("scheduler", SchedulerPlugin.class.getName(), context);
+    }
+
+    public void testGetJobStatus() throws SchedulerPluginException, SchedulerException
+    {
+        BaseObject mockJob = createJobBaseObject();
+
+        assertEquals(Trigger.STATE_NONE, schedulerPlugin.getJobStatus(mockJob).getState());
+        assertEquals("None", schedulerPlugin.getJobStatus(mockJob).getValue());
+
+        schedulerPlugin.scheduleJob(mockJob, context);
+
+        assertEquals(Trigger.STATE_NORMAL, schedulerPlugin.getJobStatus(mockJob).getState());
+        assertEquals("Normal", schedulerPlugin.getJobStatus(mockJob).getValue());
+
+        schedulerPlugin.pauseJob(mockJob, context);
+
+        assertEquals(Trigger.STATE_PAUSED, schedulerPlugin.getJobStatus(mockJob).getState());
+        assertEquals("Paused", schedulerPlugin.getJobStatus(mockJob).getValue());
+
+        schedulerPlugin.scheduleJob(mockJob, context);
+
+        // we cannot schedule a paused job, only resume it
+        assertEquals(Trigger.STATE_PAUSED, schedulerPlugin.getJobStatus(mockJob).getState());
+        assertEquals("Paused", schedulerPlugin.getJobStatus(mockJob).getValue());
+
+        schedulerPlugin.resumeJob(mockJob, context);
+        assertEquals(Trigger.STATE_NORMAL, schedulerPlugin.getJobStatus(mockJob).getState());
+        assertEquals("Normal", schedulerPlugin.getJobStatus(mockJob).getValue());
+
+        schedulerPlugin.unscheduleJob(mockJob, context);
+        assertEquals(Trigger.STATE_NONE, schedulerPlugin.getJobStatus(mockJob).getState());
+        assertEquals("None", schedulerPlugin.getJobStatus(mockJob).getValue());
+    }
+
+    public BaseObject createJobBaseObject()
+    {
+        Mock mockJobObject = mock(BaseObject.class);
+        mockJobObject.stubs().method("getStringValue").with(eq("jobName"))
+            .will(returnValue("Mock Job"));
+        mockJobObject.stubs().method("getStringValue").with(eq("jobClass"))
+            .will(returnValue("com.xpn.xwiki.plugin.scheduler.GroovyJob"));
+        mockJobObject.stubs().method("getStringValue").with(eq("cron"))
+            .will(returnValue("0 0 12 * * ?"));
+        mockJobObject.stubs().method("getLargeStringValue").with(eq("script"))
+            .will(returnValue(""));
+        mockJobObject.stubs().method("getStringValue").with(eq("status")).will(returnValue(""));
+        mockJobObject.stubs().method("getNumber").will(returnValue(0));
+        mockJobObject.stubs().method("getName").will(returnValue("ASpace.ADoc"));
+        mockJobObject.stubs().method("setStringValue");
+        return (BaseObject) mockJobObject.proxy();
+    }
+
+    public XWikiRightService createRightService()
+    {
+        Mock mockRightService = mock(XWikiRightService.class);
+        mockRightService.stubs().method("hasProgrammingRights").will(returnValue(true));
+        return (XWikiRightService) mockRightService.proxy();
+    }
+
+    public Mock createXWiki()
+    {
+        Mock mockXWiki = mock(XWiki.class, new Class[]{XWikiConfig.class, XWikiContext.class},
+            new Object[]{new XWikiConfig(), this.context});
+
+        XWikiDocument jobClass = new XWikiDocument();
+        jobClass.setFullName(SchedulerPlugin.XWIKI_JOB_CLASS);
+
+        mockXWiki.stubs().method("getDocument").will(returnValue(jobClass));
+        mockXWiki.stubs().method("saveDocument");
+        mockXWiki.stubs().method("search").will(returnValue(new java.util.ArrayList()));
+
+        mockXWiki.stubs().method("getRightService").will(returnValue(createRightService()));
+
+        return mockXWiki;
+    }
+}
+
Index: main/java/com/xpn/xwiki/plugin/scheduler/GroovyJob.java
===================================================================
--- main/java/com/xpn/xwiki/plugin/scheduler/GroovyJob.java	(revision 0)
+++ main/java/com/xpn/xwiki/plugin/scheduler/GroovyJob.java	(revision 0)
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2005-2007, XpertNet SARL, and individual contributors as indicated
+ * by the contributors.txt.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.plugin.scheduler;
+
+import groovy.lang.Binding;
+import groovy.lang.GroovyShell;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.api.Context;
+
+/**
+ * The task that will get executed by the Scheduler when the Job is triggered. This task in turn
+ * calls a Groovy script to perform the execution.
+ *
+ * <p><b>Important:</b>: Note that the script will execute in the XWiki Context that was set at the
+ * time the Job was scheduled for execution. For example calling <code>context.getDoc()</code> will
+ * return the current document that was set at that time and not the current document that is set
+ * when the Groovy script executes...
+ *
+ * @version $Id: $
+ */
+public class GroovyJob implements Job
+{
+    /**
+     * Executes the Groovy script passed in the <code>script</code> property of the {@link
+     * com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#XWIKI_JOB_CLASS} object extracted from the
+     * XWiki context passed in the Quartz's Job execution context. The XWiki Task object is looked
+     * for in the current document that was set in the context at the time the Job was scheduled.
+     *
+     * @param context the Quartz execution context containing the XWiki context from which the
+     * script to execute is retrieved
+     * @throws JobExecutionException if the script fails to execute or if the user didn't have
+     * programming rights when the Job was scheduled
+     * @see Job#execute(org.quartz.JobExecutionContext)
+     */
+    public void execute(JobExecutionContext context) throws JobExecutionException
+    {
+        try {
+            JobDataMap data = context.getJobDetail().getJobDataMap();
+
+            // The XWiki context was saved in the Job execution data map. Get it as we'll retrieve
+            // the script to execute from it.
+            Context xcontext = (Context) data.get("context");
+
+            if (xcontext.hasProgrammingRights()) {
+
+                // Get the job XObject to be executed
+                BaseObject object = (BaseObject) data.get("xjob");
+
+                // Make the Job execution data available to the Groovy script
+                Binding binding = new Binding(data.getWrappedMap());
+
+                // Execute the Groovy script
+                GroovyShell shell = new GroovyShell(binding);
+                shell.evaluate(object.getLargeStringValue("script"));
+            } else {
+                throw new JobExecutionException("The user [" + xcontext.getUser() + "] didn't have "
+                    + "programming rights when the job [" + context.getJobDetail().getName()
+                    + "] was scheduled.");
+            }
+        } catch (CompilationFailedException e) {
+            throw new JobExecutionException("Failed to execute script for job ["
+                + context.getJobDetail().getName() + "]", e, true);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
Index: main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginApi.java
===================================================================
--- main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginApi.java	(revision 4005)
+++ main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginApi.java	(working copy)
@@ -25,6 +25,7 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.quartz.SchedulerException;
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
 import com.xpn.xwiki.api.Api;
@@ -34,6 +35,18 @@
 import com.xpn.xwiki.objects.BaseObject;
 
 /**
+ * A Scheduler plugin to plan execution of Jobs from XWiki with cron expressions. The plugin use
+ * Quartz's scheduling library.
+ *
+ * Jobs are represented by {@link com.xpn.xwiki.api.Object} XObjects, instances of the {@link
+ * SchedulerPlugin#XWIKI_JOB_CLASS} XClass. These XObjects do store a job name, the implementation
+ * class name of the job to be executed, the cron expression to precise when the job should be
+ * fired, and possibly a groovy script with the job's program.
+ *
+ * The plugin offers a {@link GroovyJob} Groovy Job wrapper to execute groovy scripts (typically for
+ * use inside the Wiki), but can also be used with any Java class implementing {@link
+ * org.quartz.Job}
+ *
  * @version $Id: $
  */
 public class SchedulerPluginApi extends Api
@@ -43,6 +56,9 @@
      */
     private static final Log LOG = LogFactory.getLog(SchedulerPluginApi.class);
 
+    /**
+     * The instance of the actual implementation class of the plugin
+     */
     private SchedulerPlugin plugin;
 
     public SchedulerPluginApi(SchedulerPlugin plugin, XWikiContext context)
@@ -51,49 +67,56 @@
         setPlugin(plugin);
     }
 
-    public boolean pauseTask(String number)
+    /**
+     * Return the trigger state of the given {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#XWIKI_JOB_CLASS}
+     * XObject job. Possible values are : None (the trigger does not exists yet, or has been
+     * deleted), Normal, Blocked, Complete, Error and Paused
+     *
+     * @param object the XObject job to give the state of
+     * @return a String representing this state
+     */
+    public String getStatus(Object object)
     {
-        return pauseTask(context.getDoc().getObject(SchedulerPlugin.TASK_CLASS,
-            Integer.valueOf(number).intValue()));
+        try {
+            return getJobStatus(object.getXWikiObject()).getValue();
+        } catch (SchedulerException e) {
+            context.put("error", e.getMessage());
+            return null;
+        }
     }
 
-    public boolean pauseTask(Object object)
+    /**
+     * Return the trigger state as a ${@link JobState}, that holds both the integer trigger's inner
+     * value of the state and a String as a human readable representation of that state
+     */
+    public JobState getJobStatus(BaseObject object) throws SchedulerException
     {
-        return pauseTask(object.getXWikiObject());
+        return plugin.getJobStatus(object);
     }
 
-    public boolean pauseTask(BaseObject object)
+    public JobState getJobStatus(Object object) throws SchedulerException
     {
-        try {
-            plugin.pauseTask(String.valueOf(object.getNumber()));
-            saveStatus("Paused", object);
-            LOG.debug("Pause Task : " + object.getStringValue("taskName"));
-            return true;
-        } catch (XWikiException e) {
-            context.put("error", e.getMessage());
-            return false;
-        }
+        return plugin.getJobStatus(object.getXWikiObject());
     }
 
     /**
      * Schedule the given XObject to be executed according to its parameters. Errors are returned in
-     * the context map. Scheduling can be called for example: <code> #if($xwiki.scheduler.scheduleTask($task)!=true)
-     * #error($context.get("error") #else #info("Task scheduled") #end </code> Where $task is an
-     * XObject, instance of the XWiki.Task XClass
+     * the context map. Scheduling can be called for example: <code> #if($xwiki.scheduler.scheduleJob($job)!=true)
+     * #error($context.get("error") #else #info("Job scheduled") #end </code> Where $job is an
+     * XObject, instance of the {@link SchedulerPlugin#XWIKI_JOB_CLASS} XClass
      *
-     * @param object the XObject to be scheduled, an instance of the XClass XWiki.Task
+     * @param object the XObject to be scheduled, an instance of the XClass XWiki.SchedulerJobClass
      * @return true on success, false on failure
      */
-    public boolean scheduleTask(Object object)
+    public boolean scheduleJob(Object object)
     {
-        return scheduleTask(object.getXWikiObject());
+        return scheduleJob(object.getXWikiObject());
     }
 
-    public boolean scheduleTask(BaseObject object)
+    public boolean scheduleJob(BaseObject object)
     {
         try {
-            plugin.scheduleTask(object, context);
-            saveStatus("Scheduled", object);
+            plugin.scheduleJob(object, context);
             return true;
         } catch (Exception e) {
             context.put("error", e.getMessage());
@@ -102,17 +125,40 @@
     }
 
     /**
-     * Schedule for execution all XWiki Tasks found in the passed Document object.
+     * Schedule all {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#XWIKI_JOB_CLASS} XObjects
+     * stored inside the given Wiki document, according to each XObject own parameters.
+     *
+     * @param document the document holding the XObjects Jobs to be scheduled
+     * @return true on success, false on failure.
      */
-    public boolean scheduleTasks(Document document)
+    public boolean scheduleJobs(Document document)
     {
+        boolean result = true;
+        Vector objects = document.getObjects(SchedulerPlugin.XWIKI_JOB_CLASS);
+        for (Iterator iterator = objects.iterator(); iterator.hasNext();) {
+            Object object = (Object) iterator.next();
+            result &= scheduleJob(object.getXWikiObject());
+        }
+        return result;
+    }
+
+    /**
+     * Pause the given XObject job by pausing all of its current triggers. Can be called the same
+     * way as {@link #scheduleJob}
+     *
+     * @param object the wrapped XObject Job to be paused
+     * @return true on success, false on failure.
+     */
+    public boolean pauseJob(Object object)
+    {
+        return pauseJob(object.getXWikiObject());
+    }
+
+    public boolean pauseJob(BaseObject object)
+    {
         try {
-            Vector objects = document.getObjects(SchedulerPlugin.TASK_CLASS);
-            for (Iterator iterator = objects.iterator(); iterator.hasNext();) {
-                Object object = (Object) iterator.next();
-                scheduleTask(object.getXWikiObject());
-            }
-            saveDocument(document.getDocument());
+            plugin.pauseJob(object, context);
+            LOG.debug("Pause Job : " + object.getStringValue("jobName"));
             return true;
         } catch (XWikiException e) {
             context.put("error", e.getMessage());
@@ -120,23 +166,23 @@
         }
     }
 
-    public boolean resumeTask(String number)
+    /**
+     * Resume a XObject job that is in a {@link JobState#STATE_PAUSED} state. Can be called the same
+     * way as {@link #scheduleJob}
+     *
+     * @param object the wrapped XObject Job to be paused
+     * @return true on success, false on failure.
+     */
+    public boolean resumeJob(Object object)
     {
-        return resumeTask(context.getDoc().getObject(SchedulerPlugin.TASK_CLASS,
-            Integer.valueOf(number).intValue()));
+        return resumeJob(object.getXWikiObject());
     }
 
-    public boolean resumeTask(Object object)
+    public boolean resumeJob(BaseObject object)
     {
-        return resumeTask(object.getXWikiObject());
-    }
-
-    public boolean resumeTask(BaseObject object)
-    {
         try {
-            plugin.resumeTask(String.valueOf(object.getNumber()));
-            saveStatus("Scheduled", object);
-            LOG.debug("Resume Task : " + object.getStringValue("taskName"));
+            plugin.resumeJob(object, context);
+            LOG.debug("Resume Job : " + object.getStringValue("jobName"));
             return true;
         } catch (XWikiException e) {
             context.put("error", e.getMessage());
@@ -144,23 +190,23 @@
         }
     }
 
-    public boolean unscheduleTask(String number)
+    /**
+     * Unschedule a XObject job by deleting it from the jobs table. Can be called the same way as
+     * {@link #scheduleJob}
+     *
+     * @param object the wrapped XObject Job to be paused
+     * @return true on success, false on failure.
+     */
+    public boolean unscheduleJob(Object object)
     {
-        return unscheduleTask(context.getDoc().getObject(SchedulerPlugin.TASK_CLASS,
-            Integer.valueOf(number).intValue()));
+        return unscheduleJob(object.getXWikiObject());
     }
 
-    public boolean unscheduleTask(Object object)
+    public boolean unscheduleJob(BaseObject object)
     {
-        return unscheduleTask(object.getXWikiObject());
-    }
-
-    public boolean unscheduleTask(BaseObject object)
-    {
         try {
-            saveStatus("Unscheduled", object);
-            plugin.unscheduleTask(String.valueOf(object.getNumber()));
-            LOG.debug("Delete Task : " + object.getStringValue("taskName"));
+            plugin.unscheduleJob(object, context);
+            LOG.debug("Delete Job : " + object.getStringValue("jobName"));
             return true;
         } catch (XWikiException e) {
             context.put("error", e.getMessage());
@@ -168,11 +214,28 @@
         }
     }
 
+    /**
+     * Give, for a XObject job in a {@JobState#STATE_NORMAL} state, the next date at which the job
+     * will be executed, according to its cron expression. Errors are returned in the context map.
+     * Can be called for example: <code> #set($firetime = $xwiki.scheduler.getNextFireTime($job))
+     * #if (!$firetime || $firetime=="") #error($context.get("error") #else #info("Fire time :
+     * $firetime") #end </code> Where $job is an XObject, instance of the {@link
+     * SchedulerPlugin#XWIKI_JOB_CLASS} XClass
+     *
+     * @param object the wrapped XObject for which to give the fire date
+     * @return the date the job will be executed
+     */
     public Date getNextFireTime(Object object)
     {
+        return getNextFireTime(object.getXWikiObject());
+    }
+
+    public Date getNextFireTime(BaseObject object)
+    {
         try {
-            return plugin.getNextFireTime(String.valueOf(object.getXWikiObject().getNumber()));
-        } catch (SchedulerPluginException e) {
+            return plugin.getNextFireTime(object);
+        }
+        catch (SchedulerPluginException e) {
             context.put("error", e.getMessage());
             return null;
         }
@@ -183,19 +246,6 @@
         return plugin;
     }
 
-    private void saveStatus(String status, BaseObject object)
-        throws XWikiException
-    {
-        context.getDoc().getObject(SchedulerPlugin.TASK_CLASS,
-            object.getNumber()).setStringValue("status", status);
-        saveDocument(context.getDoc());
-    }
-
-    private void saveDocument(XWikiDocument document) throws XWikiException
-    {
-        context.getWiki().saveDocument(document, context);
-    }
-
     public void setPlugin(SchedulerPlugin plugin)
     {
         this.plugin = plugin;
Index: main/java/com/xpn/xwiki/plugin/scheduler/GroovyTask.java
===================================================================
--- main/java/com/xpn/xwiki/plugin/scheduler/GroovyTask.java	(revision 4005)
+++ main/java/com/xpn/xwiki/plugin/scheduler/GroovyTask.java	(working copy)
@@ -1,99 +0,0 @@
-/*
- * Copyright 2005-2007, XpertNet SARL, and individual contributors as indicated
- * by the contributors.txt.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package com.xpn.xwiki.plugin.scheduler;
-
-import groovy.lang.Binding;
-import groovy.lang.GroovyShell;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.quartz.Job;
-import org.quartz.JobDataMap;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import com.xpn.xwiki.objects.BaseObject;
-import com.xpn.xwiki.api.Api;
-import com.xpn.xwiki.api.Context;
-
-/**
- * The task that will get executed by the Scheduler when the Job is triggered. This task in
- * turn calls a Groovy script to perform the execution.
- *
- * <p><b>Important:</b>: Note that the script will execute in the XWiki Context that was set at
- * the time the Job was scheduled for execution. For example calling <code>context.getDoc()</code>
- * will return the current document that was set at that time and not the current document that is
- * set when the Groovy script executes...
- *
- * @version $Id: $
- */
-public class GroovyTask implements Job
-{
-    /**
-     * Executes the Groovy script passed in the <code>script</code> property of the
-     * {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#TASK_CLASS} object extracted from the
-     * XWiki context passed in the Quartz's Job execution context. The XWiki Task object is
-     * looked for in the current document that was set in the context at the time the Job was
-     * scheduled.
-     *
-     * @param context the Quartz execution context containing the XWiki context from which the
-     *        script to execute is retrieved
-     * @exception JobExecutionException if the script fails to execute or if the user didn't have
-     *            programming rights when the Job was scheduled
-     * @see Job#execute(org.quartz.JobExecutionContext)
-     */
-    public void execute(JobExecutionContext context) throws JobExecutionException
-    {
-        try {
-            JobDataMap data = context.getJobDetail().getJobDataMap();
-
-            // The XWiki context was saved in the Job execution data map. Get it as we'll retrieve
-            // the script to execute from it.
-            Context xcontext = (Context) data.get("context");
-
-            if (xcontext.hasProgrammingRights()) {
-
-                // The current task id. This is needed to find the correct XWiki Task object that
-                // was stored in the current document as there can be several tasks stored in that
-                // document.
-                int task = data.getInt("task");
-
-                // Get the correct task object from the current doc set when the Job was
-                // scheduled.
-                // TODO: I think it would be much cleaner to store the XWiki Task object directly
-                // in the Job execution context rather than having to assume it's in the current
-                // document that was set at that time... Fix this!
-                BaseObject object = xcontext.getDoc().getObject(SchedulerPlugin.TASK_CLASS, task);
-
-                // Make the Job execution data available to the Groovy script
-                Binding binding = new Binding(data.getWrappedMap());
-
-                // Execute the Groovy script
-                GroovyShell shell = new GroovyShell(binding);
-                shell.evaluate(object.getLargeStringValue("script"));
-                
-            } else {
-                throw new JobExecutionException("The user [" + xcontext.getUser() + "] didn't have "
-                    + "programming rights when the job [" + context.getJobDetail().getName()
-                    + "] was scheduled.");
-            }
-        } catch (CompilationFailedException e) {
-            throw new JobExecutionException("Failed to execute script for job ["
-                + context.getJobDetail().getName() + "]", e, true);
-        }
-    }
-}
Index: main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginException.java
===================================================================
--- main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginException.java	(revision 4005)
+++ main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPluginException.java	(working copy)
@@ -23,21 +23,21 @@
 
 public class SchedulerPluginException extends PluginException
 {
-    protected static final int ERROR_SCHEDULERPLUGIN_SAVE_TASK_CLASS = 90000;
+    protected static final int ERROR_SCHEDULERPLUGIN_SAVE_JOB_CLASS = 90000;
 
     protected static final int ERROR_SCHEDULERPLUGIN_INITIALIZE_STATUS_LISTENER = 90001;
 
-    protected static final int ERROR_SCHEDULERPLUGIN_PAUSE_TASK = 90002;
+    protected static final int ERROR_SCHEDULERPLUGIN_PAUSE_JOB = 90002;
 
-    protected static final int ERROR_SCHEDULERPLUGIN_RESUME_TASK = 90003;
+    protected static final int ERROR_SCHEDULERPLUGIN_RESUME_JOB = 90003;
 
-    protected static final int ERROR_SCHEDULERPLUGIN_SCHEDULE_TASK = 90004;
+    protected static final int ERROR_SCHEDULERPLUGIN_SCHEDULE_JOB = 90004;
 
     protected static final int ERROR_SCHEDULERPLUGIN_BAD_CRON_EXPRESSION = 90005;
 
-    protected static final int ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND = 90006;
+    protected static final int ERROR_SCHEDULERPLUGIN_JOB_XCLASS_NOT_FOUND = 90006;
 
-    protected static final int ERROR_SCHEDULERPLUGIN_TASK_DOES_NOT_EXITS = 90007;
+    protected static final int ERROR_SCHEDULERPLUGIN_JOB_DOES_NOT_EXITS = 90007;
 
     protected static final int ERROR_SCHEDULERPLUGIN_GET_SCHEDULER = 90007;
 
Index: main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java
===================================================================
--- main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java	(revision 4005)
+++ main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java	(working copy)
@@ -21,6 +21,8 @@
 
 import java.text.ParseException;
 import java.util.Date;
+import java.util.List;
+import java.util.Iterator;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -56,21 +58,17 @@
     private static final Log LOG = LogFactory.getLog(SchedulerPlugin.class);
 
     /**
-     * Fullname of the XWiki Task Class representing a task that can be scheduled by this plugin.
+     * Fullname of the XWiki Scheduler Job Class representing a job that can be scheduled by this
+     * plugin.
      */
-    public static final String TASK_CLASS = "XWiki.Task";
-    
+    public static final String XWIKI_JOB_CLASS = "XWiki.SchedulerJobClass";
+
     /**
      * Default Quartz scheduler instance.
      */
     private Scheduler scheduler;
 
     /**
-     * Map that holds Job execution data.
-     */
-    private JobDataMap data = new JobDataMap();
-
-    /**
      * {@inheritDoc}
      *
      * @see XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
@@ -83,13 +81,14 @@
 
     /**
      * {@inheritDoc}
+     *
      * @see com.xpn.xwiki.plugin.XWikiPluginInterface#init(com.xpn.xwiki.XWikiContext)
      */
     public void init(XWikiContext context)
     {
         super.init(context);
         try {
-            updateTaskClass(context);
+            updateSchedulerJobClass(context);
             setScheduler(getDefaultSchedulerInstance());
             setStatusListener();
             getScheduler().start();
@@ -102,6 +101,7 @@
 
     /**
      * {@inheritDoc}
+     *
      * @see com.xpn.xwiki.plugin.XWikiPluginInterface#virtualInit(com.xpn.xwiki.XWikiContext)
      */
     public void virtualInit(XWikiContext context)
@@ -111,24 +111,37 @@
     }
 
     /**
-     * Schedule the given task by creating a job and associating a cron trigger with it
+     * Retrieve the job's status of a given {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin#XWIKI_JOB_CLASS}
+     * job XObject, by asking the actual job status to the quartz scheduler instance. It's the
+     * actual status, as the one stored in the XObject may be changed manually by users.
      *
-     * @param object the XWiki Task object 
-     * @param context the XWiki context
+     * @param object the XObject to give the status of
+     * @return the status of the Job inside the quartz scheduler, as {@link
+     *         com.xpn.xwiki.plugin.scheduler.JobState} instance
      */
-    public boolean scheduleTask(BaseObject object, XWikiContext context)
+    public JobState getJobStatus(BaseObject object) throws SchedulerException
+    {
+        int state = getScheduler()
+            .getTriggerState(getObjectUniqueId(object), Scheduler.DEFAULT_GROUP);
+        return new JobState(state);
+    }
+
+    public boolean scheduleJob(BaseObject object, XWikiContext context)
         throws SchedulerPluginException
     {
         boolean scheduled = true;
         try {
-            String task = String.valueOf(object.getNumber());
+            JobDataMap data = new JobDataMap();
 
-            JobDetail job = new JobDetail(task, Scheduler.DEFAULT_GROUP,
-                Class.forName(object.getStringValue("taskClass")), true, false, true);
-            Trigger trigger = new CronTrigger(task, Scheduler.DEFAULT_GROUP, task,
+            String xjob = getObjectUniqueId(object);
+
+            JobDetail job = new JobDetail(xjob, Scheduler.DEFAULT_GROUP,
+                Class.forName(object.getStringValue("jobClass")));
+
+            Trigger trigger = new CronTrigger(xjob, Scheduler.DEFAULT_GROUP, xjob,
                 Scheduler.DEFAULT_GROUP, object.getStringValue("cron"));
 
-            data.put("task", object.getNumber());
+            data.put("xjob", object);
 
             // We offer the Job a wrapped copy of the request context and of the XWiki API
             Context copy = new Context((XWikiContext) context.clone());
@@ -138,135 +151,160 @@
             job.setJobDataMap(data);
 
             getScheduler().addJob(job, true);
-            int state = getScheduler().getTriggerState(task, Scheduler.DEFAULT_GROUP);
-            switch (state) {
+
+            JobState status = getJobStatus(object);
+
+            switch (status.getState()) {
                 case Trigger.STATE_PAUSED:
-                    object.setStringValue("status", "Paused");
+                    // a paused job must be resumed, not scheduled
                     break;
                 case Trigger.STATE_NORMAL:
-                    if (getTrigger(task).compareTo(trigger) != 0) {
-                        LOG.debug("Reschedule Task : " + object.getStringValue("taskName"));
+                    if (getTrigger(object).compareTo(trigger) != 0) {
+                        LOG.debug("Reschedule Job : " + object.getStringValue("jobName"));
                     }
                     getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
-                    object.setStringValue("status", "Scheduled");
                     break;
                 case Trigger.STATE_NONE:
-                    LOG.debug("Schedule Task : " + object.getStringValue("taskName"));
+                    LOG.debug("Schedule Job : " + object.getStringValue("jobName"));
                     getScheduler().scheduleJob(trigger);
-                    LOG.info("XWiki Task Status :" + object.getStringValue("status"));
+                    LOG.info("XWiki Job Status :" + object.getStringValue("status"));
                     if (object.getStringValue("status").equals("Paused")) {
-                        getScheduler().pauseJob(task, Scheduler.DEFAULT_GROUP);
-                        object.setStringValue("status", "Paused");
+                        getScheduler().pauseJob(xjob, Scheduler.DEFAULT_GROUP);
+                        saveStatus("Paused", object, context);
                     } else {
-                        object.setStringValue("status", "Scheduled");
+                        saveStatus("Normal", object, context);
                     }
                     break;
                 default:
-                    LOG.debug("Schedule Task : " + object.getStringValue("taskName"));
+                    LOG.debug("Schedule Job : " + object.getStringValue("jobName"));
                     getScheduler().scheduleJob(trigger);
-                    object.setStringValue("status", "Scheduled");
+                    saveStatus("Normal", object, context);
                     break;
             }
         } catch (SchedulerException e) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_SCHEDULE_TASK,
-                "Error while scheduling task " + object.getStringValue("taskName"), e);
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_SCHEDULE_JOB,
+                "Error while scheduling job " + object.getStringValue("jobName"), e);
         } catch (ParseException e) {
             throw new SchedulerPluginException(
                 SchedulerPluginException.ERROR_SCHEDULERPLUGIN_BAD_CRON_EXPRESSION,
-                "Error while parsing cron expression for task " + object.getStringValue("taskName"),
+                "Error while parsing cron expression for job " + object.getStringValue("jobName"),
                 e);
         } catch (ClassNotFoundException e) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND,
-                "Error while loading task class for task : " + object.getStringValue("taskName"),
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_JOB_XCLASS_NOT_FOUND,
+                "Error while loading job class for job : " + object.getStringValue("jobName"),
                 e);
+        } catch (XWikiException e) {
+            throw new SchedulerPluginException(
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_JOB_XCLASS_NOT_FOUND,
+                "Error while saving job status for job : " + object.getStringValue("jobName"),
+                e);
         }
         return scheduled;
     }
 
     /**
-     * Pause the task with the given name by pausing all of its current triggers.
+     * Pause the job with the given name by pausing all of its current triggers.
      *
-     * @param taskName the name of the task to be paused
+     * @param object the non-wrapped XObject Job to be paused
      */
-    public void pauseTask(String taskName) throws SchedulerPluginException
+    public void pauseJob(BaseObject object, XWikiContext context) throws SchedulerPluginException
     {
         try {
-            getScheduler().pauseJob(taskName, Scheduler.DEFAULT_GROUP);
+            getScheduler().pauseJob(getObjectUniqueId(object), Scheduler.DEFAULT_GROUP);
+            saveStatus("Paused", object, context);
         } catch (SchedulerException e) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_PAUSE_TASK,
-                "Error occured while trying to pause task " + taskName, e);
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_PAUSE_JOB,
+                "Error occured while trying to pause job " + object.getStringValue("jobName"), e);
+        } catch (XWikiException e) {
+            throw new SchedulerPluginException(
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_PAUSE_JOB,
+                "Error occured while trying to save status of job " +
+                    object.getStringValue("jobName"), e);
         }
     }
 
     /**
-     * Resume the task with the given name (un-pause)
+     * Resume the job with the given name (un-pause)
      *
-     * @param taskName the name of the task to be paused
+     * @param object the non-wrapped XObject Job to be resumed
      */
-    public void resumeTask(String taskName) throws PluginException
+    public void resumeJob(BaseObject object, XWikiContext context) throws SchedulerPluginException
     {
         try {
-            getScheduler().resumeJob(taskName, Scheduler.DEFAULT_GROUP);
+            getScheduler().resumeJob(getObjectUniqueId(object), Scheduler.DEFAULT_GROUP);
+            saveStatus("Normal", object, context);
         } catch (SchedulerException e) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_RESUME_TASK,
-                "Error occured while trying to resume task " + taskName, e);
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_RESUME_JOB,
+                "Error occured while trying to resume job " + object.getStringValue("jobName"), e);
+        } catch (XWikiException e) {
+            throw new SchedulerPluginException(
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_RESUME_JOB,
+                "Error occured while trying to save status of job " +
+                    object.getStringValue("jobName"), e);
         }
     }
 
     /**
-     * Unschedule the given task
+     * Unschedule the given job
      *
-     * @param taskName the task name for the task to be unscheduled
+     * @param object the unwrapped XObject job to be unscheduled
      */
-    public void unscheduleTask(String taskName) throws SchedulerPluginException
+    public void unscheduleJob(BaseObject object, XWikiContext context)
+        throws SchedulerPluginException
     {
         try {
-            getScheduler().deleteJob(taskName, Scheduler.DEFAULT_GROUP);
+            getScheduler().deleteJob(getObjectUniqueId(object), Scheduler.DEFAULT_GROUP);
+            saveStatus("None", object, context);
         } catch (SchedulerException e) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND,
-                "Error while unscheduling task " + taskName, e);
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_JOB_XCLASS_NOT_FOUND,
+                "Error while unscheduling job " + object.getStringValue("jobName"), e);
+        } catch (XWikiException e) {
+            throw new SchedulerPluginException(
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_JOB_XCLASS_NOT_FOUND,
+                "Error while saving status of job " + object.getStringValue("jobName"), e);
         }
     }
 
     /**
-     * Get Trigger object of the given task
+     * Get Trigger object of the given job
      *
-     * @param task the task name for which the trigger will be given
-     * @return the trigger object of the given task
+     * @param object the unwrapped XObject to be retrieve the trigger for
+     * @return the trigger object of the given job
      */
-    private Trigger getTrigger(String task) throws SchedulerPluginException
+    private Trigger getTrigger(BaseObject object) throws SchedulerPluginException
     {
+        String job = getObjectUniqueId(object);
         Trigger trigger;
         try {
-            trigger = getScheduler().getTrigger(task, Scheduler.DEFAULT_GROUP);
+            trigger = getScheduler().getTrigger(job, Scheduler.DEFAULT_GROUP);
         } catch (SchedulerException e) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND,
-                "Error while getting trigger for task " + task, e);
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_JOB_XCLASS_NOT_FOUND,
+                "Error while getting trigger for job " + job, e);
         }
         if (trigger == null) {
             throw new SchedulerPluginException(
-                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_TASK_DOES_NOT_EXITS,
-                "Task does not exists");
+                SchedulerPluginException.ERROR_SCHEDULERPLUGIN_JOB_DOES_NOT_EXITS,
+                "Job does not exists");
         }
 
         return trigger;
     }
 
     /**
-     * Get the next fire time for the given task name Task
+     * Get the next fire time for the given job name SchedulerJob
      *
-     * @param task the task name for which the next fire time will be given
-     * @return the next Date the task will be fired at
+     * @param object unwrapped XObject job for which the next fire time will be given
+     * @return the next Date the job will be fired at
      */
-    public Date getNextFireTime(String task) throws SchedulerPluginException
+    public Date getNextFireTime(BaseObject object) throws SchedulerPluginException
     {
-        return getTrigger(task).getNextFireTime();
+        return getTrigger(object).getNextFireTime();
     }
 
     /**
@@ -303,9 +341,8 @@
 
     /**
      * @return the default Scheduler instance
-     * @exception SchedulerPluginException if the default Scheduler instance failed to be
-     *            retrieved for any reason. Note that on the first call the default scheduler is
-     *            also initialized.
+     * @throws SchedulerPluginException if the default Scheduler instance failed to be retrieved for
+     * any reason. Note that on the first call the default scheduler is also initialized.
      */
     private synchronized Scheduler getDefaultSchedulerInstance()
         throws SchedulerPluginException
@@ -323,7 +360,8 @@
 
     /**
      * Associates the scheduler with a StatusListener
-     * @exception SchedulerPluginException if the status listener failed to be set properly
+     *
+     * @throws SchedulerPluginException if the status listener failed to be set properly
      */
     private void setStatusListener() throws SchedulerPluginException
     {
@@ -338,43 +376,65 @@
         }
     }
 
+    private void saveStatus(String status, BaseObject object, XWikiContext context)
+        throws XWikiException
+    {
+        XWikiDocument jobHolder = context.getWiki().getDocument(object.getName(), context);
+        object.setStringValue("status", status);
+        context.getWiki().saveDocument(jobHolder, context);
+    }
+
     /**
-     * Creates the XWiki Task Class if it does not exist in the wiki. Update it if it exists but
-     * is missing some properties.
+     * Compute a cross-document unique {@link com.xpn.xwiki.objects.BaseObject} id, by concatenating
+     * its name (it's document holder full name, such as "SomeSpace.SomeDoc") and it's instance
+     * number inside this document.
      *
+     * The scheduler uses this unique object id to assure the unicity of jobs
+     *
+     * @return a unique String that can identify the object
+     */
+    private String getObjectUniqueId(BaseObject object)
+    {
+        return object.getName() + "_" + object.getNumber();
+    }
+
+    /**
+     * Creates the XWiki SchedulerJob XClass if it does not exist in the wiki. Update it if it
+     * exists but is missing some properties.
+     *
      * @param context the XWiki context
-     * @exception SchedulerPluginException if the updated Task Class failed to be saved
+     * @throws SchedulerPluginException if the updated SchedulerJob XClass failed to be saved
      */
-    private void updateTaskClass(XWikiContext context) throws SchedulerPluginException
+    private void updateSchedulerJobClass(XWikiContext context) throws SchedulerPluginException
     {
         XWiki xwiki = context.getWiki();
         boolean needsUpdate = false;
 
         XWikiDocument doc;
         try {
-            doc = xwiki.getDocument(SchedulerPlugin.TASK_CLASS, context);
+            doc = xwiki.getDocument(SchedulerPlugin.XWIKI_JOB_CLASS, context);
         } catch (Exception e) {
             doc = new XWikiDocument();
-            doc.setFullName(SchedulerPlugin.TASK_CLASS);
+            doc.setFullName(SchedulerPlugin.XWIKI_JOB_CLASS);
             needsUpdate = true;
         }
 
         BaseClass bclass = doc.getxWikiClass();
-        bclass.setName(SchedulerPlugin.TASK_CLASS);
-        needsUpdate |= bclass.addTextField("taskName", "Task Name", 30);
-        needsUpdate |= bclass.addTextField("taskClass", "Task Class", 30);
+        bclass.setName(SchedulerPlugin.XWIKI_JOB_CLASS);
+        needsUpdate |= bclass.addTextField("jobName", "Job Name", 60);
+        needsUpdate |= bclass.addTextField("jobClass", "Job Class", 60);
         needsUpdate |= bclass.addTextField("status", "Status", 30);
         needsUpdate |= bclass.addTextField("cron", "Cron Expression", 30);
-        needsUpdate |= bclass.addTextAreaField("script", "Groovy Script", 45, 10);
+        needsUpdate |= bclass.addTextAreaField("script", "Job Script", 45, 10);
 
         if (needsUpdate) {
             try {
                 xwiki.saveDocument(doc, context);
             } catch (XWikiException ex) {
                 throw new SchedulerPluginException(
-                    SchedulerPluginException.ERROR_SCHEDULERPLUGIN_SAVE_TASK_CLASS,
-                    "Error while saving " + SchedulerPlugin.TASK_CLASS
-                    + " class document in XWiki", ex);
+                    SchedulerPluginException.ERROR_SCHEDULERPLUGIN_SAVE_JOB_CLASS,
+                    "Error while saving " + SchedulerPlugin.XWIKI_JOB_CLASS
+                        + " class document in XWiki", ex);
             }
         }
     }
