mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 06:53:09 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
154
dspace-api/src/main/java/org/dspace/curate/TaskResolver.java
Normal file
154
dspace-api/src/main/java/org/dspace/curate/TaskResolver.java
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user