Index: plugin/scheduler/SchedulerPluginApi.java
===================================================================
--- plugin/scheduler/SchedulerPluginApi.java	(revision 0)
+++ plugin/scheduler/SchedulerPluginApi.java	(revision 0)
@@ -0,0 +1,134 @@
+package com.xpn.xwiki.plugin.scheduler;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Api;
+import com.xpn.xwiki.api.Context;
+import com.xpn.xwiki.api.Document;
+import com.xpn.xwiki.api.Object;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.plugin.PluginException;
+
+public class SchedulerPluginApi extends Api {
+	
+	private static Log logger = LogFactory.getLog(SchedulerPluginApi.class);
+
+	private SchedulerPlugin plugin;
+
+	public SchedulerPluginApi(SchedulerPlugin plugin, XWikiContext context) {
+		super(context);
+		init(plugin, context);
+	}
+
+	private void init(SchedulerPlugin plugin, XWikiContext context) {
+		setPlugin(plugin);
+	}
+
+	public void pauseTask(String number, Document document) {
+		pauseTask(document.getObject("XWiki.Task", Integer.valueOf(number).intValue()), document);
+	}
+
+	public void pauseTask(Object object, Document document) {
+		pauseTask(object.getXWikiObject(), document.getDocument());
+	}
+
+	public void pauseTask(BaseObject object, XWikiDocument document) {
+		try {
+			plugin.pauseTask(String.valueOf(object.getNumber()));
+			saveStatus("Paused", object, document);
+			logger.debug("Pause Task : " + object.getStringValue("taskName"));
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void scheduleTask(BaseObject object, XWikiContext context) {
+		try {
+            plugin.scheduleTask(object, context);
+        } catch (PluginException e) {
+            e.printStackTrace();
+        }
+	}
+
+	public void scheduleTasks(Document document, Context context) {
+		try {
+			Vector objects = document.getObjects(SchedulerPlugin.TASK_CLASS);
+			for (Iterator iterator = objects.iterator(); iterator.hasNext();) {
+				Object object = (Object) iterator.next();
+				scheduleTask(object.getXWikiObject(), context.getContext());
+			}
+			saveDocument(document.getDocument());
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void resumeTask(String number, Document document) {
+		resumeTask(document.getObject("XWiki.Task", Integer.valueOf(number).intValue()), document);
+	}
+
+	public void resumeTask(Object object, Document document) {
+		resumeTask(object.getXWikiObject(), document.getDocument());
+	}
+
+	public void resumeTask(BaseObject object, XWikiDocument document) {
+		try {
+			plugin.resumeTask(String.valueOf(object.getNumber()));
+			saveStatus("Scheduled", object, document);
+			logger.debug("Resume Task : " + object.getStringValue("taskName"));
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void unscheduleTask(String number, Document document) {
+		unscheduleTask(document.getObject("XWiki.Task", Integer.valueOf(number).intValue()), document);
+	}
+
+	public void unscheduleTask(Object object, Document document) {
+		unscheduleTask(object.getXWikiObject(), document.getDocument());
+	}
+
+	public void unscheduleTask(BaseObject object, XWikiDocument document) {
+		try {
+			//Vector objects = document.getObjects(SchedulerPlugin.TASK_CLASS);
+			//objects.set(object.getNumber(), null);
+			// document.addObjectsToRemove(object);
+			// context.getWiki().saveDocument(document, context);
+            saveStatus("Unscheduled", object, document);
+            plugin.unscheduleTask(String.valueOf(object.getNumber()));
+			logger.debug("Delete Task : " + object.getStringValue("taskName"));
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public Date getNextFireTime(Object object) {
+		return plugin.getNextFireTime(String.valueOf(object.getNumber()));
+	}
+
+	public SchedulerPlugin getPlugin() {
+		return plugin;
+	}
+
+	private void saveStatus(String status, BaseObject object, XWikiDocument document) throws XWikiException {
+		object.setStringValue("status", status);
+		saveDocument(document);
+	}
+
+	private void saveDocument(XWikiDocument document) throws XWikiException {
+		context.getWiki().saveDocument(document, context);
+	}
+
+	public void setPlugin(SchedulerPlugin plugin) {
+		this.plugin = plugin;
+	}
+}	
\ No newline at end of file
Index: plugin/scheduler/GroovyTask.java
===================================================================
--- plugin/scheduler/GroovyTask.java	(revision 0)
+++ plugin/scheduler/GroovyTask.java	(revision 0)
@@ -0,0 +1,40 @@
+package com.xpn.xwiki.plugin.scheduler;
+
+import groovy.lang.Binding;
+import groovy.lang.GroovyShell;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+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.XWiki;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.api.Api;
+
+public class GroovyTask implements Job {
+	
+	private static Log logger = LogFactory.getLog(GroovyTask.class);
+	
+	public void execute(JobExecutionContext context) throws JobExecutionException {
+		try {
+			JobDataMap data = context.getJobDetail().getJobDataMap();
+			XWiki xwiki = (XWiki) data.get("xwiki");
+			XWikiContext xwikiContext = (XWikiContext) data.get("context");
+			int task = data.getInt("task");
+						
+			Binding binding = new Binding(data.getWrappedMap());
+			GroovyShell shell = new GroovyShell(binding);
+			BaseObject object = xwikiContext.getDoc().getObject(SchedulerPlugin.TASK_CLASS, task);
+            Api api = new Api(xwikiContext);
+            if (api.hasProgrammingRights())
+                shell.evaluate(object.getLargeStringValue("script"));
+		} catch (CompilationFailedException e) {
+			throw new JobExecutionException(e);
+		}
+	}
+}
\ No newline at end of file
Index: plugin/scheduler/StatusListener.java
===================================================================
--- plugin/scheduler/StatusListener.java	(revision 0)
+++ plugin/scheduler/StatusListener.java	(revision 0)
@@ -0,0 +1,127 @@
+/*
+ * 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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobListener;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerListener;
+import org.quartz.Trigger;
+
+/**
+ * @version $Id: $
+ */
+public class StatusListener implements SchedulerListener, JobListener {
+
+   /**
+    * Log4j logger that records events for this class
+    */	
+	private static Log log = LogFactory.getLog(StatusListener.class);
+
+    /**
+     * {@inheritDoc}
+     */
+	public void jobScheduled(Trigger trigger) {
+		log.info("Task '" + trigger.getJobName() + "' scheduled");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobUnscheduled(String name, String group) {
+		log.info("Task '" + name + "' unscheduled");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void triggerFinalized(Trigger trigger) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void triggersPaused(String trigger, String group) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void triggersResumed(String trigger, String group) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobsPaused(String name, String group) {
+		log.info("Task '" + name + "' paused");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobsResumed(String name, String group) {
+		log.info("Task '" + name + "' resumed");
+	}
+	
+	/**
+     * {@inheritDoc}
+     */
+	public void schedulerError(String message, SchedulerException error) {
+		log.error(message, error);
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void schedulerShutdown() {
+		log.warn("Scheduler is shutting down");
+	}
+	
+	/**
+     * {@inheritDoc}
+     */
+	public String getName() {
+		return "StatusListener";
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobToBeExecuted(JobExecutionContext context) {
+		log.info("Task '" + context.getJobDetail().getName() + "' is about to be executed");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobExecutionVetoed(JobExecutionContext context) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobWasExecuted(JobExecutionContext context, JobExecutionException e) {
+		log.info("Task '" + context.getJobDetail().getName() + "' executed : " + e);
+	}
+}
Index: plugin/scheduler/SchedulerPlugin.java
===================================================================
--- plugin/scheduler/SchedulerPlugin.java	(revision 0)
+++ plugin/scheduler/SchedulerPlugin.java	(revision 0)
@@ -0,0 +1,346 @@
+/*
+ * 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 java.text.ParseException;
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.CronTrigger;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.impl.StdSchedulerFactory;
+
+import com.xpn.xwiki.XWiki;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Api;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.classes.BaseClass;
+import com.xpn.xwiki.plugin.PluginException;
+import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
+import com.xpn.xwiki.plugin.XWikiPluginInterface;
+
+/**
+ * See {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin} for documentation.
+ *
+ * @version $Id: $
+ */
+public class SchedulerPlugin extends XWikiDefaultPlugin implements XWikiPluginInterface {
+	
+   /**
+    * Log4J logger object to log messages in this class.
+    */
+	private static Log logger = LogFactory.getLog(SchedulerPlugin.class);
+	
+   /**
+    * Quartz scheduler instance
+    */	
+	protected static Scheduler scheduler;
+	
+   /**
+    * Map that holds state information for Job instances.
+    */
+	private JobDataMap data = new JobDataMap();
+	
+   /**
+    * Fullname of the XWiki class representing a task that can be scheduled by this plugin
+    */
+	protected static final String TASK_CLASS = "XWiki.Task";
+	
+   /**
+	* name of the XWiki class representing a schedulable task
+    */
+	protected static final String TASK_NAME = "Task";
+	
+   /**
+    * XWiki Web space in which the schedulable task class is located
+	*/
+	protected static final String TASK_WEB = "XWiki";
+	
+	protected static final int ERROR_SCHEDULERPLUGIN_SAVE_TASK_CLASS = 0;
+	protected static final int ERROR_SCHEDULERPLUGIN_INITIALIZE_STATUS_LISTENER = 1;
+	protected static final int ERROR_SCHEDULERPLUGIN_PAUSE_TASK = 2;
+	protected static final int ERROR_SCHEDULERPLUGIN_RESUME_TASK = 3;	
+	protected static final int ERROR_SCHEDULERPLUGIN_BAD_CRON_EXPRESSION = 5;
+	protected static final int ERROR_SCHEDULERPLUGIN_SCHEDULE_TASK = 4;
+	protected static final int ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND = 6;
+	
+   /**
+    * {@inheritDoc}
+    *
+    * @see XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
+    */
+	public SchedulerPlugin(String name, String className, XWikiContext context) {
+		super(name, className, context);
+		init(context);
+	}
+
+   /**
+	* Creates the XWiki Task class if it does not exists in the wiki.
+	* Update the XWiki Task class if it already exists but needs update.
+	* 
+    * @param context the XWiki context
+    * @return the task class
+    * @throws PluginException
+    */
+    protected BaseClass getTaskClass(XWikiContext context) throws XWikiException {
+		XWikiDocument doc;
+		XWiki xwiki = context.getWiki();
+		boolean needsUpdate = false;
+
+		try {
+			doc = xwiki.getDocument(TASK_CLASS, context);
+		} catch (Exception e) {
+			doc = new XWikiDocument();
+			
+			doc.setWeb(TASK_WEB);
+			doc.setName(TASK_NAME);
+			needsUpdate = true;
+		}
+
+		BaseClass bclass = doc.getxWikiClass();
+		bclass.setName(TASK_CLASS);
+		needsUpdate |= bclass.addTextField("taskName", "Task Name", 30);
+		needsUpdate |= bclass.addTextField("taskClass", "Task Class", 30);
+		needsUpdate |= bclass.addTextField("status", "Status", 30);
+		needsUpdate |= bclass.addTextField("cron", "Cron Expression", 30);
+		needsUpdate |= bclass.addTextAreaField("script", "Groovy Script", 45, 10);
+
+		if (needsUpdate){
+			try{
+				xwiki.saveDocument(doc, context);		
+			} catch(XWikiException ex){
+				throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_SAVE_TASK_CLASS, "Error while saving " + TASK_CLASS + " class document in XWiki", ex);	
+			}
+		}
+		return bclass;
+	}
+	
+   /**
+    * Obtains the Scheduler instance 
+	* 
+    * @return the Scheduler instance
+    * @throws SchedulerException
+    */
+	private static synchronized Scheduler getSchedulerInstance() throws SchedulerException {
+		if (scheduler == null) {
+			scheduler = StdSchedulerFactory.getDefaultScheduler();
+		}
+		return scheduler;
+	}
+
+   /**
+	* Associates the scheduler with a StatusListener 
+	* 
+	* @return the Scheduler instance
+	* @throws PluginException
+	*/
+	private void setStatusListener() throws PluginException {
+		StatusListener listener = new StatusListener();
+		try{
+			scheduler.addSchedulerListener(listener);
+			scheduler.addGlobalJobListener(listener);
+		} catch (SchedulerException ex){
+			throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_INITIALIZE_STATUS_LISTENER , "Error while initializing the StatusListener", ex);
+		}
+	}
+
+   /**
+    * {@inheritDoc}
+    * 
+    * Initialization method for the SchedulerPlugin
+    * 
+    * @param context the XWikiContext needed to create/update the XWiki Task class
+    */	
+	public void init(XWikiContext context){
+		super.init(context);
+		try {
+			getTaskClass(context);
+			scheduler = getSchedulerInstance();
+			setStatusListener();
+			scheduler.start();
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		} catch (SchedulerException e) {
+			e.printStackTrace();
+		}
+	}
+		
+   /**
+    * Pause the task with the given name by pausing all of its current triggers.
+    * 
+    * @param taskName the name of the task to be paused
+    * @throws PluginException 
+    */
+	public void pauseTask(String taskName) throws PluginException {
+		try {
+			scheduler.pauseJob(taskName, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_PAUSE_TASK , "Error occured while trying to pause task " + taskName, e);
+		}
+	}
+
+   /**
+    * Resume the task with the given name (un-pause)
+    * 
+    * @param taskName the name of the task to be paused
+    * @throws PluginException 
+    */
+	public void resumeTask(String taskName) throws PluginException {
+		try {
+			scheduler.resumeJob(taskName, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_RESUME_TASK , "Error occured while trying to resume task " + taskName, e);
+		}
+	}
+	
+   /**
+    * Schedule the given task by creating a job and associating a cron trigger with it
+    * 
+    * @param object the XWiki task object
+    * @param context the XWiki context 
+    * @throws PluginException 
+    * @todo check if the task is actually scheduled
+    */
+	public boolean scheduleTask(BaseObject object, XWikiContext context) throws PluginException {
+		boolean scheduled = true;
+		try {
+			String task = String.valueOf(object.getNumber());
+			
+			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, Scheduler.DEFAULT_GROUP, object.getStringValue("cron"));
+			
+			data.put("task", object.getNumber());
+			data.put("context", context);
+			data.put("xwiki", context.getWiki());
+
+            job.setJobDataMap(data);
+			
+			scheduler.addJob(job, true);
+			int state = scheduler.getTriggerState(task, Scheduler.DEFAULT_GROUP);
+			switch (state) {
+				case Trigger.STATE_PAUSED:
+					object.setStringValue("status", "Paused");
+					break;
+				case Trigger.STATE_NORMAL:
+					if (getTrigger(task).compareTo(trigger) != 0)
+						logger.debug("Reschedule Task : " + object.getStringValue("taskName"));
+						scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
+					object.setStringValue("status", "Scheduled");
+					break;
+				case Trigger.STATE_NONE:
+					logger.debug("Schedule Task : " + object.getStringValue("taskName"));
+					scheduler.scheduleJob(trigger);
+					logger.info("XWiki Task Status :"+ object.getStringValue("status"));
+					if(object.getStringValue("status").equals("Paused")) {
+						scheduler.pauseJob(task, Scheduler.DEFAULT_GROUP);
+						object.setStringValue("status", "Paused");
+					} else {
+						object.setStringValue("status", "Scheduled");
+					}
+					break;
+				default:
+					logger.debug("Schedule Task : " + object.getStringValue("taskName"));
+					scheduler.scheduleJob(trigger);
+					object.setStringValue("status", "Scheduled");
+					break;
+			}
+		} catch (SchedulerException e) {
+            throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_SCHEDULE_TASK , "Error while scheduling task " + object.getStringValue("taskName"), e);
+		} catch (ParseException e) {
+            throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_BAD_CRON_EXPRESSION , "Error while parsing cron expression for task " + object.getStringValue("taskName"), e);
+		} catch (ClassNotFoundException e) {
+            throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND , "Error while loading task class for task : " + object.getStringValue("taskName") + "\n", e);
+		}
+		return scheduled;
+	}
+
+   /**
+    * Unschedule the given task
+    * 
+    * @param taskName the task name for the task to be unscheduled
+    */	
+	public void unscheduleTask(String taskName) {
+		try {
+			scheduler.deleteJob(taskName, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			e.printStackTrace();
+		}
+	}
+
+   /**
+    * Get Trigger object of the given task
+    * 
+    * @param task the task name for which the trigger will be given
+    * @return the trigger object of the given task
+    */	
+	public Trigger getTrigger(String task) {
+		Trigger trigger = null;
+		try {
+			trigger = scheduler.getTrigger(task, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			e.printStackTrace();
+		}
+		return trigger;
+	}
+	
+   /**
+    * Get the next fire time for the given task name Task
+    * 
+    * @param task the task name for which the next fire time will be given
+    * @return the next Date the task will be fired at
+    */		
+	public Date getNextFireTime(String task) {
+		return getTrigger(task).getNextFireTime();
+	}
+	
+   /**
+    * {@inheritDoc}
+    */	
+	public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
+        return new SchedulerPluginApi((SchedulerPlugin) plugin, context);
+    }
+
+   /**
+    * {@inheritDoc}
+    */		
+	public String getName() {
+        return "scheduler";
+    }
+	
+   /**
+    * {@inheritDoc}
+    */		
+	public void flushCache() {
+    }
+
+   /**
+    * {@inheritDoc}
+    */		
+	public void virtualInit(XWikiContext context) {
+		init(context);
+    }	
+}
\ No newline at end of file
Index: plugin/scheduler/GroovyTask.java
===================================================================
--- plugin/scheduler/GroovyTask.java	(revision 0)
+++ plugin/scheduler/GroovyTask.java	(revision 0)
@@ -0,0 +1,40 @@
+package com.xpn.xwiki.plugin.scheduler;
+
+import groovy.lang.Binding;
+import groovy.lang.GroovyShell;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+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.XWiki;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.api.Api;
+
+public class GroovyTask implements Job {
+	
+	private static Log logger = LogFactory.getLog(GroovyTask.class);
+	
+	public void execute(JobExecutionContext context) throws JobExecutionException {
+		try {
+			JobDataMap data = context.getJobDetail().getJobDataMap();
+			XWiki xwiki = (XWiki) data.get("xwiki");
+			XWikiContext xwikiContext = (XWikiContext) data.get("context");
+			int task = data.getInt("task");
+						
+			Binding binding = new Binding(data.getWrappedMap());
+			GroovyShell shell = new GroovyShell(binding);
+			BaseObject object = xwikiContext.getDoc().getObject(SchedulerPlugin.TASK_CLASS, task);
+            Api api = new Api(xwikiContext);
+            if (api.hasProgrammingRights())
+                shell.evaluate(object.getLargeStringValue("script"));
+		} catch (CompilationFailedException e) {
+			throw new JobExecutionException(e);
+		}
+	}
+}
\ No newline at end of file
Index: plugin/scheduler/SchedulerPlugin.java
===================================================================
--- plugin/scheduler/SchedulerPlugin.java	(revision 0)
+++ plugin/scheduler/SchedulerPlugin.java	(revision 0)
@@ -0,0 +1,346 @@
+/*
+ * 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 java.text.ParseException;
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.CronTrigger;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.impl.StdSchedulerFactory;
+
+import com.xpn.xwiki.XWiki;
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Api;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.classes.BaseClass;
+import com.xpn.xwiki.plugin.PluginException;
+import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
+import com.xpn.xwiki.plugin.XWikiPluginInterface;
+
+/**
+ * See {@link com.xpn.xwiki.plugin.scheduler.SchedulerPlugin} for documentation.
+ *
+ * @version $Id: $
+ */
+public class SchedulerPlugin extends XWikiDefaultPlugin implements XWikiPluginInterface {
+	
+   /**
+    * Log4J logger object to log messages in this class.
+    */
+	private static Log logger = LogFactory.getLog(SchedulerPlugin.class);
+	
+   /**
+    * Quartz scheduler instance
+    */	
+	protected static Scheduler scheduler;
+	
+   /**
+    * Map that holds state information for Job instances.
+    */
+	private JobDataMap data = new JobDataMap();
+	
+   /**
+    * Fullname of the XWiki class representing a task that can be scheduled by this plugin
+    */
+	protected static final String TASK_CLASS = "XWiki.Task";
+	
+   /**
+	* name of the XWiki class representing a schedulable task
+    */
+	protected static final String TASK_NAME = "Task";
+	
+   /**
+    * XWiki Web space in which the schedulable task class is located
+	*/
+	protected static final String TASK_WEB = "XWiki";
+	
+	protected static final int ERROR_SCHEDULERPLUGIN_SAVE_TASK_CLASS = 0;
+	protected static final int ERROR_SCHEDULERPLUGIN_INITIALIZE_STATUS_LISTENER = 1;
+	protected static final int ERROR_SCHEDULERPLUGIN_PAUSE_TASK = 2;
+	protected static final int ERROR_SCHEDULERPLUGIN_RESUME_TASK = 3;	
+	protected static final int ERROR_SCHEDULERPLUGIN_BAD_CRON_EXPRESSION = 5;
+	protected static final int ERROR_SCHEDULERPLUGIN_SCHEDULE_TASK = 4;
+	protected static final int ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND = 6;
+	
+   /**
+    * {@inheritDoc}
+    *
+    * @see XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
+    */
+	public SchedulerPlugin(String name, String className, XWikiContext context) {
+		super(name, className, context);
+		init(context);
+	}
+
+   /**
+	* Creates the XWiki Task class if it does not exists in the wiki.
+	* Update the XWiki Task class if it already exists but needs update.
+	* 
+    * @param context the XWiki context
+    * @return the task class
+    * @throws PluginException
+    */
+    protected BaseClass getTaskClass(XWikiContext context) throws XWikiException {
+		XWikiDocument doc;
+		XWiki xwiki = context.getWiki();
+		boolean needsUpdate = false;
+
+		try {
+			doc = xwiki.getDocument(TASK_CLASS, context);
+		} catch (Exception e) {
+			doc = new XWikiDocument();
+			
+			doc.setWeb(TASK_WEB);
+			doc.setName(TASK_NAME);
+			needsUpdate = true;
+		}
+
+		BaseClass bclass = doc.getxWikiClass();
+		bclass.setName(TASK_CLASS);
+		needsUpdate |= bclass.addTextField("taskName", "Task Name", 30);
+		needsUpdate |= bclass.addTextField("taskClass", "Task Class", 30);
+		needsUpdate |= bclass.addTextField("status", "Status", 30);
+		needsUpdate |= bclass.addTextField("cron", "Cron Expression", 30);
+		needsUpdate |= bclass.addTextAreaField("script", "Groovy Script", 45, 10);
+
+		if (needsUpdate){
+			try{
+				xwiki.saveDocument(doc, context);		
+			} catch(XWikiException ex){
+				throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_SAVE_TASK_CLASS, "Error while saving " + TASK_CLASS + " class document in XWiki", ex);	
+			}
+		}
+		return bclass;
+	}
+	
+   /**
+    * Obtains the Scheduler instance 
+	* 
+    * @return the Scheduler instance
+    * @throws SchedulerException
+    */
+	private static synchronized Scheduler getSchedulerInstance() throws SchedulerException {
+		if (scheduler == null) {
+			scheduler = StdSchedulerFactory.getDefaultScheduler();
+		}
+		return scheduler;
+	}
+
+   /**
+	* Associates the scheduler with a StatusListener 
+	* 
+	* @return the Scheduler instance
+	* @throws PluginException
+	*/
+	private void setStatusListener() throws PluginException {
+		StatusListener listener = new StatusListener();
+		try{
+			scheduler.addSchedulerListener(listener);
+			scheduler.addGlobalJobListener(listener);
+		} catch (SchedulerException ex){
+			throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_INITIALIZE_STATUS_LISTENER , "Error while initializing the StatusListener", ex);
+		}
+	}
+
+   /**
+    * {@inheritDoc}
+    * 
+    * Initialization method for the SchedulerPlugin
+    * 
+    * @param context the XWikiContext needed to create/update the XWiki Task class
+    */	
+	public void init(XWikiContext context){
+		super.init(context);
+		try {
+			getTaskClass(context);
+			scheduler = getSchedulerInstance();
+			setStatusListener();
+			scheduler.start();
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		} catch (SchedulerException e) {
+			e.printStackTrace();
+		}
+	}
+		
+   /**
+    * Pause the task with the given name by pausing all of its current triggers.
+    * 
+    * @param taskName the name of the task to be paused
+    * @throws PluginException 
+    */
+	public void pauseTask(String taskName) throws PluginException {
+		try {
+			scheduler.pauseJob(taskName, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_PAUSE_TASK , "Error occured while trying to pause task " + taskName, e);
+		}
+	}
+
+   /**
+    * Resume the task with the given name (un-pause)
+    * 
+    * @param taskName the name of the task to be paused
+    * @throws PluginException 
+    */
+	public void resumeTask(String taskName) throws PluginException {
+		try {
+			scheduler.resumeJob(taskName, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_RESUME_TASK , "Error occured while trying to resume task " + taskName, e);
+		}
+	}
+	
+   /**
+    * Schedule the given task by creating a job and associating a cron trigger with it
+    * 
+    * @param object the XWiki task object
+    * @param context the XWiki context 
+    * @throws PluginException 
+    * @todo check if the task is actually scheduled
+    */
+	public boolean scheduleTask(BaseObject object, XWikiContext context) throws PluginException {
+		boolean scheduled = true;
+		try {
+			String task = String.valueOf(object.getNumber());
+			
+			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, Scheduler.DEFAULT_GROUP, object.getStringValue("cron"));
+			
+			data.put("task", object.getNumber());
+			data.put("context", context);
+			data.put("xwiki", context.getWiki());
+
+            job.setJobDataMap(data);
+			
+			scheduler.addJob(job, true);
+			int state = scheduler.getTriggerState(task, Scheduler.DEFAULT_GROUP);
+			switch (state) {
+				case Trigger.STATE_PAUSED:
+					object.setStringValue("status", "Paused");
+					break;
+				case Trigger.STATE_NORMAL:
+					if (getTrigger(task).compareTo(trigger) != 0)
+						logger.debug("Reschedule Task : " + object.getStringValue("taskName"));
+						scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
+					object.setStringValue("status", "Scheduled");
+					break;
+				case Trigger.STATE_NONE:
+					logger.debug("Schedule Task : " + object.getStringValue("taskName"));
+					scheduler.scheduleJob(trigger);
+					logger.info("XWiki Task Status :"+ object.getStringValue("status"));
+					if(object.getStringValue("status").equals("Paused")) {
+						scheduler.pauseJob(task, Scheduler.DEFAULT_GROUP);
+						object.setStringValue("status", "Paused");
+					} else {
+						object.setStringValue("status", "Scheduled");
+					}
+					break;
+				default:
+					logger.debug("Schedule Task : " + object.getStringValue("taskName"));
+					scheduler.scheduleJob(trigger);
+					object.setStringValue("status", "Scheduled");
+					break;
+			}
+		} catch (SchedulerException e) {
+            throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_SCHEDULE_TASK , "Error while scheduling task " + object.getStringValue("taskName"), e);
+		} catch (ParseException e) {
+            throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_BAD_CRON_EXPRESSION , "Error while parsing cron expression for task " + object.getStringValue("taskName"), e);
+		} catch (ClassNotFoundException e) {
+            throw new PluginException(SchedulerPlugin.class, ERROR_SCHEDULERPLUGIN_TASK_CLASS_NOT_FOUND , "Error while loading task class for task : " + object.getStringValue("taskName") + "\n", e);
+		}
+		return scheduled;
+	}
+
+   /**
+    * Unschedule the given task
+    * 
+    * @param taskName the task name for the task to be unscheduled
+    */	
+	public void unscheduleTask(String taskName) {
+		try {
+			scheduler.deleteJob(taskName, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			e.printStackTrace();
+		}
+	}
+
+   /**
+    * Get Trigger object of the given task
+    * 
+    * @param task the task name for which the trigger will be given
+    * @return the trigger object of the given task
+    */	
+	public Trigger getTrigger(String task) {
+		Trigger trigger = null;
+		try {
+			trigger = scheduler.getTrigger(task, Scheduler.DEFAULT_GROUP);
+		} catch (SchedulerException e) {
+			e.printStackTrace();
+		}
+		return trigger;
+	}
+	
+   /**
+    * Get the next fire time for the given task name Task
+    * 
+    * @param task the task name for which the next fire time will be given
+    * @return the next Date the task will be fired at
+    */		
+	public Date getNextFireTime(String task) {
+		return getTrigger(task).getNextFireTime();
+	}
+	
+   /**
+    * {@inheritDoc}
+    */	
+	public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
+        return new SchedulerPluginApi((SchedulerPlugin) plugin, context);
+    }
+
+   /**
+    * {@inheritDoc}
+    */		
+	public String getName() {
+        return "scheduler";
+    }
+	
+   /**
+    * {@inheritDoc}
+    */		
+	public void flushCache() {
+    }
+
+   /**
+    * {@inheritDoc}
+    */		
+	public void virtualInit(XWikiContext context) {
+		init(context);
+    }	
+}
\ No newline at end of file
Index: plugin/scheduler/SchedulerPluginApi.java
===================================================================
--- plugin/scheduler/SchedulerPluginApi.java	(revision 0)
+++ plugin/scheduler/SchedulerPluginApi.java	(revision 0)
@@ -0,0 +1,134 @@
+package com.xpn.xwiki.plugin.scheduler;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.api.Api;
+import com.xpn.xwiki.api.Context;
+import com.xpn.xwiki.api.Document;
+import com.xpn.xwiki.api.Object;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.plugin.PluginException;
+
+public class SchedulerPluginApi extends Api {
+	
+	private static Log logger = LogFactory.getLog(SchedulerPluginApi.class);
+
+	private SchedulerPlugin plugin;
+
+	public SchedulerPluginApi(SchedulerPlugin plugin, XWikiContext context) {
+		super(context);
+		init(plugin, context);
+	}
+
+	private void init(SchedulerPlugin plugin, XWikiContext context) {
+		setPlugin(plugin);
+	}
+
+	public void pauseTask(String number, Document document) {
+		pauseTask(document.getObject("XWiki.Task", Integer.valueOf(number).intValue()), document);
+	}
+
+	public void pauseTask(Object object, Document document) {
+		pauseTask(object.getXWikiObject(), document.getDocument());
+	}
+
+	public void pauseTask(BaseObject object, XWikiDocument document) {
+		try {
+			plugin.pauseTask(String.valueOf(object.getNumber()));
+			saveStatus("Paused", object, document);
+			logger.debug("Pause Task : " + object.getStringValue("taskName"));
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void scheduleTask(BaseObject object, XWikiContext context) {
+		try {
+            plugin.scheduleTask(object, context);
+        } catch (PluginException e) {
+            e.printStackTrace();
+        }
+	}
+
+	public void scheduleTasks(Document document, Context context) {
+		try {
+			Vector objects = document.getObjects(SchedulerPlugin.TASK_CLASS);
+			for (Iterator iterator = objects.iterator(); iterator.hasNext();) {
+				Object object = (Object) iterator.next();
+				scheduleTask(object.getXWikiObject(), context.getContext());
+			}
+			saveDocument(document.getDocument());
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void resumeTask(String number, Document document) {
+		resumeTask(document.getObject("XWiki.Task", Integer.valueOf(number).intValue()), document);
+	}
+
+	public void resumeTask(Object object, Document document) {
+		resumeTask(object.getXWikiObject(), document.getDocument());
+	}
+
+	public void resumeTask(BaseObject object, XWikiDocument document) {
+		try {
+			plugin.resumeTask(String.valueOf(object.getNumber()));
+			saveStatus("Scheduled", object, document);
+			logger.debug("Resume Task : " + object.getStringValue("taskName"));
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public void unscheduleTask(String number, Document document) {
+		unscheduleTask(document.getObject("XWiki.Task", Integer.valueOf(number).intValue()), document);
+	}
+
+	public void unscheduleTask(Object object, Document document) {
+		unscheduleTask(object.getXWikiObject(), document.getDocument());
+	}
+
+	public void unscheduleTask(BaseObject object, XWikiDocument document) {
+		try {
+			//Vector objects = document.getObjects(SchedulerPlugin.TASK_CLASS);
+			//objects.set(object.getNumber(), null);
+			// document.addObjectsToRemove(object);
+			// context.getWiki().saveDocument(document, context);
+            saveStatus("Unscheduled", object, document);
+            plugin.unscheduleTask(String.valueOf(object.getNumber()));
+			logger.debug("Delete Task : " + object.getStringValue("taskName"));
+		} catch (XWikiException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public Date getNextFireTime(Object object) {
+		return plugin.getNextFireTime(String.valueOf(object.getNumber()));
+	}
+
+	public SchedulerPlugin getPlugin() {
+		return plugin;
+	}
+
+	private void saveStatus(String status, BaseObject object, XWikiDocument document) throws XWikiException {
+		object.setStringValue("status", status);
+		saveDocument(document);
+	}
+
+	private void saveDocument(XWikiDocument document) throws XWikiException {
+		context.getWiki().saveDocument(document, context);
+	}
+
+	public void setPlugin(SchedulerPlugin plugin) {
+		this.plugin = plugin;
+	}
+}	
\ No newline at end of file
Index: plugin/scheduler/StatusListener.java
===================================================================
--- plugin/scheduler/StatusListener.java	(revision 0)
+++ plugin/scheduler/StatusListener.java	(revision 0)
@@ -0,0 +1,127 @@
+/*
+ * 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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobListener;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerListener;
+import org.quartz.Trigger;
+
+/**
+ * @version $Id: $
+ */
+public class StatusListener implements SchedulerListener, JobListener {
+
+   /**
+    * Log4j logger that records events for this class
+    */	
+	private static Log log = LogFactory.getLog(StatusListener.class);
+
+    /**
+     * {@inheritDoc}
+     */
+	public void jobScheduled(Trigger trigger) {
+		log.info("Task '" + trigger.getJobName() + "' scheduled");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobUnscheduled(String name, String group) {
+		log.info("Task '" + name + "' unscheduled");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void triggerFinalized(Trigger trigger) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void triggersPaused(String trigger, String group) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void triggersResumed(String trigger, String group) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobsPaused(String name, String group) {
+		log.info("Task '" + name + "' paused");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobsResumed(String name, String group) {
+		log.info("Task '" + name + "' resumed");
+	}
+	
+	/**
+     * {@inheritDoc}
+     */
+	public void schedulerError(String message, SchedulerException error) {
+		log.error(message, error);
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void schedulerShutdown() {
+		log.warn("Scheduler is shutting down");
+	}
+	
+	/**
+     * {@inheritDoc}
+     */
+	public String getName() {
+		return "StatusListener";
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobToBeExecuted(JobExecutionContext context) {
+		log.info("Task '" + context.getJobDetail().getName() + "' is about to be executed");
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobExecutionVetoed(JobExecutionContext context) {
+	}
+
+	/**
+     * {@inheritDoc}
+     */
+	public void jobWasExecuted(JobExecutionContext context, JobExecutionException e) {
+		log.info("Task '" + context.getJobDetail().getName() + "' executed : " + e);
+	}
+}
