DS-939: Enhancements to Curation Framework (Site-wide Tasks & Associated EPerson with Tasks)

This first patch enhances the org.dspace.curate.* classes to support the idea of Site-wide Tasks & associating a "task performer" (Eperson) with a running task.

git-svn-id: http://scm.dspace.org/svn/repo/dspace/trunk@6517 9c30dcfa-912a-0410-8fc2-9e0234be79fd
This commit is contained in:
Tim Donohue
2011-08-02 16:54:45 +00:00
parent e3250b8ac6
commit 9735249d53
2 changed files with 294 additions and 4 deletions

View File

@@ -20,9 +20,11 @@ import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.ItemIterator;
import org.dspace.content.Site;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.PluginManager;
import org.dspace.eperson.EPerson;
import org.dspace.handle.HandleManager;
/**
@@ -53,6 +55,8 @@ public class Curator
private static Logger log = Logger.getLogger(Curator.class);
private static final ThreadLocal<Integer> performer = new ThreadLocal<Integer>();
private Map<String, TaskRunner> trMap = new HashMap<String, TaskRunner>();
private List<String> perfList = new ArrayList<String>();
private TaskQueue taskQ = null;
@@ -75,7 +79,7 @@ public class Curator
*/
public Curator addTask(String taskName)
{
CurationTask task = (CurationTask)PluginManager.getNamedPlugin("curate", CurationTask.class, taskName);
CurationTask task = TaskResolver.resolveTask(taskName);
if (task != null)
{
try
@@ -160,11 +164,17 @@ public class Curator
{
if (id == null)
{
log.error("curate - null id");
return;
throw new IOException("Cannot perform curation task(s) on a null object identifier!");
}
try
{
//Save the currently authenticated user's ID to the current Task thread
//(Allows individual tasks to retrieve current user info via currentPerformer() method)
if(c.getCurrentUser()!=null)
{
performer.set(Integer.valueOf(c.getCurrentUser().getID()));
}
DSpaceObject dso = HandleManager.resolveToObject(c, id);
if (dso != null)
{
@@ -182,10 +192,20 @@ public class Curator
{
throw new IOException(sqlE.getMessage(), sqlE);
}
finally
{
performer.remove();
}
}
/**
* Performs all configured tasks upon DSpace object
* (Community, Collection or Item).
* <P>
* Note: Site-wide tasks will default to running as
* an Anonymous User unless you call the Site-wide task
* via the 'curate(Context,String)' method with an
* authenticated Context object.
*
* @param dso the DSpace object
* @throws IOException
@@ -214,6 +234,10 @@ public class Curator
{
doCommunity(tr, (Community)dso);
}
else if (type == Constants.SITE)
{
doSite(tr, (Site) dso);
}
}
}
@@ -306,6 +330,60 @@ public class Curator
}
}
/**
* Returns the Eperson ID of the Context currently in use in performing a task,
* if known, else <code>null</code>.
* <P>
* In many circumstances, this value will be null:
* when the curator is not in the perform method, when curation
* is invoked with a DSO (the context is 'hidden').
* <P>
* The primary intended use for this method is to ensure individual tasks,
* which may need to create a new Context, can also properly initialize that
* Context with an EPerson ID (to ensure proper access rights exist in that Context).
* <P>
* Current performer information is also used when executing Site-Wide tasks
* (see Curator.doSite() method).
*/
public static Integer currentPerformer()
{
return performer.get();
}
/**
* Returns a Context object which is "authenticated" as the current
* EPerson performer (see 'currentPerformer()' method). This is primarily a
* utility method to allow tasks access to an authenticated Context when
* necessary.
* <P>
* If the 'currentPerformer()' is null or not set, then this just returns
* a brand new Context object representing an Anonymous User.
*
* @return authenticated Context object (or anonymous Context if currentPerformer() is null)
*/
public static Context authenticatedContext()
throws SQLException
{
//Create a new context
Context ctx = new Context();
Integer epersonID = currentPerformer();
//If a Curator 'performer' ID is set
if(epersonID!=null)
{
//parse the performer's User ID & set as the currently authenticated user in Context
EPerson autenticatedUser = EPerson.find(ctx, epersonID.intValue());
ctx.setCurrentUser(autenticatedUser);
}
else
{
//otherwise, no-op. This is the equivalent of an ANONYMOUS USER Context
}
return ctx;
}
/**
* Returns whether a given DSO is a 'container' - collection or community
* @param dso a DSpace object
@@ -317,6 +395,63 @@ public class Curator
dso.getType() == Constants.COLLECTION);
}
/**
* Run task for entire Site (including all Communities, Collections & Items)
* @param tr TaskRunner
* @param site DSpace Site object
* @return true if successful, false otherwise
* @throws IOException
*/
private boolean doSite(TaskRunner tr, Site site) throws IOException
{
Context ctx = null;
try
{
// Site-wide Tasks really should have an EPerson performer associated with them,
// otherwise they are run as an "anonymous" user with limited access rights.
if(Curator.currentPerformer()==null)
{
log.warn("You are running one or more Site-Wide curation tasks in ANONYMOUS USER mode," +
" as there is no EPerson 'performer' associated with this task. To associate an EPerson 'performer' " +
" you should ensure tasks are called via the Curator.curate(Context, ID) method.");
}
else
{
// Create a new Context for this Sitewide task, authenticated as the current task performer.
ctx = Curator.authenticatedContext();
}
//Run task for the Site object itself
if (! tr.run(site))
{
return false;
}
//Then, perform this task for all Top-Level Communities in the Site
// (this will recursively perform task for all objects in DSpace)
for (Community subcomm : Community.findAllTop(ctx))
{
if (! doCommunity(tr, subcomm))
{
return false;
}
}
//complete & close our created Context
ctx.complete();
}
catch (SQLException sqlE)
{
//abort Context & all changes
if(ctx!=null)
ctx.abort();
throw new IOException(sqlE);
}
return true;
}
/**
* Run task for Community along with all sub-communities and collections.
* @param tr TaskRunner
@@ -410,7 +545,8 @@ public class Curator
throw new IOException("DSpaceObject is null");
}
statusCode = task.perform(dso);
log.info(logMessage(dso.getHandle()));
String id = (dso.getHandle() != null) ? dso.getHandle() : "workflow item: " + dso.getID();
log.info(logMessage(id));
return ! suspend(statusCode);
}

View File

@@ -0,0 +1,154 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.curate;
import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.log4j.Logger;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.PluginManager;
/**
* TaskResolver takes a logical name of a curation task and delivers a
* suitable implementation object. Supported implementation types include:
* (1) Classpath-local Java classes configured and loaded via PluginManager.
* (2) Local script-based tasks, viz. coded in any scripting language whose
* runtimes are accessible via the JSR-223 scripting API. This really amounts
* to the family of dynamic JVM languages: JRuby, Jython, Groovy, Javascript, etc
* Note that the requisite jars and other resources for these languages must be
* installed in the DSpace instance for them to be used here.
* Further work may involve remote URL-loadable code, etc.
*
* Scripted tasks are configured in dspace/config/modules/curate.cfg with the
* property "script.tasks" with value syntax:
* <task-desc> = taskName,
* <task-desc> = taskName
* where task-desc is a descriptor of the form:
* <engine>:<relfilePath>:<implClassName>
* An example property value:
*
* ruby:rubytask.rb:LinkChecker = linkchecker
*
* This descriptor means that the 'ruby' script engine will be created,
* a script file named 'rubytask.rb' in the directory <taskbase>/ruby/rubtask.rb will be loaded
* and the resolver will expect that a class 'LinkChecker' will be defined in that script file.
*
* @author richardrodgers
*/
public class TaskResolver
{
// logging service
private static Logger log = Logger.getLogger(TaskResolver.class);
// base directory of task scripts
private static String scriptDir = ConfigurationManager.getProperty("curate", "script.dir");
// map of task script descriptions, keyed by logical task name
private static Map<String, String> scriptMap = new HashMap<String, String>();
static
{
// build map of task descriptors
loadDescriptors();
}
private TaskResolver()
{
}
/**
* Loads the map of script descriptors
*/
public static void loadDescriptors()
{
scriptMap.clear();
String propVal = ConfigurationManager.getProperty("curate", "script.tasks");
if (propVal != null)
{
for (String desc : propVal.split(","))
{
String[] parts = desc.split("=");
scriptMap.put(parts[1].trim(), parts[0].trim());
}
}
}
/**
* Returns a task implementation for a given task name,
* or <code>null</code> if no implementation could be obtained.
*/
public static CurationTask resolveTask(String taskName)
{
CurationTask task = (CurationTask)PluginManager.getNamedPlugin("curate", CurationTask.class, taskName);
if (task == null)
{
// maybe it is implemented by a script?
String scriptDesc = scriptMap.get(taskName);
if (scriptDesc != null)
{
String[] descParts = scriptDesc.split(":");
// first descriptor token is name ('alias') of scripting engine,
// which is also the subdirectory where script file kept
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName(descParts[0]);
if (engine != null)
{
// see if we can locate the script file and load it
// the second token is the relative path to the file
File script = new File(scriptDir, descParts[1]);
if (script.exists())
{
try
{
Reader reader = new FileReader(script);
engine.eval(reader);
reader.close();
// third token is name of class implementing
// CurationTask interface - add ".new" to ask for an instance
String implInst = descParts[2] + ".new";
task = (CurationTask)engine.eval(implInst);
}
catch (FileNotFoundException fnfE)
{
log.error("Script: '" + script.getName() + "' not found for task: " + taskName);
}
catch (IOException ioE)
{
log.error("Error loading script: '" + script.getName() + "'");
}
catch (ScriptException scE)
{
log.error("Error evaluating script: '" + script.getName() + "' msg: " + scE.getMessage());
}
}
else
{
log.error("No script: '" + script.getName() + "' found for task: " + taskName);
}
}
else
{
log.error("Script engine: '" + descParts[0] + "' is not installed");
}
}
}
return task;
}
}