- findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
+ throws SQLException;
+
/**
* Retrieve the list of items submitted by eperson, ordered by recently submitted, optionally limitable
*
diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java
index ecfc29d29d..e878367ec4 100644
--- a/dspace-api/src/main/java/org/dspace/core/Context.java
+++ b/dspace-api/src/main/java/org/dspace/core/Context.java
@@ -179,7 +179,7 @@ public class Context implements AutoCloseable {
}
currentUser = null;
- currentLocale = I18nUtil.DEFAULTLOCALE;
+ currentLocale = I18nUtil.getDefaultLocale();
extraLogInfo = "";
ignoreAuth = false;
@@ -190,7 +190,15 @@ public class Context implements AutoCloseable {
setMode(this.mode);
}
- public static boolean updateDatabase() {
+ /**
+ * Update the DSpace database, ensuring that any necessary migrations are run prior to initializing
+ * Hibernate.
+ *
+ * This is synchronized as it only needs to be run successfully *once* (for the first Context initialized).
+ *
+ * @return true/false, based on whether database was successfully updated
+ */
+ public static synchronized boolean updateDatabase() {
//If the database has not been updated yet, update it and remember that.
if (databaseUpdated.compareAndSet(false, true)) {
@@ -200,7 +208,7 @@ public class Context implements AutoCloseable {
try {
DatabaseUtils.updateDatabase();
} catch (SQLException sqle) {
- log.fatal("Cannot initialize database via Flyway!", sqle);
+ log.fatal("Cannot update or initialize database via Flyway!", sqle);
databaseUpdated.set(false);
}
}
@@ -641,9 +649,9 @@ public class Context implements AutoCloseable {
/**
* Temporary change the user bound to the context, empty the special groups that
* are retained to allow subsequent restore
- *
+ *
* @param newUser the EPerson to bound to the context
- *
+ *
* @throws IllegalStateException if the switch was already performed without be
* restored
*/
@@ -661,7 +669,7 @@ public class Context implements AutoCloseable {
/**
* Restore the user bound to the context and his special groups
- *
+ *
* @throws IllegalStateException if no switch was performed before
*/
public void restoreContextUser() {
@@ -876,4 +884,5 @@ public class Context implements AutoCloseable {
private void reloadContextBoundEntities() throws SQLException {
currentUser = reloadEntity(currentUser);
}
+
}
diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java
index 37e48c4a4f..cd0609e29f 100644
--- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java
+++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java
@@ -37,9 +37,6 @@ import org.dspace.services.factory.DSpaceServicesFactory;
public class I18nUtil {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(I18nUtil.class);
- // the default Locale of this DSpace Instance
- public static final Locale DEFAULTLOCALE = getDefaultLocale();
-
// delimiters between elements of UNIX/POSIX locale spec, e.g. en_US.UTF-8
private static final String LOCALE_DELIMITERS = " _.";
@@ -127,7 +124,7 @@ public class I18nUtil {
return parseLocales(locales);
} else {
Locale[] availableLocales = new Locale[1];
- availableLocales[0] = DEFAULTLOCALE;
+ availableLocales[0] = getDefaultLocale();
return availableLocales;
}
}
@@ -148,7 +145,7 @@ public class I18nUtil {
Locale supportedLocale = null;
String testLocale = "";
if (availableLocales == null) {
- supportedLocale = DEFAULTLOCALE;
+ supportedLocale = getDefaultLocale();
} else {
if (!locale.getVariant().equals("")) {
testLocale = locale.toString();
@@ -188,12 +185,29 @@ public class I18nUtil {
}
}
if (!isSupported) {
- supportedLocale = DEFAULTLOCALE;
+ supportedLocale = getDefaultLocale();
}
}
return supportedLocale;
}
+ /**
+ * Gets the appropriate supported Locale according for a given Locale If
+ * no appropriate supported locale is found, the DEFAULTLOCALE is used
+ *
+ * @param locale String to find the corresponding Locale
+ * @return supportedLocale
+ * Locale for session according to locales supported by this DSpace instance as set in dspace.cfg
+ */
+ public static Locale getSupportedLocale(String locale) {
+ Locale currentLocale = null;
+ if (locale != null) {
+ currentLocale = I18nUtil.getSupportedLocale(new Locale(locale));
+ } else {
+ currentLocale = I18nUtil.getDefaultLocale();
+ }
+ return currentLocale;
+ }
/**
* Get the appropriate localized version of submission-forms.xml according to language settings
@@ -220,7 +234,7 @@ public class I18nUtil {
* String of the message
*/
public static String getMessage(String key) {
- return getMessage(key.trim(), DEFAULTLOCALE);
+ return getMessage(key.trim(), getDefaultLocale());
}
/**
@@ -233,7 +247,7 @@ public class I18nUtil {
*/
public static String getMessage(String key, Locale locale) {
if (locale == null) {
- locale = DEFAULTLOCALE;
+ locale = getDefaultLocale();
}
ResourceBundle.Control control =
ResourceBundle.Control.getNoFallbackControl(
@@ -384,4 +398,23 @@ public class I18nUtil {
}
return resultList.toArray(new Locale[resultList.size()]);
}
+
+ /**
+ * Check if the input locale is in the list of supported locales
+ * @param locale
+ * @return true if locale is supported, false otherwise
+ */
+ public static boolean isSupportedLocale(Locale locale) {
+ boolean isSupported = false;
+ Locale[] supportedLocales = getSupportedLocales();
+ if (supportedLocales != null) {
+ for (Locale sLocale: supportedLocales) {
+ if (locale.getLanguage().equals(sLocale.getLanguage()) ) {
+ isSupported = true;
+ break;
+ }
+ }
+ }
+ return isSupported;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java
index f8291dc977..ea8cdc1403 100644
--- a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java
@@ -345,8 +345,8 @@ public class LegacyPluginServiceImpl implements PluginService {
" for interface=" + iname +
" pluginName=" + name);
Object result = pluginClass.newInstance();
- if (result instanceof SelfNamedPlugin) {
- ((SelfNamedPlugin) result).setPluginInstanceName(name);
+ if (result instanceof NameAwarePlugin) {
+ ((NameAwarePlugin) result).setPluginInstanceName(name);
}
return result;
}
diff --git a/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java b/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java
new file mode 100644
index 0000000000..6c562ea04c
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java
@@ -0,0 +1,42 @@
+/**
+ * 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.core;
+
+/**
+ * This is the interface that should be implemented by all the named plugin that
+ * like to be aware of their name
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ * @version $Revision$
+ * @see org.dspace.core.service.PluginService
+ */
+public interface NameAwarePlugin {
+
+ /**
+ * Get the instance's particular name.
+ * Returns the name by which the class was chosen when
+ * this instance was created. Only works for instances created
+ * by PluginService
, or if someone remembers to call setPluginName.
+ *
+ * Useful when the implementation class wants to be configured differently
+ * when it is invoked under different names.
+ *
+ * @return name or null if not available.
+ */
+ public String getPluginInstanceName();
+
+ /**
+ * Set the name under which this plugin was instantiated.
+ * Not to be invoked by application code, it is
+ * called automatically by PluginService.getNamedPlugin()
+ * when the plugin is instantiated.
+ *
+ * @param name -- name used to select this class.
+ */
+ public void setPluginInstanceName(String name);
+}
diff --git a/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java b/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java
index 2bdcf830e7..680fa15c80 100644
--- a/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java
+++ b/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java
@@ -28,7 +28,7 @@ package org.dspace.core;
* @version $Revision$
* @see org.dspace.core.service.PluginService
*/
-public abstract class SelfNamedPlugin {
+public abstract class SelfNamedPlugin implements NameAwarePlugin {
// the specific alias used to find the class that created this instance.
private String myName = null;
@@ -52,30 +52,13 @@ public abstract class SelfNamedPlugin {
return null;
}
- /**
- * Get an instance's particular name.
- * Returns the name by which the class was chosen when
- * this instance was created. Only works for instances created
- * by PluginService
, or if someone remembers to call setPluginName.
- *
- * Useful when the implementation class wants to be configured differently
- * when it is invoked under different names.
- *
- * @return name or null if not available.
- */
+ @Override
public String getPluginInstanceName() {
return myName;
}
- /**
- * Set the name under which this plugin was instantiated.
- * Not to be invoked by application code, it is
- * called automatically by PluginService.getNamedPlugin()
- * when the plugin is instantiated.
- *
- * @param name -- name used to select this class.
- */
- protected void setPluginInstanceName(String name) {
+ @Override
+ public void setPluginInstanceName(String name) {
myName = name;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java
index 2b6c52d0d6..754f3b4ab3 100644
--- a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java
+++ b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java
@@ -199,6 +199,9 @@ public class MetadataWebService extends AbstractCurationTask implements Namespac
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
+ // disallow DTD parsing to ensure no XXE attacks can occur.
+ // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
docBuilder = factory.newDocumentBuilder();
} catch (ParserConfigurationException pcE) {
log.error("caught exception: " + pcE);
diff --git a/dspace-api/src/main/java/org/dspace/curate/Curation.java b/dspace-api/src/main/java/org/dspace/curate/Curation.java
new file mode 100644
index 0000000000..44cbb24ed9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java
@@ -0,0 +1,371 @@
+/**
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.Writer;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.io.output.NullOutputStream;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.factory.ContentServiceFactory;
+import org.dspace.core.Context;
+import org.dspace.core.factory.CoreServiceFactory;
+import org.dspace.curate.factory.CurateServiceFactory;
+import org.dspace.eperson.EPerson;
+import org.dspace.eperson.factory.EPersonServiceFactory;
+import org.dspace.eperson.service.EPersonService;
+import org.dspace.handle.factory.HandleServiceFactory;
+import org.dspace.handle.service.HandleService;
+import org.dspace.scripts.DSpaceRunnable;
+import org.dspace.utils.DSpace;
+
+/**
+ * CurationCli provides command-line access to Curation tools and processes.
+ *
+ * @author richardrodgers
+ */
+public class Curation extends DSpaceRunnable {
+
+ protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
+
+ protected Context context;
+ private CurationClientOptions curationClientOptions;
+
+ private String task;
+ private String taskFile;
+ private String id;
+ private String queue;
+ private String scope;
+ private String reporter;
+ private Map parameters;
+ private boolean verbose;
+
+ @Override
+ public void internalRun() throws Exception {
+ if (curationClientOptions == CurationClientOptions.HELP) {
+ printHelp();
+ return;
+ }
+
+ Curator curator = initCurator();
+
+ // load curation tasks
+ if (curationClientOptions == CurationClientOptions.TASK) {
+ long start = System.currentTimeMillis();
+ handleCurationTask(curator);
+ this.endScript(start);
+ }
+
+ // process task queue
+ if (curationClientOptions == CurationClientOptions.QUEUE) {
+ // process the task queue
+ TaskQueue taskQueue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService()
+ .getSinglePlugin(TaskQueue.class);
+ if (taskQueue == null) {
+ super.handler.logError("No implementation configured for queue");
+ throw new UnsupportedOperationException("No queue service available");
+ }
+ long timeRun = this.runQueue(taskQueue, curator);
+ this.endScript(timeRun);
+ }
+ }
+
+ /**
+ * Does the curation task (-t) or the task in the given file (-T).
+ * Checks:
+ * - if required option -i is missing.
+ * - if option -t has a valid task option
+ */
+ private void handleCurationTask(Curator curator) throws IOException, SQLException {
+ String taskName;
+ if (commandLine.hasOption('t')) {
+ if (verbose) {
+ handler.logInfo("Adding task: " + this.task);
+ }
+ curator.addTask(this.task);
+ if (verbose && !curator.hasTask(this.task)) {
+ handler.logInfo("Task: " + this.task + " not resolved");
+ }
+ } else if (commandLine.hasOption('T')) {
+ // load taskFile
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(this.taskFile));
+ while ((taskName = reader.readLine()) != null) {
+ if (verbose) {
+ super.handler.logInfo("Adding task: " + taskName);
+ }
+ curator.addTask(taskName);
+ }
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ }
+ // run tasks against object
+ if (verbose) {
+ super.handler.logInfo("Starting curation");
+ super.handler.logInfo("Curating id: " + this.id);
+ }
+ if ("all".equals(this.id)) {
+ // run on whole Site
+ curator.curate(context,
+ ContentServiceFactory.getInstance().getSiteService().findSite(context).getHandle());
+ } else {
+ curator.curate(context, this.id);
+ }
+ }
+
+ /**
+ * Runs task queue (-q set)
+ *
+ * @param queue The task queue
+ * @param curator The curator
+ * @return Time when queue started
+ */
+ private long runQueue(TaskQueue queue, Curator curator) throws SQLException, AuthorizeException, IOException {
+ // use current time as our reader 'ticket'
+ long ticket = System.currentTimeMillis();
+ Iterator entryIter = queue.dequeue(this.queue, ticket).iterator();
+ while (entryIter.hasNext()) {
+ TaskQueueEntry entry = entryIter.next();
+ if (verbose) {
+ super.handler.logInfo("Curating id: " + entry.getObjectId());
+ }
+ curator.clear();
+ // does entry relate to a DSO or workflow object?
+ if (entry.getObjectId().indexOf('/') > 0) {
+ for (String taskName : entry.getTaskNames()) {
+ curator.addTask(taskName);
+ }
+ curator.curate(context, entry.getObjectId());
+ } else {
+ // make eperson who queued task the effective user
+ EPerson agent = ePersonService.findByEmail(context, entry.getEpersonId());
+ if (agent != null) {
+ context.setCurrentUser(agent);
+ }
+ CurateServiceFactory.getInstance().getWorkflowCuratorService()
+ .curate(curator, context, entry.getObjectId());
+ }
+ }
+ queue.release(this.queue, ticket, true);
+ return ticket;
+ }
+
+ /**
+ * End of curation script; logs script time if -v verbose is set
+ *
+ * @param timeRun Time script was started
+ * @throws SQLException If DSpace contextx can't complete
+ */
+ private void endScript(long timeRun) throws SQLException {
+ context.complete();
+ if (verbose) {
+ long elapsed = System.currentTimeMillis() - timeRun;
+ this.handler.logInfo("Ending curation. Elapsed time: " + elapsed);
+ }
+ }
+
+ /**
+ * Initialize the curator with command line variables
+ *
+ * @return Initialised curator
+ * @throws FileNotFoundException If file of command line variable -r reporter is not found
+ */
+ private Curator initCurator() throws FileNotFoundException {
+ Curator curator = new Curator();
+ OutputStream reporterStream;
+ if (null == this.reporter) {
+ reporterStream = new NullOutputStream();
+ } else if ("-".equals(this.reporter)) {
+ reporterStream = System.out;
+ } else {
+ reporterStream = new PrintStream(this.reporter);
+ }
+ Writer reportWriter = new OutputStreamWriter(reporterStream);
+ curator.setReporter(reportWriter);
+
+ if (this.scope != null) {
+ Curator.TxScope txScope = Curator.TxScope.valueOf(this.scope.toUpperCase());
+ curator.setTransactionScope(txScope);
+ }
+
+ curator.addParameters(parameters);
+ // we are operating in batch mode, if anyone cares.
+ curator.setInvoked(Curator.Invoked.BATCH);
+ return curator;
+ }
+
+ @Override
+ public void printHelp() {
+ super.printHelp();
+ super.handler.logInfo("\nwhole repo: CurationCli -t estimate -i all");
+ super.handler.logInfo("single item: CurationCli -t generate -i itemId");
+ super.handler.logInfo("task queue: CurationCli -q monthly");
+ }
+
+ @Override
+ public CurationScriptConfiguration getScriptConfiguration() {
+ return new DSpace().getServiceManager().getServiceByName("curate", CurationScriptConfiguration.class);
+ }
+
+ @Override
+ public void setup() throws ParseException {
+ assignCurrentUserInContext();
+ this.curationClientOptions = CurationClientOptions.getClientOption(commandLine);
+
+ if (this.curationClientOptions != null) {
+ this.initGeneralLineOptionsAndCheckIfValid();
+ if (curationClientOptions == CurationClientOptions.TASK) {
+ this.initTaskLineOptionsAndCheckIfValid();
+ } else if (curationClientOptions == CurationClientOptions.QUEUE) {
+ this.queue = this.commandLine.getOptionValue('q');
+ }
+ } else {
+ throw new IllegalArgumentException("[--help || --task|--taskfile <> -identifier <> || -queue <> ] must be" +
+ " specified");
+ }
+ }
+
+ /**
+ * This method will assign the currentUser to the {@link Context} variable which is also created in this method.
+ * The instance of the method in this class will fetch the EPersonIdentifier from this class, this identifier
+ * was given to this class upon instantiation, it'll then be used to find the {@link EPerson} associated with it
+ * and this {@link EPerson} will be set as the currentUser of the created {@link Context}
+ * @throws ParseException If something went wrong with the retrieval of the EPerson Identifier
+ */
+ protected void assignCurrentUserInContext() throws ParseException {
+ UUID currentUserUuid = this.getEpersonIdentifier();
+ try {
+ this.context = new Context(Context.Mode.BATCH_EDIT);
+ EPerson eperson = ePersonService.find(context, currentUserUuid);
+ if (eperson == null) {
+ super.handler.logError("EPerson not found: " + currentUserUuid);
+ throw new IllegalArgumentException("Unable to find a user with uuid: " + currentUserUuid);
+ }
+ this.context.setCurrentUser(eperson);
+ } catch (SQLException e) {
+ handler.handleException("Something went wrong trying to fetch eperson for uuid: " + currentUserUuid, e);
+ }
+ }
+
+ /**
+ * Fills in some optional command line options.
+ * Checks if there are missing required options or invalid values for options.
+ */
+ private void initGeneralLineOptionsAndCheckIfValid() {
+ // report file
+ if (this.commandLine.hasOption('r')) {
+ this.reporter = this.commandLine.getOptionValue('r');
+ }
+
+ // parameters
+ this.parameters = new HashMap<>();
+ if (this.commandLine.hasOption('p')) {
+ for (String parameter : this.commandLine.getOptionValues('p')) {
+ String[] parts = parameter.split("=", 2);
+ String name = parts[0].trim();
+ String value;
+ if (parts.length > 1) {
+ value = parts[1].trim();
+ } else {
+ value = "true";
+ }
+ this.parameters.put(name, value);
+ }
+ }
+
+ // verbose
+ verbose = false;
+ if (commandLine.hasOption('v')) {
+ verbose = true;
+ }
+
+ // scope
+ if (this.commandLine.getOptionValue('s') != null) {
+ this.scope = this.commandLine.getOptionValue('s');
+ if (this.scope != null && Curator.TxScope.valueOf(this.scope.toUpperCase()) == null) {
+ this.handler.logError("Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " +
+ "'open' recognized");
+ throw new IllegalArgumentException(
+ "Bad transaction scope '" + this.scope + "': only 'object', 'curation' or " +
+ "'open' recognized");
+ }
+ }
+ }
+
+ /**
+ * Fills in required command line options for the task or taskFile option.
+ * Checks if there are is a missing required -i option and if -i is either 'all' or a valid dso handle.
+ * Checks if -t task has a valid task option.
+ * Checks if -T taskfile is a valid file.
+ */
+ private void initTaskLineOptionsAndCheckIfValid() {
+ // task or taskFile
+ if (this.commandLine.hasOption('t')) {
+ this.task = this.commandLine.getOptionValue('t');
+ if (!CurationClientOptions.getTaskOptions().contains(this.task)) {
+ super.handler
+ .logError("-t task must be one of: " + CurationClientOptions.getTaskOptions());
+ throw new IllegalArgumentException(
+ "-t task must be one of: " + CurationClientOptions.getTaskOptions());
+ }
+ } else if (this.commandLine.hasOption('T')) {
+ this.taskFile = this.commandLine.getOptionValue('T');
+ if (!(new File(this.taskFile).isFile())) {
+ super.handler
+ .logError("-T taskFile must be valid file: " + this.taskFile);
+ throw new IllegalArgumentException("-T taskFile must be valid file: " + this.taskFile);
+ }
+ }
+
+ if (this.commandLine.hasOption('i')) {
+ this.id = this.commandLine.getOptionValue('i').toLowerCase();
+ if (!this.id.equalsIgnoreCase("all")) {
+ HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
+ DSpaceObject dso;
+ try {
+ dso = handleService.resolveToObject(this.context, id);
+ } catch (SQLException e) {
+ super.handler.logError("SQLException trying to resolve handle " + id + " to a valid dso");
+ throw new IllegalArgumentException(
+ "SQLException trying to resolve handle " + id + " to a valid dso");
+ }
+ if (dso == null) {
+ super.handler.logError("Id must be specified: a valid dso handle or 'all'; " + this.id + " could " +
+ "not be resolved to valid dso handle");
+ throw new IllegalArgumentException(
+ "Id must be specified: a valid dso handle or 'all'; " + this.id + " could " +
+ "not be resolved to valid dso handle");
+ }
+ }
+ } else {
+ super.handler.logError("Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " +
+ "help)");
+ throw new IllegalArgumentException(
+ "Id must be specified: a handle, 'all', or no -i and a -q task queue (-h for " +
+ "help)");
+ }
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java
index 3832ddf3ec..f70aea5b1d 100644
--- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java
+++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java
@@ -7,269 +7,42 @@
*/
package org.dspace.curate;
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintStream;
-import java.io.Writer;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
+import java.sql.SQLException;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.PosixParser;
-import org.apache.commons.io.output.NullOutputStream;
-import org.dspace.content.factory.ContentServiceFactory;
+import org.apache.commons.cli.ParseException;
import org.dspace.core.Context;
-import org.dspace.core.factory.CoreServiceFactory;
-import org.dspace.curate.factory.CurateServiceFactory;
import org.dspace.eperson.EPerson;
-import org.dspace.eperson.factory.EPersonServiceFactory;
-import org.dspace.eperson.service.EPersonService;
/**
- * CurationCli provides command-line access to Curation tools and processes.
- *
- * @author richardrodgers
+ * This is the CLI version of the {@link Curation} script.
+ * This will only be called when the curate script is called from a commandline instance.
*/
-public class CurationCli {
+public class CurationCli extends Curation {
/**
- * Default constructor
+ * This is the overridden instance of the {@link Curation#assignCurrentUserInContext()} method in the parent class
+ * {@link Curation}.
+ * This is done so that the CLI version of the Script is able to retrieve its currentUser from the -e flag given
+ * with the parameters of the Script.
+ * @throws ParseException If the e flag was not given to the parameters when calling the script
*/
- private CurationCli() { }
-
- public static void main(String[] args) throws Exception {
- // create an options object and populate it
- CommandLineParser parser = new PosixParser();
-
- Options options = new Options();
-
- options.addOption("t", "task", true,
- "curation task name");
- options.addOption("T", "taskfile", true,
- "file containing curation task names");
- options.addOption("i", "id", true,
- "Id (handle) of object to perform task on, or 'all' to perform on whole repository");
- options.addOption("p", "parameter", true,
- "a task parameter 'NAME=VALUE'");
- options.addOption("q", "queue", true,
- "name of task queue to process");
- options.addOption("e", "eperson", true,
- "email address of curating eperson");
- options.addOption("r", "reporter", true,
- "relative or absolute path to the desired report file. "
- + "Use '-' to report to console. "
- + "If absent, no reporting");
- options.addOption("s", "scope", true,
- "transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' " +
- "applies");
- options.addOption("v", "verbose", false,
- "report activity to stdout");
- options.addOption("h", "help", false, "help");
-
- CommandLine line = parser.parse(options, args);
-
- String taskName = null;
- String taskFileName = null;
- String idName = null;
- String taskQueueName = null;
- String ePersonName = null;
- String reporterName = null;
- String scope = null;
- boolean verbose = false;
- final Map parameters = new HashMap<>();
-
- if (line.hasOption('h')) {
- HelpFormatter help = new HelpFormatter();
- help.printHelp("CurationCli\n", options);
- System.out
- .println("\nwhole repo: CurationCli -t estimate -i all");
- System.out
- .println("single item: CurationCli -t generate -i itemId");
- System.out
- .println("task queue: CurationCli -q monthly");
- System.exit(0);
- }
-
- if (line.hasOption('t')) { // task
- taskName = line.getOptionValue('t');
- }
-
- if (line.hasOption('T')) { // task file
- taskFileName = line.getOptionValue('T');
- }
-
- if (line.hasOption('i')) { // id
- idName = line.getOptionValue('i');
- }
-
- if (line.hasOption('q')) { // task queue
- taskQueueName = line.getOptionValue('q');
- }
-
- if (line.hasOption('e')) { // eperson
- ePersonName = line.getOptionValue('e');
- }
-
- if (line.hasOption('p')) { // parameter
- for (String parameter : line.getOptionValues('p')) {
- String[] parts = parameter.split("=", 2);
- String name = parts[0].trim();
- String value;
- if (parts.length > 1) {
- value = parts[1].trim();
- } else {
- value = "true";
- }
- parameters.put(name, value);
- }
- }
- if (line.hasOption('r')) { // report file
- reporterName = line.getOptionValue('r');
- }
-
-
- if (line.hasOption('s')) { // transaction scope
- scope = line.getOptionValue('s');
- }
-
- if (line.hasOption('v')) { // verbose
- verbose = true;
- }
-
- // now validate the args
- if (idName == null && taskQueueName == null) {
- System.out.println("Id must be specified: a handle, 'all', or a task queue (-h for help)");
- System.exit(1);
- }
-
- if (taskName == null && taskFileName == null && taskQueueName == null) {
- System.out.println("A curation task or queue must be specified (-h for help)");
- System.exit(1);
- }
-
- if (scope != null && Curator.TxScope.valueOf(scope.toUpperCase()) == null) {
- System.out.println("Bad transaction scope '" + scope + "': only 'object', 'curation' or 'open' recognized");
- System.exit(1);
- }
- EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
-
- Context c = new Context(Context.Mode.BATCH_EDIT);
- if (ePersonName != null) {
- EPerson ePerson = ePersonService.findByEmail(c, ePersonName);
- if (ePerson == null) {
- System.out.println("EPerson not found: " + ePersonName);
- System.exit(1);
- }
- c.setCurrentUser(ePerson);
- } else {
- c.turnOffAuthorisationSystem();
- }
-
- Curator curator = new Curator();
- OutputStream reporter;
- if (null == reporterName) {
- reporter = new NullOutputStream();
- } else if ("-".equals(reporterName)) {
- reporter = System.out;
- } else {
- reporter = new PrintStream(reporterName);
- }
- Writer reportWriter = new OutputStreamWriter(reporter);
- curator.setReporter(reportWriter);
-
- if (scope != null) {
- Curator.TxScope txScope = Curator.TxScope.valueOf(scope.toUpperCase());
- curator.setTransactionScope(txScope);
- }
- curator.addParameters(parameters);
- // we are operating in batch mode, if anyone cares.
- curator.setInvoked(Curator.Invoked.BATCH);
- // load curation tasks
- if (taskName != null) {
- if (verbose) {
- System.out.println("Adding task: " + taskName);
- }
- curator.addTask(taskName);
- if (verbose && !curator.hasTask(taskName)) {
- System.out.println("Task: " + taskName + " not resolved");
- }
- } else if (taskQueueName == null) {
- // load taskFile
- BufferedReader reader = null;
+ @Override
+ protected void assignCurrentUserInContext() throws ParseException {
+ if (this.commandLine.hasOption('e')) {
+ String ePersonEmail = this.commandLine.getOptionValue('e');
+ this.context = new Context(Context.Mode.BATCH_EDIT);
try {
- reader = new BufferedReader(new FileReader(taskFileName));
- while ((taskName = reader.readLine()) != null) {
- if (verbose) {
- System.out.println("Adding task: " + taskName);
- }
- curator.addTask(taskName);
+ EPerson ePerson = ePersonService.findByEmail(this.context, ePersonEmail);
+ if (ePerson == null) {
+ super.handler.logError("EPerson not found: " + ePersonEmail);
+ throw new IllegalArgumentException("Unable to find a user with email: " + ePersonEmail);
}
- } finally {
- if (reader != null) {
- reader.close();
- }
- }
- }
- // run tasks against object
- long start = System.currentTimeMillis();
- if (verbose) {
- System.out.println("Starting curation");
- }
- if (idName != null) {
- if (verbose) {
- System.out.println("Curating id: " + idName);
- }
- if ("all".equals(idName)) {
- // run on whole Site
- curator.curate(c, ContentServiceFactory.getInstance().getSiteService().findSite(c).getHandle());
- } else {
- curator.curate(c, idName);
+ this.context.setCurrentUser(ePerson);
+ } catch (SQLException e) {
+ throw new IllegalArgumentException("SQLException trying to find user with email: " + ePersonEmail);
}
} else {
- // process the task queue
- TaskQueue queue = (TaskQueue) CoreServiceFactory.getInstance().getPluginService()
- .getSinglePlugin(TaskQueue.class);
- if (queue == null) {
- System.out.println("No implementation configured for queue");
- throw new UnsupportedOperationException("No queue service available");
- }
- // use current time as our reader 'ticket'
- long ticket = System.currentTimeMillis();
- Iterator entryIter = queue.dequeue(taskQueueName, ticket).iterator();
- while (entryIter.hasNext()) {
- TaskQueueEntry entry = entryIter.next();
- if (verbose) {
- System.out.println("Curating id: " + entry.getObjectId());
- }
- curator.clear();
- // does entry relate to a DSO or workflow object?
- if (entry.getObjectId().indexOf("/") > 0) {
- for (String task : entry.getTaskNames()) {
- curator.addTask(task);
- }
- curator.curate(c, entry.getObjectId());
- } else {
- // make eperson who queued task the effective user
- EPerson agent = ePersonService.findByEmail(c, entry.getEpersonId());
- if (agent != null) {
- c.setCurrentUser(agent);
- }
- CurateServiceFactory.getInstance().getWorkflowCuratorService()
- .curate(curator, c, entry.getObjectId());
- }
- }
- queue.release(taskQueueName, ticket, true);
- }
- c.complete();
- if (verbose) {
- long elapsed = System.currentTimeMillis() - start;
- System.out.println("Ending curation. Elapsed time: " + elapsed);
+ throw new ParseException("Required parameter -e missing!");
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java
new file mode 100644
index 0000000000..5e1d014873
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java
@@ -0,0 +1,26 @@
+/**
+ * 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 org.apache.commons.cli.Options;
+
+/**
+ * This is the CLI version of the {@link CurationScriptConfiguration} class that handles the configuration for the
+ * {@link CurationCli} script
+ */
+public class CurationCliScriptConfiguration extends CurationScriptConfiguration {
+
+ @Override
+ public Options getOptions() {
+ options = super.getOptions();
+ options.addOption("e", "eperson", true, "email address of curating eperson");
+ options.getOption("e").setType(String.class);
+ options.getOption("e").setRequired(true);
+ return options;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java
new file mode 100644
index 0000000000..8ec0f14697
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java
@@ -0,0 +1,89 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * This Enum holds all the possible options and combinations for the Curation script
+ *
+ * @author Maria Verdonck (Atmire) on 23/06/2020
+ */
+public enum CurationClientOptions {
+ TASK,
+ QUEUE,
+ HELP;
+
+ private static List taskOptions;
+
+ /**
+ * This method resolves the CommandLine parameters to figure out which action the curation script should perform
+ *
+ * @param commandLine The relevant CommandLine for the curation script
+ * @return The curation option to be ran, parsed from the CommandLine
+ */
+ protected static CurationClientOptions getClientOption(CommandLine commandLine) {
+ if (commandLine.hasOption("h")) {
+ return CurationClientOptions.HELP;
+ } else if (commandLine.hasOption("t") || commandLine.hasOption("T")) {
+ return CurationClientOptions.TASK;
+ } else if (commandLine.hasOption("q")) {
+ return CurationClientOptions.QUEUE;
+ }
+ return null;
+ }
+
+ /**
+ * This method will create all the possible Options for the {@link Curation} script.
+ * This will be used by {@link CurationScriptConfiguration}
+ * @return The options for the {@link Curation} script
+ */
+ protected static Options constructOptions() {
+ Options options = new Options();
+
+ options.addOption("t", "task", true, "curation task name; options: " + getTaskOptions());
+ options.addOption("T", "taskfile", true, "file containing curation task names");
+ options.addOption("i", "id", true,
+ "Id (handle) of object to perform task on, or 'all' to perform on whole repository");
+ options.addOption("p", "parameter", true, "a task parameter 'NAME=VALUE'");
+ options.addOption("q", "queue", true, "name of task queue to process");
+ options.addOption("r", "reporter", true,
+ "relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " +
+ "reporting");
+ options.addOption("s", "scope", true,
+ "transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' applies");
+ options.addOption("v", "verbose", false, "report activity to stdout");
+ options.addOption("h", "help", false, "help");
+
+ return options;
+ }
+
+ /**
+ * Creates list of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask
+ *
+ * @return List of the taskOptions' keys from the configs of plugin.named.org.dspace.curate.CurationTask
+ */
+ public static List getTaskOptions() {
+ if (taskOptions == null) {
+ ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
+ String[] taskConfigs = configurationService.getArrayProperty("plugin.named.org.dspace.curate.CurationTask");
+ taskOptions = new ArrayList<>();
+ for (String taskConfig : taskConfigs) {
+ taskOptions.add(StringUtils.substringAfterLast(taskConfig, "=").trim());
+ }
+ }
+ return taskOptions;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java
new file mode 100644
index 0000000000..fefb4eb768
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java
@@ -0,0 +1,61 @@
+/**
+ * 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.sql.SQLException;
+
+import org.apache.commons.cli.Options;
+import org.dspace.authorize.service.AuthorizeService;
+import org.dspace.core.Context;
+import org.dspace.scripts.configuration.ScriptConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * The {@link ScriptConfiguration} for the {@link Curation} script
+ *
+ * @author Maria Verdonck (Atmire) on 23/06/2020
+ */
+public class CurationScriptConfiguration extends ScriptConfiguration {
+
+ @Autowired
+ private AuthorizeService authorizeService;
+
+ private Class dspaceRunnableClass;
+
+ @Override
+ public Class getDspaceRunnableClass() {
+ return this.dspaceRunnableClass;
+ }
+
+ @Override
+ public void setDspaceRunnableClass(Class dspaceRunnableClass) {
+ this.dspaceRunnableClass = dspaceRunnableClass;
+ }
+
+ /**
+ * Only admin can run Curation script via the scripts and processes endpoints.
+ * @param context The relevant DSpace context
+ * @return True if currentUser is admin, otherwise false
+ */
+ @Override
+ public boolean isAllowedToExecute(Context context) {
+ try {
+ return authorizeService.isAdmin(context);
+ } catch (SQLException e) {
+ throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ if (options == null) {
+ super.options = CurationClientOptions.constructOptions();
+ }
+ return options;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java
index 44733174df..8f12750bae 100644
--- a/dspace-api/src/main/java/org/dspace/curate/Curator.java
+++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java
@@ -98,6 +98,7 @@ public class Curator {
communityService = ContentServiceFactory.getInstance().getCommunityService();
itemService = ContentServiceFactory.getInstance().getItemService();
handleService = HandleServiceFactory.getInstance().getHandleService();
+ resolver = new TaskResolver();
}
/**
@@ -142,10 +143,10 @@ public class Curator {
// performance order currently FIFO - to be revisited
perfList.add(taskName);
} catch (IOException ioE) {
- log.error("Task: '" + taskName + "' initialization failure: " + ioE.getMessage());
+ System.out.println("Task: '" + taskName + "' initialization failure: " + ioE.getMessage());
}
} else {
- log.error("Task: '" + taskName + "' does not resolve");
+ System.out.println("Task: '" + taskName + "' does not resolve");
}
return this;
}
@@ -259,13 +260,6 @@ public class Curator {
/**
* Performs all configured tasks upon DSpace object
* (Community, Collection or Item).
- *
- * Note: Site-wide tasks will default to running as
- * an Anonymous User unless you call the Site-wide task
- * via the {@link curate(Context,String)} or
- * {@link #curate(Context, DSpaceObject)} method with an
- * authenticated Context object.
- *
* @param dso the DSpace object
* @throws IOException if IO error
*/
@@ -325,7 +319,7 @@ public class Curator {
taskQ.enqueue(queueId, new TaskQueueEntry(c.getCurrentUser().getName(),
System.currentTimeMillis(), perfList, id));
} else {
- log.error("curate - no TaskQueue implemented");
+ System.out.println("curate - no TaskQueue implemented");
}
}
@@ -346,7 +340,7 @@ public class Curator {
try {
reporter.append(message);
} catch (IOException ex) {
- log.error("Task reporting failure", ex);
+ System.out.println("Task reporting failure: " + ex);
}
}
@@ -552,7 +546,7 @@ public class Curator {
return !suspend(statusCode);
} catch (IOException ioe) {
//log error & pass exception upwards
- log.error("Error executing curation task '" + task.getName() + "'", ioe);
+ System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe);
throw ioe;
}
}
@@ -568,7 +562,7 @@ public class Curator {
return !suspend(statusCode);
} catch (IOException ioe) {
//log error & pass exception upwards
- log.error("Error executing curation task '" + task.getName() + "'", ioe);
+ System.out.println("Error executing curation task '" + task.getName() + "'; " + ioe);
throw ioe;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java
index d3efb3c626..d82779015f 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java
@@ -7,6 +7,9 @@
*/
package org.dspace.discovery;
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -31,7 +34,7 @@ public class DiscoverQuery {
**/
private String query;
private List filterQueries;
- private String DSpaceObjectFilter = null;
+ private List dspaceObjectFilters = new ArrayList<>();
private List fieldPresentQueries;
private boolean spellCheck;
@@ -118,20 +121,33 @@ public class DiscoverQuery {
* Sets the DSpace object filter, must be an DSpace Object type integer
* can be used to only return objects from a certain DSpace Object type
*
- * @param DSpaceObjectFilter the DSpace object filer
+ * @param dspaceObjectFilter the DSpace object filter
*/
- public void setDSpaceObjectFilter(String DSpaceObjectFilter) {
- this.DSpaceObjectFilter = DSpaceObjectFilter;
+ public void setDSpaceObjectFilter(String dspaceObjectFilter) {
+ this.dspaceObjectFilters = singletonList(dspaceObjectFilter);
}
/**
- * Gets the DSpace object filter
- * can be used to only return objects from a certain DSpace Object type
+ * Adds a DSpace object filter, must be an DSpace Object type integer.
+ * Can be used to also return objects from a certain DSpace Object type.
*
- * @return the DSpace object filer
+ * @param dspaceObjectFilter the DSpace object filer
*/
- public String getDSpaceObjectFilter() {
- return DSpaceObjectFilter;
+ public void addDSpaceObjectFilter(String dspaceObjectFilter) {
+
+ if (isNotBlank(dspaceObjectFilter)) {
+ this.dspaceObjectFilters.add(dspaceObjectFilter);
+ }
+ }
+
+ /**
+ * Gets the DSpace object filters
+ * can be used to only return objects from certain DSpace Object types
+ *
+ * @return the DSpace object filters
+ */
+ public List getDSpaceObjectFilters() {
+ return dspaceObjectFilters;
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java
index 43ea9eefb2..195c9cd6fc 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java
@@ -8,6 +8,7 @@
package org.dspace.discovery;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
import org.apache.logging.log4j.Logger;
@@ -15,6 +16,7 @@ import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Constants;
import org.dspace.core.Context;
+import org.dspace.discovery.indexobject.factory.IndexFactory;
import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory;
import org.dspace.event.Consumer;
import org.dspace.event.Event;
@@ -67,7 +69,7 @@ public class IndexEventConsumer implements Consumer {
int st = event.getSubjectType();
if (!(st == Constants.ITEM || st == Constants.BUNDLE
- || st == Constants.COLLECTION || st == Constants.COMMUNITY)) {
+ || st == Constants.COLLECTION || st == Constants.COMMUNITY || st == Constants.SITE)) {
log
.warn("IndexConsumer should not have been given this kind of Subject in an event, skipping: "
+ event.toString());
@@ -104,10 +106,28 @@ public class IndexEventConsumer implements Consumer {
case Event.MODIFY:
case Event.MODIFY_METADATA:
if (subject == null) {
- log.warn(event.getEventTypeAsString() + " event, could not get object for "
+ if (st == Constants.SITE) {
+ // Update the indexable objects of type in event.detail of objects with ids in event.identifiers
+ for (String id : event.getIdentifiers()) {
+ IndexFactory indexableObjectService = IndexObjectFactoryFactory.getInstance().
+ getIndexFactoryByType(event.getDetail());
+ Optional indexableObject = Optional.empty();
+ indexableObject = indexableObjectService.findIndexableObject(ctx, id);
+ if (indexableObject.isPresent()) {
+ log.debug("consume() adding event to update queue: " + event.toString());
+ objectsToUpdate
+ .addAll(indexObjectServiceFactory
+ .getIndexableObjects(ctx, indexableObject.get().getIndexedObject()));
+ } else {
+ log.warn("Cannot resolve " + id);
+ }
+ }
+ } else {
+ log.warn(event.getEventTypeAsString() + " event, could not get object for "
+ event.getSubjectTypeAsString() + " id="
+ event.getSubjectID()
+ ", perhaps it has been deleted.");
+ }
} else {
log.debug("consume() adding event to update queue: " + event.toString());
objectsToUpdate.addAll(indexObjectServiceFactory.getIndexableObjects(ctx, subject));
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
index 1c47d46162..88e32d0aaf 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
@@ -7,6 +7,8 @@
*/
package org.dspace.discovery;
+import static java.util.stream.Collectors.joining;
+
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -751,8 +753,13 @@ public class SolrServiceImpl implements SearchService, IndexingService {
String filterQuery = discoveryQuery.getFilterQueries().get(i);
solrQuery.addFilterQuery(filterQuery);
}
- if (discoveryQuery.getDSpaceObjectFilter() != null) {
- solrQuery.addFilterQuery(SearchUtils.RESOURCE_TYPE_FIELD + ":" + discoveryQuery.getDSpaceObjectFilter());
+ if (discoveryQuery.getDSpaceObjectFilters() != null) {
+ solrQuery.addFilterQuery(
+ discoveryQuery.getDSpaceObjectFilters()
+ .stream()
+ .map(filter -> SearchUtils.RESOURCE_TYPE_FIELD + ":" + filter)
+ .collect(joining(" OR "))
+ );
}
for (int i = 0; i < discoveryQuery.getFieldPresentQueries().size(); i++) {
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java
index 187c6b0600..2b2be66384 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java
@@ -17,6 +17,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.solr.common.SolrInputDocument;
import org.dspace.browse.BrowseException;
import org.dspace.browse.BrowseIndex;
+import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.authority.service.ChoiceAuthorityService;
@@ -63,7 +64,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
return;
}
Item item = ((IndexableItem) indexableObject).getIndexedObject();
-
+ Collection collection = item.getOwningCollection();
// Get the currently configured browse indexes
BrowseIndex[] bis;
try {
@@ -175,7 +176,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
true);
if (!ignorePrefered) {
preferedLabel = choiceAuthorityService
- .getLabel(values.get(x), values.get(x).getLanguage());
+ .getLabel(values.get(x), collection, values.get(x).getLanguage());
}
List variants = null;
@@ -195,7 +196,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
if (!ignoreVariants) {
variants = choiceAuthorityService
.getVariants(
- values.get(x));
+ values.get(x), collection);
}
if (StringUtils
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java
new file mode 100644
index 0000000000..90aafcbd30
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java
@@ -0,0 +1,43 @@
+/**
+ * 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.discovery.indexobject;
+
+import java.io.Serializable;
+
+import org.dspace.core.ReloadableEntity;
+import org.dspace.discovery.IndexableObject;
+
+/**
+ * This class exists in order to provide a default implementation for the equals and hashCode methods.
+ * Since IndexableObjects can be made multiple times for the same underlying object, we needed a more finetuned
+ * equals and hashcode methods. We're simply checking that the underlying objects are equal and generating the hashcode
+ * for the underlying object. This way, we'll always get a proper result when calling equals or hashcode on an
+ * IndexableObject because it'll depend on the underlying object
+ * @param Refers to the underlying entity that is linked to this object
+ * @param The type of ID that this entity uses
+ */
+public abstract class AbstractIndexableObject, PK extends Serializable>
+ implements IndexableObject {
+
+ @Override
+ public boolean equals(Object obj) {
+ //Two IndexableObjects of the same DSpaceObject are considered equal
+ if (!(obj instanceof AbstractIndexableObject)) {
+ return false;
+ }
+ IndexableDSpaceObject other = (IndexableDSpaceObject) obj;
+ return other.getIndexedObject().equals(getIndexedObject());
+ }
+
+ @Override
+ public int hashCode() {
+ //Two IndexableObjects of the same DSpaceObject are considered equal
+ return getIndexedObject().hashCode();
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java
index ca1423e593..2e4eb67723 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java
@@ -12,6 +12,7 @@ import java.sql.SQLException;
import java.util.Date;
import java.util.List;
+import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
@@ -56,7 +57,7 @@ public abstract class IndexFactoryImpl implements
doc.addField(SearchUtils.RESOURCE_ID_FIELD, indexableObject.getID().toString());
//Do any additional indexing, depends on the plugins
- for (SolrServiceIndexPlugin solrServiceIndexPlugin : solrServiceIndexPlugins) {
+ for (SolrServiceIndexPlugin solrServiceIndexPlugin : ListUtils.emptyIfNull(solrServiceIndexPlugins)) {
solrServiceIndexPlugin.additionalIndex(context, indexableObject, doc);
}
@@ -190,4 +191,4 @@ public abstract class IndexFactoryImpl implements
public void deleteAll() throws IOException, SolrServerException {
solrSearchCore.getSolr().deleteByQuery(SearchUtils.RESOURCE_TYPE_FIELD + ":" + getType());
}
-}
\ No newline at end of file
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableClaimedTask.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableClaimedTask.java
index 3810b6803f..b96899b618 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableClaimedTask.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableClaimedTask.java
@@ -7,7 +7,6 @@
*/
package org.dspace.discovery.indexobject;
-import org.dspace.discovery.IndexableObject;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
/**
@@ -15,7 +14,7 @@ import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
*
* @author Kevin Van de Velde (kevin at atmire dot com)
*/
-public class IndexableClaimedTask implements IndexableObject {
+public class IndexableClaimedTask extends AbstractIndexableObject {
private ClaimedTask claimedTask;
public static final String TYPE = ClaimedTask.class.getSimpleName();
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableDSpaceObject.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableDSpaceObject.java
index 7ad82b1a95..7abc11eb7f 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableDSpaceObject.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableDSpaceObject.java
@@ -10,7 +10,6 @@ package org.dspace.discovery.indexobject;
import java.util.UUID;
import org.dspace.content.DSpaceObject;
-import org.dspace.discovery.IndexableObject;
/**
* DSpaceObject implementation for the IndexableObject, contains methods used by all DSpaceObject methods
@@ -18,7 +17,7 @@ import org.dspace.discovery.IndexableObject;
*
* @author Kevin Van de Velde (kevin at atmire dot com)
*/
-public abstract class IndexableDSpaceObject implements IndexableObject {
+public abstract class IndexableDSpaceObject extends AbstractIndexableObject {
private T dso;
@@ -40,4 +39,6 @@ public abstract class IndexableDSpaceObject implements I
public UUID getID() {
return dso.getID();
}
-}
\ No newline at end of file
+
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableInProgressSubmission.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableInProgressSubmission.java
index cfa27ff814..d6dd785801 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableInProgressSubmission.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableInProgressSubmission.java
@@ -8,14 +8,13 @@
package org.dspace.discovery.indexobject;
import org.dspace.content.InProgressSubmission;
-import org.dspace.discovery.IndexableObject;
/**
* InProgressSubmission implementation for the IndexableObject
* @author Kevin Van de Velde (kevin at atmire dot com)
*/
public abstract class IndexableInProgressSubmission
- implements IndexableObject {
+ extends AbstractIndexableObject {
protected T inProgressSubmission;
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableMetadataField.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableMetadataField.java
new file mode 100644
index 0000000000..70e63d19ba
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexableMetadataField.java
@@ -0,0 +1,51 @@
+/**
+ * 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.discovery.indexobject;
+
+import org.dspace.content.MetadataField;
+import org.dspace.discovery.IndexableObject;
+
+/**
+ * {@link MetadataField} implementation for the {@link IndexableObject}
+ *
+ * @author Maria Verdonck (Atmire) on 14/07/2020
+ */
+public class IndexableMetadataField extends AbstractIndexableObject {
+
+ private MetadataField metadataField;
+ public static final String TYPE = MetadataField.class.getSimpleName();
+
+ public IndexableMetadataField(MetadataField metadataField) {
+ this.metadataField = metadataField;
+ }
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public Integer getID() {
+ return this.metadataField.getID();
+ }
+
+ @Override
+ public MetadataField getIndexedObject() {
+ return this.metadataField;
+ }
+
+ @Override
+ public void setIndexedObject(MetadataField metadataField) {
+ this.metadataField = metadataField;
+ }
+
+ @Override
+ public String getTypeText() {
+ return TYPE.toUpperCase();
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexablePoolTask.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexablePoolTask.java
index 6eea1f0ebb..39fdb8b8b5 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexablePoolTask.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexablePoolTask.java
@@ -7,14 +7,13 @@
*/
package org.dspace.discovery.indexobject;
-import org.dspace.discovery.IndexableObject;
import org.dspace.xmlworkflow.storedcomponents.PoolTask;
/**
* PoolTask implementation for the IndexableObject
* @author Kevin Van de Velde (kevin at atmire dot com)
*/
-public class IndexablePoolTask implements IndexableObject {
+public class IndexablePoolTask extends AbstractIndexableObject {
public static final String TYPE = PoolTask.class.getSimpleName();
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java
index 7f98131566..2a1008aaf9 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java
@@ -173,6 +173,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl discoveryConfigurations)
throws SQLException, IOException {
+ // use the item service to retrieve the owning collection also for inprogress submission
+ Collection collection = (Collection) itemService.getParentObject(context, item);
//Keep a list of our sort values which we added, sort values can only be added once
List sortFieldsAdded = new ArrayList<>();
Map