Merge branch 'main' of https://github.com/DSpace/DSpace into feature-issue2816-openaire-funding-external-lookup

This commit is contained in:
Paulo Graça
2021-09-06 18:09:22 +01:00
48 changed files with 3722 additions and 360 deletions

View File

@@ -927,6 +927,13 @@
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -60,7 +60,6 @@ public class Harvest {
options.addOption("p", "purge", false, "delete all items in the collection");
options.addOption("r", "run", false, "run the standard harvest procedure");
options.addOption("g", "ping", false, "test the OAI server and set");
options.addOption("o", "once", false, "run the harvest procedure with specified parameters");
options.addOption("s", "setup", false, "Set the collection up for harvesting");
options.addOption("S", "start", false, "start the harvest loop");
options.addOption("R", "reset", false, "reset harvest status on all collections");
@@ -97,9 +96,6 @@ public class Harvest {
HelpFormatter myhelp = new HelpFormatter();
myhelp.printHelp("Harvest\n", options);
System.out.println("\nPING OAI server: Harvest -g -a oai_source -i oai_set_id");
System.out.println(
"RUNONCE harvest with arbitrary options: Harvest -o -e eperson -c collection -t harvest_type -a " +
"oai_source -i oai_set_id -m metadata_format");
System.out.println(
"SETUP a collection for harvesting: Harvest -s -c collection -t harvest_type -a oai_source -i " +
"oai_set_id -m metadata_format");
@@ -125,9 +121,6 @@ public class Harvest {
if (line.hasOption('g')) {
command = "ping";
}
if (line.hasOption('o')) {
command = "runOnce";
}
if (line.hasOption('S')) {
command = "start";
}
@@ -246,6 +239,10 @@ public class Harvest {
}
pingResponder(oaiSource, oaiSetID, metadataKey);
} else {
System.out.println("Error - your command '" + command + "' was not recoginzed properly");
System.out.println(" (run with -h flag for details)");
System.exit(1);
}
}

View File

@@ -0,0 +1,46 @@
/**
* 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.content.logic;
import org.apache.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.core.Context;
/**
* The default filter, a very simple implementation of Filter / LogicalStatement
* The idea is to have this as a wrapper / root class for all logical operations, so it takes a single
* statement as a property (unlike an operator) and takes no parameters (unlike a condition)
*
* @author Kim Shepherd
* @version $Revision$
*/
public class DefaultFilter implements Filter {
private LogicalStatement statement;
private static Logger log = Logger.getLogger(Filter.class);
/**
* Set statement from Spring configuration in item-filters.xml
* Be aware that this is singular not plural. A filter can have one sub-statement only.
*
* @param statement LogicalStatement of this filter (operator, condition, or another filter)
*/
public void setStatement(LogicalStatement statement) {
this.statement = statement;
}
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
* @param item Item to evaluate
* @return boolean
* @throws LogicalStatementException
*/
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return this.statement.getResult(context, item);
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.content.logic;
import org.dspace.content.Item;
import org.dspace.core.Context;
/**
* The interface for Filter currently doesn't add anything to LogicalStatement but inherits from it
* just to keep naming / reflection clean, and in case Filters should do anything additional in future.
* We need this as filters have to be specified in the spring configuration (item-filters.xml).
* Filters are the top level elements of the logic. Only logical statements that implement this interface
* are allowed to be the root element of a spring configuration (item-filters.xml) of this logic framework.
* A filter is just helping to differentiate between logical statement that can be used as root elements and
* logical statement that shouldn't be use as root element. A filter may contain only one substatement.
*
* @author Kim Shepherd
* @version $Revision$
* @see org.dspace.content.logic.DefaultFilter
*/
public interface Filter extends LogicalStatement {
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
* @param item Item to evaluate
* @return boolean
* @throws LogicalStatementException
*/
boolean getResult(Context context, Item item) throws LogicalStatementException;
}

View File

@@ -0,0 +1,31 @@
/**
* 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.content.logic;
import org.dspace.content.Item;
import org.dspace.core.Context;
/**
* The base interface used by all logic classes: all operators and conditions are logical statements.
* All statements must accept an Item object and return a boolean result.
* The philosophy is that because Filter, Condition, Operator classes implement getResult(), they can all be
* used as sub-statements in other Filters and Operators.
*
* @author Kim Shepherd
* @version $Revision$
*/
public interface LogicalStatement {
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
boolean getResult(Context context, Item item) throws LogicalStatementException;
}

View File

@@ -0,0 +1,35 @@
/**
* 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.content.logic;
/**
* Exception for errors encountered while evaluating logical statements
* defined as spring beans.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class LogicalStatementException extends RuntimeException {
public LogicalStatementException() {
super();
}
public LogicalStatementException(String s, Throwable t) {
super(s, t);
}
public LogicalStatementException(String s) {
super(s);
}
public LogicalStatementException(Throwable t) {
super(t);
}
}

View File

@@ -0,0 +1,143 @@
/**
* 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.content.logic;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
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.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.dspace.kernel.ServiceManager;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* A command-line runner used for testing a logical filter against an item, or all items
*
* @author Kim Shepherd
* @version $Revision$
*/
public class TestLogicRunner {
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(TestLogicRunner.class);
/**
* Default constructor
*/
private TestLogicRunner() { }
/**
* Main runner method for CLI usage
* @param argv array of command-line arguments
*/
public static void main(String[] argv) {
System.out.println("Starting impl of main() test spring logic item filter");
// initialize options
Options options = new Options();
options.addOption("h", "help", false, "Help");
options.addOption("l", "list", false, "List filters");
options.addOption("f", "filter", true, "Use filter <filter>");
options.addOption("i","item", true, "Run filter over item <handle>");
options.addOption("a","all", false, "Run filter over all items");
// initialize parser
CommandLineParser parser = new PosixParser();
CommandLine line = null;
HelpFormatter helpformater = new HelpFormatter();
try {
line = parser.parse(options, argv);
} catch (ParseException ex) {
System.out.println(ex.getMessage());
System.exit(1);
}
if (line.hasOption("help")) {
helpformater.printHelp("\nTest the DSpace logical item filters\n", options);
System.exit(0);
}
// Create a context
Context c = new Context(Context.Mode.READ_ONLY);
//c.turnOffAuthorisationSystem();
ServiceManager manager = DSpaceServicesFactory.getInstance().getServiceManager();
if (line.hasOption("list")) {
// Lit filters and exit
List<Filter> filters = manager.getServicesByType(Filter.class);
for (Filter filter : filters) {
System.out.println(filter.getClass().toString());
}
System.out.println("See item-filters.xml spring config for filter names");
System.exit(0);
}
Filter filter;
if (line.hasOption("filter")) {
String filterName = line.getOptionValue("filter");
filter = manager.getServiceByName(filterName, Filter.class);
if (filter == null) {
System.out.println("Error loading filter: " + filterName);
System.exit(1);
}
if (line.hasOption("item")) {
String handle = line.getOptionValue("item");
HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
try {
DSpaceObject dso = handleService.resolveToObject(c, handle);
if (Constants.typeText[dso.getType()].equals("ITEM")) {
Item item = (Item) dso;
System.out.println(filter.getResult(c, item));
} else {
System.out.println(handle + " is not an ITEM");
}
} catch (SQLException | LogicalStatementException e) {
System.out.println("Error encountered processing item " + handle + ": " + e.getMessage());
}
} else if (line.hasOption("all")) {
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
try {
Iterator<Item> itemIterator = itemService.findAll(c);
while (itemIterator.hasNext()) {
Item i = itemIterator.next();
System.out.println(
"Testing '" + filter + "' on item " + i.getHandle() + " ('" + i.getName() + "')"
);
System.out.println(filter.getResult(c, i));
}
} catch (SQLException | LogicalStatementException e) {
System.out.println("Error encountered processing items: " + e.getMessage());
}
} else {
helpformater.printHelp("\nTest the DSpace logical item filters\n", options);
}
}
}
}

View File

@@ -0,0 +1,91 @@
/**
* 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.content.logic.condition;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.handle.service.HandleService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Abstract class for conditions, to implement the basic getter and setter parameters
*
* @author Kim Shepherd
* @version $Revision$
*/
public abstract class AbstractCondition implements Condition {
// Parameters map (injected, required -- see setter annotation)
private Map<String, Object> parameters;
// Declare and instantiate spring services
//@Autowired(required = true)
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
//@Autowired(required = true)
protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
@Autowired(required = true)
protected HandleService handleService;
// Logging
Logger log = LogManager.getLogger(AbstractCondition.class);
/**
* Get parameters set by spring configuration in item-filters.xml
* These could be any kind of map that the extending condition class needs for evaluation
* @return map of parameters
* @throws LogicalStatementException
*/
@Override
public Map<String, Object> getParameters() throws LogicalStatementException {
return this.parameters;
}
/**
* Set parameters - used by Spring when creating beans from item-filters.xml
* These could be any kind of map that the extending condition class needs for evaluation
* @param parameters
* @throws LogicalStatementException
*/
@Autowired(required = true)
@Override
public void setParameters(Map<String, Object> parameters) throws LogicalStatementException {
this.parameters = parameters;
}
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
* @param item Item to evaluate
* @return boolean
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
if (item == null) {
log.error("Error evaluating item. Passed item is null, returning false");
return false;
}
if (context == null) {
throw new IllegalStateException("Context is null");
}
return true;
}
@Override
public void setItemService(ItemService itemService) {
this.itemService = itemService;
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.content.logic.condition;
import java.util.List;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition to evaluate an item based on how many bitstreams it has in a particular bundle
*
* @author Kim Shepherd
* @version $Revision$
*/
public class BitstreamCountCondition extends AbstractCondition {
/**
* Return true if bitstream count is within bounds of min and/or max parameters
* Return false if out of bounds
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
// This super call just throws some useful exceptions if required objects are null
super.getResult(context, item);
int min = -1;
if (getParameters().get("min") != null) {
min = Integer.parseInt((String)getParameters().get("min"));
}
int max = -1;
if (getParameters().get("max") != null) {
max = Integer.parseInt((String)getParameters().get("max"));
}
String bundleName = (String)getParameters().get("bundle");
if (min < 0 && max < 0) {
throw new LogicalStatementException("Either min or max parameter must be 0 or bigger.");
}
List<Bundle> bundles;
int count = 0;
if (bundleName != null) {
bundles = item.getBundles(bundleName);
} else {
bundles = item.getBundles();
}
for (Bundle bundle : bundles) {
count += bundle.getBitstreams().size();
}
if (min < 0) {
return (count <= max);
}
if (max < 0) {
return (count >= min);
}
return (count <= max && count >= min);
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.content.logic.condition;
import java.util.Map;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
/**
* The Condition interface
*
* A condition is one logical statement testing an item for any idea. A condition is always a logical statements. An
* operator is not a condition but also a logical statement.
*
* @author Kim Shepherd
* @version $Revision$
*/
public interface Condition extends LogicalStatement {
/**
* Set parameters - used by Spring
* @param parameters
* @throws LogicalStatementException
*/
void setParameters(Map<String, Object> parameters) throws LogicalStatementException;
/**
* Get parameters set by Spring in item-filters.xml
* These could be any kind of map that the extending condition class needs for evaluation
* @return map of parameters
* @throws LogicalStatementException
*/
Map<String, Object> getParameters() throws LogicalStatementException;
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
* @param item Item to evaluate
* @return boolean
* @throws LogicalStatementException
*/
boolean getResult(Context context, Item item) throws LogicalStatementException;
public void setItemService(ItemService itemService);
}

View File

@@ -0,0 +1,80 @@
/**
* 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.content.logic.condition;
import java.sql.SQLException;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition that accepts a list of collection handles and returns true
* if the item belongs to any of them.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class InCollectionCondition extends AbstractCondition {
private static Logger log = LogManager.getLogger(InCollectionCondition.class);
/**
* Return true if item is in one of the specified collections
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
List<String> collectionHandles = (List<String>)getParameters().get("collections");
// Look for the handle among an archived item's collections - this test will only work after submission
// and archival is complete
List<Collection> itemCollections = item.getCollections();
for (Collection collection : itemCollections) {
if (collectionHandles.contains(collection.getHandle())) {
log.debug("item " + item.getHandle() + " is in collection "
+ collection.getHandle() + ", returning true");
return true;
}
}
// Look for the parent object of the item. This is important as the item.getOwningCollection method
// may return null, even though the item itself does have a parent object, at the point of archival
try {
DSpaceObject parent = itemService.getParentObject(context, item);
if (parent != null) {
log.debug("Got parent DSO for item: " + parent.getID().toString());
log.debug("Parent DSO handle: " + parent.getHandle());
if (collectionHandles.contains(parent.getHandle())) {
log.debug("item " + item.getHandle() + " is in collection "
+ parent.getHandle() + ", returning true");
return true;
}
} else {
log.debug("Parent DSO is null...");
}
} catch (SQLException e) {
log.error("Error obtaining parent DSO", e);
throw new LogicalStatementException(e);
}
// If we reach this statement, the item did not appear in any of the collections from the parameters
log.debug("item " + item.getHandle() + " not found in the passed collection handle list");
return false;
}
}

View File

@@ -0,0 +1,90 @@
/**
* 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.content.logic.condition;
import java.sql.SQLException;
import java.util.List;
import org.apache.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition that accepts a list of community handles and returns true
* if the item belongs to any of them.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class InCommunityCondition extends AbstractCondition {
private static Logger log = Logger.getLogger(InCommunityCondition.class);
/**
* Return true if item is in one of the specified collections
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
List<String> communityHandles = (List<String>)getParameters().get("communities");
List<Collection> itemCollections = item.getCollections();
// Check communities of item.getCollections() - this will only see collections if the item is archived
for (Collection collection : itemCollections) {
try {
List<Community> communities = collection.getCommunities();
for (Community community : communities) {
if (communityHandles.contains(community.getHandle())) {
return true;
}
}
} catch (SQLException e) {
log.error(e.getMessage());
throw new LogicalStatementException(e);
}
}
// Look for the parent object of the item. This is important as the item.getOwningCollection method
// may return null, even though the item itself does have a parent object, at the point of archival
try {
DSpaceObject parent = itemService.getParentObject(context, item);
if (parent instanceof Collection) {
log.debug("Got parent DSO for item: " + parent.getID().toString());
log.debug("Parent DSO handle: " + parent.getHandle());
try {
// Now iterate communities of this parent collection
Collection collection = (Collection)parent;
List<Community> communities = collection.getCommunities();
for (Community community : communities) {
if (communityHandles.contains(community.getHandle())) {
return true;
}
}
} catch (SQLException e) {
log.error(e.getMessage());
throw new LogicalStatementException(e);
}
} else {
log.debug("Parent DSO is null or is not a Collection...");
}
} catch (SQLException e) {
log.error("Error obtaining parent DSO", e);
throw new LogicalStatementException(e);
}
return false;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.content.logic.condition;
import org.apache.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition that returns true if the item is withdrawn
*
* @author Kim Shepherd
* @version $Revision$
*/
public class IsWithdrawnCondition extends AbstractCondition {
private static Logger log = Logger.getLogger(IsWithdrawnCondition.class);
/**
* Return true if item is withdrawn
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
log.debug("Result of isWithdrawn is " + item.isWithdrawn());
return item.isWithdrawn();
}
}

View File

@@ -0,0 +1,66 @@
/**
* 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.content.logic.condition;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition that returns true if a pattern (regex) matches any value
* in a given metadata field
*
* @author Kim Shepherd
* @version $Revision$
*/
public class MetadataValueMatchCondition extends AbstractCondition {
private static Logger log = Logger.getLogger(MetadataValueMatchCondition.class);
/**
* Return true if any value for a specified field in the item matches a specified regex pattern
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
String field = (String)getParameters().get("field");
if (field == null) {
return false;
}
String[] fieldParts = field.split("\\.");
String schema = (fieldParts.length > 0 ? fieldParts[0] : null);
String element = (fieldParts.length > 1 ? fieldParts[1] : null);
String qualifier = (fieldParts.length > 2 ? fieldParts[2] : null);
List<MetadataValue> values = itemService.getMetadata(item, schema, element, qualifier, Item.ANY);
for (MetadataValue value : values) {
if (getParameters().get("pattern") instanceof String) {
String pattern = (String)getParameters().get("pattern");
log.debug("logic for " + item.getHandle() + ": pattern passed is " + pattern
+ ", checking value " + value.getValue());
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(value.getValue());
if (m.find()) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.content.logic.condition;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition that returns true if any pattern in a list of patterns matches any value
* in a given metadata field
*
* @author Kim Shepherd
* @version $Revision$
*/
public class MetadataValuesMatchCondition extends AbstractCondition {
private static Logger log = Logger.getLogger(MetadataValuesMatchCondition.class);
/**
* Return true if any value for a specified field in the item matches any of the specified regex patterns
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
String field = (String)getParameters().get("field");
if (field == null) {
return false;
}
String[] fieldParts = field.split("\\.");
String schema = (fieldParts.length > 0 ? fieldParts[0] : null);
String element = (fieldParts.length > 1 ? fieldParts[1] : null);
String qualifier = (fieldParts.length > 2 ? fieldParts[2] : null);
List<MetadataValue> values = itemService.getMetadata(item, schema, element, qualifier, Item.ANY);
for (MetadataValue value : values) {
if (getParameters().get("patterns") instanceof List) {
List<String> patternList = (List<String>)getParameters().get("patterns");
// If the list is empty, just return true and log error?
log.error("No patterns were passed for metadata value matching, defaulting to 'true'");
if (patternList == null) {
return true;
}
for (String pattern : patternList) {
log.debug("logic for " + item.getHandle() + ": pattern passed is " + pattern
+ ", checking value " + value.getValue());
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(value.getValue());
if (m.find()) {
return true;
}
}
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
/**
* 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.content.logic.condition;
import java.sql.SQLException;
import java.util.List;
import org.apache.log4j.Logger;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Constants;
import org.dspace.core.Context;
/**
* A condition that accepts a group and action parameter and returns true if the group
* can perform the action on a given item
*
* @author Kim Shepherd
* @version $Revision$
*/
public class ReadableByGroupCondition extends AbstractCondition {
private static Logger log = Logger.getLogger(ReadableByGroupCondition.class);
// Authorize service
AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
/**
* Return true if this item allows a specified action (eg READ, WRITE, ADD) by a specified group
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
String group = (String)getParameters().get("group");
String action = (String)getParameters().get("action");
try {
List<ResourcePolicy> policies = authorizeService
.getPoliciesActionFilter(context, item, Constants.getActionID(action));
for (ResourcePolicy policy : policies) {
if (policy.getGroup().getName().equals(group)) {
return true;
}
}
} catch (SQLException e) {
log.error("Error trying to read policies for " + item.getHandle() + ": " + e.getMessage());
throw new LogicalStatementException(e);
}
log.debug("item " + item.getHandle() + " not readable by anonymous group");
return false;
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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.content.logic.operator;
import java.util.ArrayList;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* Abstract class for an operator.
* An operator contains a list of logical statements (conditions or more operators) and depending on the kind
* of operator (AND, OR, NOT, etc.) the results of some or all sub-statements are evaluated and returned
* as a logical result
*
* @author Kim Shepherd
* @version $Revision$
*/
public abstract class AbstractOperator implements LogicalStatement {
private List<LogicalStatement> statements = new ArrayList<>();
/**
* Get sub-statements for this operator
* @return list of sub-statements
*/
public List<LogicalStatement> getStatements() {
return statements;
}
/**
* Set sub-statements for this operator, as defined in item-filters.xml
* @param statements list of logical statements
*/
public void setStatements(List<LogicalStatement> statements) {
this.statements = statements;
}
/**
* Default constructor
*/
public AbstractOperator() {}
/**
* Constructor to create operator from some predefined statements
* @param statements
*/
public AbstractOperator(List<LogicalStatement> statements) {
this.statements = statements;
}
/**
*
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation (of sub-statements)
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return false;
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.content.logic.operator;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* An operator that implements AND by evaluating sub-statements and only returning
* true if all sub-statements return true
*
* @author Kim Shepherd
* @version $Revision$
*/
public class And extends AbstractOperator {
/**
* Default constructor
*/
public And() {
super();
}
/**
* Constructor that accepts predefined list of statements as defined in item-filters.xml
* @param statements List of logical statements
*/
And(List<LogicalStatement> statements) {
super(statements);
}
/**
* Return true if ALL statements return true
* Return false otherwise
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of AND
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
for (LogicalStatement statement : getStatements()) {
if (!statement.getResult(context, item)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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.content.logic.operator;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* An operator that implements NAND by negating an AND operation
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Nand extends AbstractOperator {
/**
* Default constructor
*/
public Nand() {
super();
}
/**
* Constructor that accepts predefined list of statements as defined in item-filters.xml
* @param statements List of logical statements
*/
public Nand(List<LogicalStatement> statements) {
super(statements);
}
/**
* Return true if the result of AND'ing all sub-statements is false (ie. a NOT(AND())
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of NAND
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return !(new And(getStatements()).getResult(context, item));
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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.content.logic.operator;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* An operator that implements NIR by negating an OR operation
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Nor extends AbstractOperator {
/**
* Default constructor
*/
public Nor() {
super();
}
/**
* Constructor that accepts predefined list of statements as defined in item-filters.xml
* @param statements List of logical statements
*/
public Nor(List<LogicalStatement> statements) {
super(statements);
}
/**
* Return true if the result of OR'ing the sub-statements is false
* Return false otherwise
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of NOR
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return !(new Or(getStatements()).getResult(context, item));
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.content.logic.operator;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* An operator that implements NOT by simply negating a statement
* Note that this operator doesn't actually implement the 'AbstractOperator' interface because
* we only want one sub-statement. So it's actually just a simple implementation of LogicalStatement.
* Not can have one sub-statement only, while and, or, nor, ... can have multiple sub-statements.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Not implements LogicalStatement {
private LogicalStatement statement;
/**
* Get sub-statement (note: singular! even though we keep the method name) for this operator
* @return list of sub-statements
*/
public LogicalStatement getStatements() {
return statement;
}
/**
* Set sub-statement (note: singular!) for this operator, as defined in item-filters.xml
* @param statement a single statement to apply to NOT operation
*/
public void setStatements(LogicalStatement statement) {
this.statement = statement;
}
/**
* Default constructor
*/
public Not() {}
/**
* Constructor that accepts predefined list of statements as defined in item-filters.xml
* @param statement Single logical statement
*/
public Not(LogicalStatement statement) {
this.statement = statement;
}
/**
* Return true if the result of the sub-statement is false
* Return false otherwise
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of NOT
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return !statement.getResult(context, item);
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.content.logic.operator;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* An operator that implements OR by evaluating sub-statements and returns
* true if one or more sub-statements return true
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Or extends AbstractOperator {
/**
* Default constructor
*/
public Or() {
super();
}
/**
* Constructor that accepts predefined list of statements as defined in item-filters.xml
* @param statements List of logical statements
*/
public Or(List<LogicalStatement> statements) {
super(statements);
}
/**
* Return true if any sub-statement returns true
* Return false otherwise
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of OR
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
for (LogicalStatement statement : getStatements()) {
if (statement.getResult(context, item)) {
return true;
}
}
return false;
}
}

View File

@@ -183,7 +183,7 @@ public class PackageUtils {
Item item, Collection collection)
throws SQLException, IOException, AuthorizeException {
if (license == null) {
license = collection.getLicenseCollection();
license = collectionService.getLicense(collection);
}
InputStream lis = new ByteArrayInputStream(license.getBytes());

View File

@@ -0,0 +1,150 @@
/**
* 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.ctask.general;
import java.io.IOException;
import java.sql.SQLException;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.curate.AbstractCurationTask;
import org.dspace.curate.Curator;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.doi.DOIIdentifierNotApplicableException;
import org.dspace.utils.DSpace;
/**
* This curation task will register a DOI for an item, optionally ignoring any logical filtering applied
* to normal identifier registration and DOI service operation.
*
* @author Kim Shepherd
*/
public class RegisterDOI extends AbstractCurationTask {
// Curation task status
private int status = Curator.CURATE_SUCCESS;
// The skipFilter boolean has a default value of 'true', as per intended operation
private boolean skipFilter = true;
// The distributed boolean has a default value of 'false' for safest operation
private boolean distributed = false;
// Prefix for configuration module
private static final String PLUGIN_PREFIX = "doi-curation";
// Logger
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegisterDOI.class);
// DOI provider
private DOIIdentifierProvider provider;
/**
* Initialise the curation task and read configuration, instantiate the DOI provider
*/
@Override
public void init(Curator curator, String taskId) throws IOException {
super.init(curator, taskId);
// Get 'skip filter' behaviour from configuration, with a default value of 'true'
skipFilter = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".skip-filter", true);
// Get distribution behaviour from configuration, with a default value of 'false'
distributed = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".distributed", false);
log.debug("PLUGIN_PREFIX = " + PLUGIN_PREFIX + ", skipFilter = " + skipFilter +
", distributed = " + distributed);
// Instantiate DOI provider singleton
provider = new DSpace().getSingletonService(DOIIdentifierProvider.class);
}
/**
* Override the abstract 'perform' method to either distribute, or perform single-item
* depending on configuration. By default, the task is *not* distributed, since that could be unsafe
* and the original purpose of this task is to essentially implement a "Register DOI" button on the Edit Item page.
* @param dso DSpaceObject for which to register a DOI (must be item)
* @return status indicator
* @throws IOException
*/
@Override
public int perform(DSpaceObject dso) throws IOException {
// Check distribution configuration
if (distributed) {
// This task is configured for distributed use. Call distribute() and let performItem handle
// the main processing.
distribute(dso);
} else {
// This task is NOT configured for distributed use (default). Instead process a single item directly
if (dso instanceof Item) {
Item item = (Item) dso;
performRegistration(item);
} else {
log.warn("DOI registration attempted on non-item DSpace Object: " + dso.getID());
}
return status;
}
return status;
}
/**
* This is called when the task is distributed (ie. called on a set of items or over a whole structure)
* @param item the DSpace Item
*/
@Override
protected void performItem(Item item) {
performRegistration(item);
}
/**
* Shared 'perform' code between perform() and performItem() - a curation wrapper for the register() method
* @param item the item for which to register a DOI
*/
private void performRegistration(Item item) {
// Request DOI registration and report results
String doi = register(item);
String result = "DOI registration task performed on " + item.getHandle() + ".";
if (doi != null) {
result += " DOI: (" + doi + ")";
} else {
result += " DOI was null, either item was filtered or an error was encountered.";
}
setResult(result);
report(result);
}
/**
* Perform the DOIIdentifierProvider.register call, with skipFilter passed as per config and defaults
* @param item The item for which to register a DOI
*/
private String register(Item item) {
String doi = null;
// Attempt DOI registration and report successes and failures
try {
log.debug("Registering DOI with skipFilter = " + skipFilter);
doi = provider.register(Curator.curationContext(), item, skipFilter);
if (doi != null) {
String message = "New DOI minted in database for item " + item.getHandle() + ": " + doi
+ ". This DOI will be registered online with the DOI provider when the queue is next run";
report(message);
} else {
log.error("Got a null DOI after registering...");
}
} catch (SQLException e) {
// Exception obtaining context
log.error("Error obtaining curator context: " + e.getMessage());
status = Curator.CURATE_ERROR;
} catch (DOIIdentifierNotApplicableException e) {
// Filter returned 'false' so DOI was not registered. This is normal behaviour when filter is running.
log.info("Item was filtered from DOI registration: " + e.getMessage());
String message = "Item " + item.getHandle() + " was skipped from DOI registration because it matched " +
"the item filter configured in identifier-services.xml.";
report(message);
status = Curator.CURATE_SUCCESS;
} catch (IdentifierException e) {
// Any other identifier exception is probably a true error
log.error("Error registering identifier: " + e.getMessage());
status = Curator.CURATE_ERROR;
}
return doi;
}
}

View File

@@ -37,7 +37,6 @@ import org.hibernate.proxy.HibernateProxyHelper;
* Class representing an e-person.
*
* @author David Stuve
* @version $Revision$
*/
@Entity
@Cacheable
@@ -381,6 +380,13 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport {
return digestAlgorithm;
}
/**
* Store the digest algorithm used to hash the password. You should also
* set the {@link setPassword password hash} and the
* {@link setDigestAlgorithm digest algorithm}.
*
* @param digestAlgorithm
*/
void setDigestAlgorithm(String digestAlgorithm) {
this.digestAlgorithm = digestAlgorithm;
}
@@ -389,6 +395,13 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport {
return salt;
}
/**
* Store the salt used when hashing the password. You should also set the
* {@link setPassword password hash} and the {@link setDigestAlgorithm
* digest algorithm}.
*
* @param salt
*/
void setSalt(String salt) {
this.salt = salt;
}
@@ -397,6 +410,12 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport {
return password;
}
/**
* Store the <strong>hash of a</strong> password. You should also set the
* {@link setSalt salt} and the {@link setDigestAlgorithm digest algorithm}.
*
* @param password
*/
void setPassword(String password) {
this.password = password;
}

View File

@@ -13,6 +13,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -28,6 +29,8 @@ import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.util.ConsoleService;
import org.dspace.util.ConsoleServiceImpl;
public class EPersonCLITool {
@@ -57,13 +60,22 @@ public class EPersonCLITool {
private static final Option OPT_NEW_EMAIL = new Option("i", "newEmail", true, "new email address");
private static final Option OPT_NEW_NETID = new Option("I", "newNetid", true, "new network ID");
private static final Option OPT_NEW_PASSWORD
= new Option("w", "newPassword", false, "prompt for new password");
private static final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
static final String ERR_PASSWORD_EMPTY = "The new password may not be empty.";
static final String ERR_PASSWORD_NOMATCH = "Passwords do not match. Password not set";
private static final EPersonService ePersonService
= EPersonServiceFactory.getInstance().getEPersonService();
private static ConsoleService consoleService
= new ConsoleServiceImpl();
/**
* Default constructor
*/
private EPersonCLITool() { }
EPersonCLITool() { }
/**
* Tool for manipulating user accounts.
@@ -110,7 +122,6 @@ public class EPersonCLITool {
new HelpFormatter().printHelp("user [options]", globalOptions);
context.abort();
status = 1;
throw new IllegalArgumentException();
}
if (context.isValid()) {
@@ -120,6 +131,8 @@ public class EPersonCLITool {
System.err.println(ex.getMessage());
}
}
System.exit(status);
}
/**
@@ -177,11 +190,11 @@ public class EPersonCLITool {
EPerson eperson = null;
try {
eperson = ePersonService.create(context);
} catch (SQLException ex) {
} catch (SQLException | AuthorizeException ex) {
context.abort();
System.err.println(ex.getMessage());
return 1;
} catch (AuthorizeException ex) { /* XXX SNH */ }
}
eperson.setCanLogIn(true);
eperson.setSelfRegistered(false);
@@ -204,11 +217,11 @@ public class EPersonCLITool {
try {
ePersonService.update(context, eperson);
System.out.printf("Created EPerson %s\n", eperson.getID().toString());
} catch (SQLException ex) {
} catch (SQLException | AuthorizeException ex) {
context.abort();
System.err.println(ex.getMessage());
return 1;
} catch (AuthorizeException ex) { /* XXX SNH */ }
}
return 0;
}
@@ -315,6 +328,7 @@ public class EPersonCLITool {
options.addOption(OPT_CAN_LOGIN);
options.addOption(OPT_NEW_EMAIL);
options.addOption(OPT_NEW_NETID);
options.addOption(OPT_NEW_PASSWORD);
options.addOption("h", "help", false, "explain --modify options");
@@ -334,11 +348,14 @@ public class EPersonCLITool {
// Modify!
EPerson eperson = null;
String userName = null;
try {
if (command.hasOption(OPT_NETID.getOpt())) {
eperson = ePersonService.findByNetid(context, command.getOptionValue(OPT_NETID.getOpt()));
userName = command.getOptionValue(OPT_NETID.getOpt());
eperson = ePersonService.findByNetid(context, userName);
} else if (command.hasOption(OPT_EMAIL.getOpt())) {
eperson = ePersonService.findByEmail(context, command.getOptionValue(OPT_EMAIL.getOpt()));
userName = command.getOptionValue(OPT_EMAIL.getOpt());
eperson = ePersonService.findByEmail(context, userName);
} else {
System.err.println("No EPerson selected");
return 1;
@@ -361,6 +378,25 @@ public class EPersonCLITool {
eperson.setNetid(command.getOptionValue(OPT_NEW_NETID.getOpt()));
modified = true;
}
if (command.hasOption(OPT_NEW_PASSWORD.getOpt())) {
char[] password1 = consoleService.readPassword(
"Enter new password for user '%s': ", userName);
char[] password2 = consoleService.readPassword(
"Enter new password again to verify: ");
if (password1.length <= 0 || password2.length <= 0) {
System.err.println(ERR_PASSWORD_EMPTY);
} else if (Arrays.equals(password1, password2)) {
PasswordHash newHashedPassword = new PasswordHash(String.valueOf(password1));
Arrays.fill(password1, '\0'); // Obliterate cleartext passwords
Arrays.fill(password2, '\0');
eperson.setPassword(newHashedPassword.getHashString());
eperson.setSalt(newHashedPassword.getSaltString());
eperson.setDigestAlgorithm(newHashedPassword.getAlgorithm());
modified = true;
} else {
System.err.println(ERR_PASSWORD_NOMATCH);
}
}
if (command.hasOption(OPT_GIVENNAME.getOpt())) {
eperson.setFirstName(context, command.getOptionValue(OPT_GIVENNAME.getOpt()));
modified = true;
@@ -387,15 +423,16 @@ public class EPersonCLITool {
eperson.setCanLogIn(Boolean.valueOf(command.getOptionValue(OPT_CAN_LOGIN.getOpt())));
modified = true;
}
if (modified) {
try {
ePersonService.update(context, eperson);
System.out.printf("Modified EPerson %s\n", eperson.getID().toString());
} catch (SQLException ex) {
} catch (SQLException | AuthorizeException ex) {
context.abort();
System.err.println(ex.getMessage());
return 1;
} catch (AuthorizeException ex) { /* XXX SNH */ }
}
} else {
System.out.println("No changes.");
}
@@ -407,6 +444,7 @@ public class EPersonCLITool {
/**
* Command to list known EPersons.
*/
@SuppressWarnings("unused")
private static int cmdList(Context context, String[] argv) {
// XXX ideas:
// specific user/netid
@@ -427,4 +465,13 @@ public class EPersonCLITool {
return 0;
}
/**
* Replace the ConsoleService for testing.
*
* @param service new ConsoleService to be used henceforth.
*/
void setConsoleService(ConsoleService service) {
consoleService = service;
}
}

View File

@@ -19,11 +19,14 @@ import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
import org.dspace.identifier.doi.DOIIdentifierNotApplicableException;
import org.dspace.identifier.service.DOIService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,8 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired;
*
* @author Pascal-Nicolas Becker
*/
public class DOIIdentifierProvider
extends IdentifierProvider {
public class DOIIdentifierProvider extends FilteredIdentifierProvider {
private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class);
/**
@@ -87,6 +89,11 @@ public class DOIIdentifierProvider
@Autowired(required = true)
protected ItemService itemService;
protected Filter filterService;
/**
* Empty / default constructor for Spring
*/
protected DOIIdentifierProvider() {
}
@@ -103,6 +110,10 @@ public class DOIIdentifierProvider
*/
private String NAMESPACE_SEPARATOR;
/**
* Get DOI prefix from configuration
* @return a String containing the DOI prefix
*/
protected String getPrefix() {
if (null == this.PREFIX) {
this.PREFIX = this.configurationService.getProperty(CFG_PREFIX);
@@ -116,6 +127,10 @@ public class DOIIdentifierProvider
return this.PREFIX;
}
/**
* Get namespace separator from configuration
* @return a String containing the namespace separator
*/
protected String getNamespaceSeparator() {
if (null == this.NAMESPACE_SEPARATOR) {
this.NAMESPACE_SEPARATOR = this.configurationService.getProperty(CFG_NAMESPACE_SEPARATOR);
@@ -126,11 +141,27 @@ public class DOIIdentifierProvider
return this.NAMESPACE_SEPARATOR;
}
/**
* Set the DOI connector, which is the component that commuincates with the remote registration service
* (eg. DataCite, EZID, Crossref)
* Spring will use this setter to set the DOI connector from the configured property in identifier-services.xml
*
* @param connector a DOIConnector
*/
@Autowired(required = true)
public void setDOIConnector(DOIConnector connector) {
this.connector = connector;
}
/**
* Set the Filter to use when testing items to see if a DOI should be registered
* Spring will use this setter to set the filter from the configured property in identifier-services.xml
* @param filterService - an object implementing the org.dspace.content.logic.Filter interface
*/
public void setFilterService(Filter filterService) {
this.filterService = filterService;
}
/**
* This identifier provider supports identifiers of type
* {@link org.dspace.identifier.DOI}.
@@ -164,23 +195,65 @@ public class DOIIdentifierProvider
return true;
}
/**
* Register a new identifier for a given DSpaceObject, never skipping or ignoring any configured filter
* @param context - DSpace context
* @param dso - DSpaceObject to use for identifier registration
* @return identifier
* @throws IdentifierException
*/
@Override
public String register(Context context, DSpaceObject dso)
throws IdentifierException {
return register(context, dso, false);
}
/**
* Register a specified DOI for a given DSpaceObject, never skipping or ignoring any configured filter
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new DOI
* @param identifier - String containing the identifier to register
* @throws IdentifierException
*/
@Override
public void register(Context context, DSpaceObject dso, String identifier)
throws IdentifierException {
register(context, dso, identifier, false);
}
/**
* Register a new DOI for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new DOI
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @throws IdentifierException
*/
@Override
public String register(Context context, DSpaceObject dso, boolean skipFilter)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
return null;
}
String doi = mint(context, dso);
String doi = mint(context, dso, skipFilter);
// register tries to reserve doi if it's not already.
// So we don't have to reserve it here.
register(context, dso, doi);
register(context, dso, doi, skipFilter);
return doi;
}
/**
* Register a specified DOI for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new DOI
* @param identifier - String containing the DOI to register
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @throws IdentifierException
*/
@Override
public void register(Context context, DSpaceObject dso, String identifier)
public void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
@@ -191,7 +264,7 @@ public class DOIIdentifierProvider
// search DOI in our db
try {
doiRow = loadOrCreateDOI(context, dso, doi);
doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
} catch (SQLException ex) {
log.error("Error in databse connection: " + ex.getMessage());
throw new RuntimeException("Error in database conncetion.", ex);
@@ -234,6 +307,22 @@ public class DOIIdentifierProvider
*/
@Override
public void reserve(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException {
reserve(context, dso, identifier, false);
}
/**
* Reserve a specified DOI for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
@Override
public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
throws IdentifierException, IllegalArgumentException {
String doi = doiService.formatIdentifier(identifier);
DOI doiRow = null;
@@ -241,7 +330,7 @@ public class DOIIdentifierProvider
try {
// if the doi is in our db already loadOrCreateDOI just returns.
// if it is not loadOrCreateDOI safes the doi.
doiRow = loadOrCreateDOI(context, dso, doi);
doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
} catch (SQLException sqle) {
throw new RuntimeException(sqle);
}
@@ -258,14 +347,37 @@ public class DOIIdentifierProvider
}
}
/**
* Perform the actual online / API interaction required to reserve the DOI online
* always applying filters if they are configured
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void reserveOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
reserveOnline(context, dso, identifier, false);
}
/**
* Perform the actual online / API interaction required to reserve the DOI online
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void reserveOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
throws IdentifierException, IllegalArgumentException, SQLException {
String doi = doiService.formatIdentifier(identifier);
// get TableRow and ensure DOI belongs to dso regarding our db
DOI doiRow = loadOrCreateDOI(context, dso, doi);
DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
if (DELETED.equals(doiRow.getStatus()) ||
TO_BE_DELETED.equals(doiRow.getStatus())) {
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to reserve a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
@@ -276,14 +388,41 @@ public class DOIIdentifierProvider
doiService.update(context, doiRow);
}
/**
* Perform the actual online / API interaction required to register the DOI online
* always applying filters if they are configured
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to register
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void registerOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
registerOnline(context, dso, identifier, false);
}
/**
* Perform the actual online / API interaction required to register the DOI online
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to register
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void registerOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
throws IdentifierException, IllegalArgumentException, SQLException {
log.debug("registerOnline: skipFilter is " + skipFilter);
String doi = doiService.formatIdentifier(identifier);
// get TableRow and ensure DOI belongs to dso regarding our db
DOI doiRow = loadOrCreateDOI(context, dso, doi);
DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
if (DELETED.equals(doiRow.getStatus()) ||
TO_BE_DELETED.equals(doiRow.getStatus())) {
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to register a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
@@ -294,7 +433,7 @@ public class DOIIdentifierProvider
} catch (DOIIdentifierException die) {
// do we have to reserve DOI before we can register it?
if (die.getCode() == DOIIdentifierException.RESERVE_FIRST) {
this.reserveOnline(context, dso, identifier);
this.reserveOnline(context, dso, identifier, skipFilter);
connector.registerDOI(context, dso, doi);
} else {
throw die;
@@ -314,13 +453,35 @@ public class DOIIdentifierProvider
doiService.update(context, doiRow);
}
/**
* Update metadata for a registered object
* If the DOI for hte item already exists, *always* skip the filter since it should only be used for
* allowing / disallowing reservation and registration, not metadata updates or deletions
*
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void updateMetadata(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
String doi = doiService.formatIdentifier(identifier);
DOI doiRow = loadOrCreateDOI(context, dso, doi);
if (DELETED.equals(doiRow.getStatus()) ||
TO_BE_DELETED.equals(doiRow.getStatus())) {
String doi = doiService.formatIdentifier(identifier);
boolean skipFilter = false;
if (doiService.findDOIByDSpaceObject(context, dso) != null) {
// We can skip the filter here since we know the DOI already exists for the item
log.debug("updateMetadata: found DOIByDSpaceObject: " +
doiService.findDOIByDSpaceObject(context, dso).getDoi());
skipFilter = true;
}
DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to register a DOI that "
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
@@ -338,6 +499,18 @@ public class DOIIdentifierProvider
doiService.update(context, doiRow);
}
/**
* Update metadata for a registered object in the DOI Connector to update the agency records
* If the DOI for hte item already exists, *always* skip the filter since it should only be used for
* allowing / disallowing reservation and registration, not metadata updates or deletions
*
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void updateMetadataOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, SQLException {
String doi = doiService.formatIdentifier(identifier);
@@ -348,12 +521,10 @@ public class DOIIdentifierProvider
doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length()));
} catch (SQLException sqle) {
log.warn("SQLException while searching a DOI in our db.", sqle);
throw new RuntimeException("Unable to retrieve information about " +
"a DOI out of database.", sqle);
throw new RuntimeException("Unable to retrieve information about a DOI out of database.", sqle);
}
if (null == doiRow) {
log.error("Cannot update metadata for DOI {}: unable to find it in "
+ "our db.", doi);
log.error("Cannot update metadata for DOI {}: unable to find it in our db.", doi);
throw new DOIIdentifierException("Unable to find DOI.",
DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
@@ -368,8 +539,7 @@ public class DOIIdentifierProvider
DOIIdentifierException.MISMATCH);
}
if (DELETED.equals(doiRow.getStatus()) ||
TO_BE_DELETED.equals(doiRow.getStatus())) {
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to update the metadata"
+ "of a DOI that is marked as DELETED.",
DOIIdentifierException.DOI_IS_DELETED);
@@ -388,38 +558,66 @@ public class DOIIdentifierProvider
doiService.update(context, doiRow);
}
/**
* Mint a new DOI in DSpace - this is usually the first step of registration
* Always apply filters if they are configured
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @return a String containing the new identifier
* @throws IdentifierException
*/
@Override
public String mint(Context context, DSpaceObject dso)
throws IdentifierException {
return mint(context, dso, false);
}
/**
* Mint a new DOI in DSpace - this is usually the first step of registration
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @param skipFilter - boolean indicating whether to skip any filtering of items before minting.
* @return a String containing the new identifier
* @throws IdentifierException
*/
@Override
public String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException {
String doi = null;
try {
doi = getDOIByObject(context, dso);
} catch (SQLException e) {
log.error("Error while attemping to retrieve information about a DOI for "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso
.getID() + ".");
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + ".");
throw new RuntimeException("Error while attempting to retrieve " +
"information about a DOI for " + contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso) +
"information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", e);
}
if (null == doi) {
try {
DOI doiRow = loadOrCreateDOI(context, dso, null);
DOI doiRow = loadOrCreateDOI(context, dso, null, skipFilter);
doi = DOI.SCHEME + doiRow.getDoi();
} catch (SQLException e) {
log.error("Error while creating new DOI for Object of " +
"ResourceType {} with id {}.", dso.getType(), dso.getID());
throw new RuntimeException("Error while attempting to create a " +
"new DOI for " + contentServiceFactory.getDSpaceObjectService(dso)
.getTypeText(dso) + " with ID " +
"new DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " +
dso.getID() + ".", e);
}
}
return doi;
}
/**
* Resolve an identifier to a DSpaceObject, if it is registered
* @param context - DSpace context
* @param identifier - to be resolved.
* @param attributes - additional information for resolving {@code identifier}.
* @return a DSpaceObject identified by the identifier string
* @throws IdentifierNotFoundException
* @throws IdentifierNotResolvableException
*/
@Override
public DSpaceObject resolve(Context context, String identifier, String... attributes)
throws IdentifierNotFoundException, IdentifierNotResolvableException {
@@ -437,13 +635,20 @@ public class DOIIdentifierProvider
return dso;
} catch (SQLException sqle) {
log.error("SQLException while searching a DOI in our db.", sqle);
throw new RuntimeException("Unable to retrieve information about " +
"a DOI out of database.", sqle);
throw new RuntimeException("Unable to retrieve information about a DOI out of database.", sqle);
} catch (IdentifierException e) {
throw new IdentifierNotResolvableException(e);
}
}
/**
* Look up a DOI identifier for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject to look up
* @return a String containing the DOI
* @throws IdentifierNotFoundException
* @throws IdentifierNotResolvableException
*/
@Override
public String lookup(Context context, DSpaceObject dso)
throws IdentifierNotFoundException, IdentifierNotResolvableException {
@@ -455,15 +660,20 @@ public class DOIIdentifierProvider
}
if (null == doi) {
throw new IdentifierNotFoundException("No DOI for DSpaceObject of type "
+ contentServiceFactory.getDSpaceObjectService(dso)
.getTypeText(dso) + " with ID " + dso
.getID() + " found.");
throw new IdentifierNotFoundException("No DOI for DSpaceObject of type " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + " found.");
}
return doi;
}
/**
* Delete all DOIs for a DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject to have all its DOIs deleted
* @throws IdentifierException
*/
@Override
public void delete(Context context, DSpaceObject dso)
throws IdentifierException {
@@ -475,12 +685,11 @@ public class DOIIdentifierProvider
doi = getDOIByObject(context, dso);
}
} catch (SQLException ex) {
log.error("Error while attemping to retrieve information about a DOI for "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso
.getID() + ".", ex);
log.error("Error while attemping to retrieve information about a DOI for " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while attempting to retrieve " +
"information about a DOI for " + contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso) +
"information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", ex);
}
@@ -493,25 +702,30 @@ public class DOIIdentifierProvider
doi = getDOIOutOfObject(dso);
}
} catch (AuthorizeException ex) {
log.error("Error while removing a DOI out of the metadata of an "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso
.getID() + ".", ex);
throw new RuntimeException("Error while removing a DOI out of the "
+ "metadata of an " + contentServiceFactory.getDSpaceObjectService(dso)
.getTypeText(dso) + " with ID "
+ dso.getID() + ".", ex);
log.error("Error while removing a DOI out of the metadata of an " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while removing a DOI out of the metadata of an " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", ex);
} catch (SQLException ex) {
log.error("Error while removing a DOI out of the metadata of an "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso
.getID() + ".", ex);
throw new RuntimeException("Error while removing a DOI out of the "
+ "metadata of an " + contentServiceFactory.getDSpaceObjectService(dso)
.getTypeText(dso) + " with ID "
+ dso.getID() + ".", ex);
log.error("Error while removing a DOI out of the metadata of an " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", ex);
throw new RuntimeException("Error while removing a DOI out of the " +
"metadata of an " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) +
" with ID " + dso.getID() + ".", ex);
}
}
/**
* Delete a specific DOI for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject to be de-identified.
* @param identifier - String containing identifier to delete
* @throws IdentifierException
*/
@Override
public void delete(Context context, DSpaceObject dso, String identifier)
throws IdentifierException {
@@ -567,8 +781,13 @@ public class DOIIdentifierProvider
// DOIS. But it is possible to mark a DOI as "inactive".
}
public void deleteOnline(Context context, String identifier)
throws DOIIdentifierException {
/**
* Delete a specific DOI in the registration agency records via the DOI Connector
* @param context - DSpace context
* @param identifier - String containing identifier to delete
* @throws IdentifierException
*/
public void deleteOnline(Context context, String identifier) throws DOIIdentifierException {
String doi = doiService.formatIdentifier(identifier);
DOI doiRow = null;
@@ -583,8 +802,7 @@ public class DOIIdentifierProvider
DOIIdentifierException.DOI_DOES_NOT_EXIST);
}
if (!TO_BE_DELETED.equals(doiRow.getStatus())) {
log.error("This identifier: {} couldn't be deleted. "
+ "Delete it first from metadata.",
log.error("This identifier: {} couldn't be deleted. Delete it first from metadata.",
DOI.SCHEME + doiRow.getDoi());
throw new IllegalArgumentException("Couldn't delete this identifier:"
+ DOI.SCHEME + doiRow.getDoi()
@@ -603,13 +821,12 @@ public class DOIIdentifierProvider
/**
* Returns a DSpaceObject depending on its DOI.
*
* @param context The relevant DSpace Context.
* @param context the context
* @param identifier The DOI in a format that is accepted by
* {@link org.dspace.identifier.service.DOIService#formatIdentifier(String)}.
* @return Null if the DOI couldn't be found or the associated DSpaceObject.
* @throws SQLException if database error
* @throws DOIIdentifierException If {@code identifier} is null or an empty string.
* @throws IdentifierException If {@code identifier} is null or an empty string.
* @throws IllegalArgumentException If the identifier couldn't be recognized as DOI.
*/
public DSpaceObject getObjectByDOI(Context context, String identifier)
@@ -622,8 +839,7 @@ public class DOIIdentifierProvider
}
if (doiRow.getDSpaceObject() == null) {
log.error("Found DOI " + doi +
" in database, but no assigned Object could be found.");
log.error("Found DOI " + doi + " in database, but no assigned Object could be found.");
throw new IllegalStateException("Found DOI " + doi +
" in database, but no assigned Object could be found.");
}
@@ -640,8 +856,7 @@ public class DOIIdentifierProvider
* @return The DOI as String or null if DOI was not found.
* @throws SQLException if database error
*/
public String getDOIByObject(Context context, DSpaceObject dso)
throws SQLException {
public String getDOIByObject(Context context, DSpaceObject dso) throws SQLException {
// String sql = "SELECT * FROM Doi WHERE resource_type_id = ? " +
// "AND resource_id = ? AND ((status != ? AND status != ?) OR status IS NULL)";
@@ -651,13 +866,10 @@ public class DOIIdentifierProvider
}
if (doiRow.getDoi() == null) {
log.error("A DOI with an empty doi column was found in the database. DSO-Type: "
+ contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso
.getID() + ".");
throw new IllegalStateException("A DOI with an empty doi column " +
"was found in the database. DSO-Type: " + contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso) +
", ID: " + dso.getID() + ".");
log.error("A DOI with an empty doi column was found in the database. DSO-Type: " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso.getID() + ".");
throw new IllegalStateException("A DOI with an empty doi column was found in the database. DSO-Type: " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso.getID() + ".");
}
return DOI.SCHEME + doiRow.getDoi();
@@ -679,8 +891,31 @@ public class DOIIdentifierProvider
* DOI is registered for another object already.
*/
protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier)
throws SQLException, DOIIdentifierException {
throws SQLException, DOIIdentifierException, IdentifierNotApplicableException {
return loadOrCreateDOI(context, dso, doiIdentifier, false);
}
/**
* Load DOI from database, or create one if it doesn't yet exist
* We need to distinguish several cases. LoadOrCreate can be called with a specifid identifier to load or create.
* It can also be used to create a new unspecified identifier. In the latter case doiIdentifier is set null.
* If doiIdentifier is set, we know which doi we should try to load or create, but even in sucha situation
* we might be able to find it in the database or might have to create it.
*
* @param context - DSpace context
* @param dso - DSpaceObject to identify
* @param doiIdentifier - DOI to load or create (null to mint a new one)
* @param skipFilter - Whether or not to skip the filters for the checkMintable() check
* @return
* @throws SQLException
* @throws DOIIdentifierException
*/
protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter)
throws SQLException, DOIIdentifierException, IdentifierNotApplicableException {
DOI doi = null;
// Was an identifier specified that we shall try to load or create if it is not existing yet?
if (null != doiIdentifier) {
// we expect DOIs to have the DOI-Scheme except inside the doi table:
doiIdentifier = doiIdentifier.substring(DOI.SCHEME.length());
@@ -692,7 +927,7 @@ public class DOIIdentifierProvider
// doi was deleted, check resource type
if (doi.getResourceTypeId() != null
&& doi.getResourceTypeId() != dso.getType()) {
// doi was assigend to another resource type. Don't
// doi was assigned to another resource type. Don't
// reactivate it
throw new DOIIdentifierException("Cannot reassing "
+ "previously deleted DOI " + doiIdentifier
@@ -707,6 +942,7 @@ public class DOIIdentifierProvider
// reassign doi
// nothing to do here, doi will br reassigned after this
// if-else-if-else-...-block
// will check if a filter prohibits creation of DOIs after this if-else-block
}
} else {
// doi is assigned to a DSO; is it assigned to our specific dso?
@@ -721,6 +957,16 @@ public class DOIIdentifierProvider
}
}
// we did not find the doi in the database or shall reassign it. Before doing so, we should check if a
// filter is in place to prevent the creation of new DOIs for certain items.
if (skipFilter) {
log.warn("loadOrCreateDOI: Skipping default item filter");
} else {
// Find out if we're allowed to create a DOI
// throws an exception if creation of a new DOI is prohibited by a filter
checkMintable(context, dso);
}
// check prefix
if (!doiIdentifier.startsWith(this.getPrefix() + "/")) {
throw new DOIIdentifierException("Trying to create a DOI " +
@@ -732,7 +978,16 @@ public class DOIIdentifierProvider
doi = doiService.create(context);
}
} else {
// We need to generate a new DOI.
// We need to generate a new DOI. Before doing so, we should check if a
// filter is in place to prevent the creation of new DOIs for certain items.
if (skipFilter) {
log.warn("loadOrCreateDOI: Skipping default item filter");
} else {
// Find out if we're allowed to create a DOI
// throws an exception if creation of a new DOI is prohibited by a filter
checkMintable(context, dso);
}
doi = doiService.create(context);
doiIdentifier = this.getPrefix() + "/" + this.getNamespaceSeparator() +
doi.getID();
@@ -745,7 +1000,7 @@ public class DOIIdentifierProvider
try {
doiService.update(context, doi);
} catch (SQLException e) {
throw new RuntimeException("Cannot save DOI to databse for unkown reason.");
throw new RuntimeException("Cannot save DOI to database for unknown reason.");
}
return doi;
@@ -758,13 +1013,11 @@ public class DOIIdentifierProvider
* @return The DOI or null if no DOI was found.
* @throws DOIIdentifierException if identifier error
*/
public String getDOIOutOfObject(DSpaceObject dso)
throws DOIIdentifierException {
public String getDOIOutOfObject(DSpaceObject dso) throws DOIIdentifierException {
// FIXME
if (!(dso instanceof Item)) {
throw new IllegalArgumentException("We currently support DOIs for "
+ "Items only, not for " + contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso) + ".");
throw new IllegalArgumentException("We currently support DOIs for Items only, not for " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ".");
}
Item item = (Item) dso;
@@ -792,14 +1045,13 @@ public class DOIIdentifierProvider
throws SQLException, AuthorizeException, IdentifierException {
// FIXME
if (!(dso instanceof Item)) {
throw new IllegalArgumentException("We currently support DOIs for "
+ "Items only, not for " + contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso) + ".");
throw new IllegalArgumentException("We currently support DOIs for Items only, not for " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ".");
}
Item item = (Item) dso;
itemService
.addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, doiService.DOIToExternalForm(doi));
itemService.addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null,
doiService.DOIToExternalForm(doi));
try {
itemService.update(context, item);
} catch (SQLException | AuthorizeException ex) {
@@ -821,9 +1073,8 @@ public class DOIIdentifierProvider
throws AuthorizeException, SQLException, IdentifierException {
// FIXME
if (!(dso instanceof Item)) {
throw new IllegalArgumentException("We currently support DOIs for "
+ "Items only, not for " + contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso) + ".");
throw new IllegalArgumentException("We currently support DOIs for Items only, not for " +
contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ".");
}
Item item = (Item) dso;
@@ -841,4 +1092,31 @@ public class DOIIdentifierProvider
remainder);
itemService.update(context, item);
}
/**
* Checks to see if an item can have a DOI minted, using the configured logical filter
* @param context
* @param dso The item to be evaluated
* @throws DOIIdentifierNotApplicableException
*/
@Override
public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException {
// If the check fails, an exception will be thrown to be caught by the calling method
if (this.filterService != null && contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) {
try {
boolean result = filterService.getResult(context, (Item) dso);
log.debug("Result of filter for " + dso.getHandle() + " is " + result);
if (!result) {
throw new DOIIdentifierNotApplicableException("Item " + dso.getHandle() +
" was evaluated as 'false' by the item filter, not minting");
}
} catch (LogicalStatementException e) {
log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage());
throw new DOIIdentifierNotApplicableException(e);
}
} else {
log.debug("DOI Identifier Provider: filterService is null (ie. don't prevent DOI minting)");
}
}
}

View File

@@ -0,0 +1,92 @@
/**
* 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.identifier;
import java.sql.SQLException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This abstract class adds extra method signatures so that implementing IdentifierProviders can
* handle "skip filter" booleans, so that any configured filters can be skipped and DOI registration forced.
*
* @author Kim Shepherd
* @version $Revision$
*/
public abstract class FilteredIdentifierProvider extends IdentifierProvider {
protected Filter filterService;
/**
* Setter for spring to set the filter service from the property in configuration XML
* @param filterService - an object implementing the org.dspace.content.logic.Filter interface
*/
@Autowired
public void setFilterService(Filter filterService) {
this.filterService = filterService;
}
/**
* Register a new identifier for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject to use for identifier registration
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @return identifier
* @throws IdentifierException
*/
public abstract String register(Context context, DSpaceObject dso, boolean skipFilter)
throws IdentifierException;
/**
* Register a specified identifier for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @param identifier - String containing the identifier to register
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @throws IdentifierException
*/
public abstract void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
throws IdentifierException;
/**
* Reserve a specified identifier for a given DSpaceObject (eg. reserving a DOI online with a registration agency)
* @param context - DSpace context
* @param dso - DSpaceObject identified by this identifier
* @param identifier - String containing the identifier to reserve
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public abstract void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
throws IdentifierException, IllegalArgumentException, SQLException;
/**
* Mint a new identifier in DSpace - this is usually the first step of registration
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @param skipFilter - boolean indicating whether to skip any filtering of items before minting.
* @return a String containing the new identifier
* @throws IdentifierException
*/
public abstract String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException;
/**
* Check configured item filters to see if this identifier is allowed to be minted
* @param context - DSpace context
* @param dso - DSpaceObject to be inspected
* @throws IdentifierException
*/
public abstract void checkMintable(Context context, DSpaceObject dso) throws IdentifierException;
}

View File

@@ -0,0 +1,34 @@
/**
* 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.identifier;
/**
*
* Thrown when an identifier should not be applied to an item, eg. when it has been filtered by an item filter
*
*
* @author Kim Shepherd
*/
public class IdentifierNotApplicableException extends IdentifierException {
public IdentifierNotApplicableException() {
super();
}
public IdentifierNotApplicableException(String message) {
super(message);
}
public IdentifierNotApplicableException(String message, Throwable cause) {
super(message, cause);
}
public IdentifierNotApplicableException(Throwable cause) {
super(cause);
}
}

View File

@@ -66,10 +66,14 @@ public class IdentifierServiceImpl implements IdentifierService {
public void reserve(Context context, DSpaceObject dso)
throws AuthorizeException, SQLException, IdentifierException {
for (IdentifierProvider service : providers) {
try {
String identifier = service.mint(context, dso);
if (!StringUtils.isEmpty(identifier)) {
service.reserve(context, dso, identifier);
}
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not reserved (inapplicable): " + e.getMessage());
}
}
//Update our item
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
@@ -81,7 +85,11 @@ public class IdentifierServiceImpl implements IdentifierService {
// Next resolve all other services
for (IdentifierProvider service : providers) {
if (service.supports(identifier)) {
try {
service.reserve(context, dso, identifier);
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not reserved (inapplicable): " + e.getMessage());
}
}
}
//Update our item
@@ -94,7 +102,11 @@ public class IdentifierServiceImpl implements IdentifierService {
//We need to commit our context because one of the providers might require the handle created above
// Next resolve all other services
for (IdentifierProvider service : providers) {
try {
service.register(context, dso);
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not registered (inapplicable): " + e.getMessage());
}
}
//Update our item / collection / community
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
@@ -108,8 +120,12 @@ public class IdentifierServiceImpl implements IdentifierService {
boolean registered = false;
for (IdentifierProvider service : providers) {
if (service.supports(identifier)) {
try {
service.register(context, object, identifier);
registered = true;
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not registered (inapplicable): " + e.getMessage());
}
}
}
if (!registered) {
@@ -152,8 +168,7 @@ public class IdentifierServiceImpl implements IdentifierService {
if (!StringUtils.isEmpty(result)) {
if (log.isDebugEnabled()) {
try {
log.debug("Got an identifier from "
+ service.getClass().getCanonicalName() + ".");
log.debug("Got an identifier from " + service.getClass().getCanonicalName() + ".");
} catch (NullPointerException ex) {
log.debug(ex.getMessage(), ex);
}
@@ -178,7 +193,7 @@ public class IdentifierServiceImpl implements IdentifierService {
if (!identifiers.contains(handle)
&& !identifiers.contains("hdl:" + handle)
&& !identifiers.contains(handleService.getCanonicalForm(handle))) {
// The VerionedHandleIdentifierProvider gets loaded by default
// The VersionedHandleIdentifierProvider gets loaded by default
// it returns handles without any scheme (neither hdl: nor http:).
// If the VersionedHandleIdentifierProvider is not loaded,
// we adds the handle in way it would.
@@ -224,8 +239,7 @@ public class IdentifierServiceImpl implements IdentifierService {
}
@Override
public void delete(Context context, DSpaceObject dso)
throws AuthorizeException, SQLException, IdentifierException {
public void delete(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException {
for (IdentifierProvider service : providers) {
try {
service.delete(context, dso);

View File

@@ -222,7 +222,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
// Should never return null!
protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history)
throws AuthorizeException, SQLException, DOIIdentifierException {
throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException {
// Mint foreach new version an identifier like: 12345/100.versionNumber
// use the bare handle (g.e. 12345/100) for the first version.

View File

@@ -0,0 +1,36 @@
/**
* 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.identifier.doi;
import org.dspace.identifier.IdentifierNotApplicableException;
/**
*
* Thrown when an identifier should not be applied to an item, eg. when it has been filtered by an item filter
*
*
* @author Kim Shepherd
*/
public class DOIIdentifierNotApplicableException extends IdentifierNotApplicableException {
public DOIIdentifierNotApplicableException() {
super();
}
public DOIIdentifierNotApplicableException(String message) {
super(message);
}
public DOIIdentifierNotApplicableException(String message, Throwable cause) {
super(message, cause);
}
public DOIIdentifierNotApplicableException(Throwable cause) {
super(cause);
}
}

View File

@@ -61,7 +61,13 @@ public class DOIOrganiser {
protected ItemService itemService;
protected DOIService doiService;
protected ConfigurationService configurationService;
protected boolean skipFilter;
/**
* Constructor to be called within the main() method
* @param context - DSpace context
* @param provider - DOI identifier provider to use
*/
public DOIOrganiser(Context context, DOIIdentifierProvider provider) {
this.context = context;
this.provider = provider;
@@ -70,8 +76,13 @@ public class DOIOrganiser {
this.itemService = ContentServiceFactory.getInstance().getItemService();
this.doiService = IdentifierServiceFactory.getInstance().getDOIService();
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
this.skipFilter = false;
}
/**
* Main command-line runner method as with other DSpace launcher commands
* @param args - the command line arguments to parse as parameters
*/
public static void main(String[] args) {
LOG.debug("Starting DOI organiser ");
@@ -83,7 +94,6 @@ public class DOIOrganiser {
DOIOrganiser organiser = new DOIOrganiser(context,
new DSpace().getSingletonService(DOIIdentifierProvider.class));
// run command line interface
runCLI(context, organiser, args);
@@ -97,7 +107,7 @@ public class DOIOrganiser {
}
public static void runCLI(Context context, DOIOrganiser organiser, String[] args) {
// initlize options
// initialize options
Options options = new Options();
options.addOption("h", "help", false, "Help");
@@ -115,6 +125,9 @@ public class DOIOrganiser {
options.addOption("q", "quiet", false,
"Turn the command line output off.");
options.addOption(null, "skip-filter", false,
"Skip the configured item filter when registering or reserving.");
Option registerDoi = Option.builder()
.longOpt("register-doi")
.hasArg()
@@ -157,7 +170,6 @@ public class DOIOrganiser {
options.addOption(delete);
// initialize parser
CommandLineParser parser = new DefaultParser();
CommandLine line = null;
@@ -170,7 +182,6 @@ public class DOIOrganiser {
System.exit(1);
}
// process options
// user asks for help
if (line.hasOption('h') || 0 == line.getOptions().length) {
@@ -192,9 +203,13 @@ public class DOIOrganiser {
}
DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService();
// Should we skip the filter?
if (line.hasOption("skip-filter")) {
System.out.println("Skipping the item filter");
organiser.skipFilter = true;
}
if (line.hasOption('s')) {
try {
List<DOI> dois = doiService
.getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_RESERVED));
@@ -214,7 +229,6 @@ public class DOIOrganiser {
}
if (line.hasOption('r')) {
try {
List<DOI> dois = doiService
.getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_REGISTERED));
@@ -229,11 +243,12 @@ public class DOIOrganiser {
} catch (SQLException ex) {
System.err.println("Error in database connection:" + ex.getMessage());
ex.printStackTrace(System.err);
} catch (DOIIdentifierException ex) {
System.err.println("Error registering DOI identifier:" + ex.getMessage());
}
}
if (line.hasOption('u')) {
try {
List<DOI> dois = doiService.getDOIsByStatus(context, Arrays.asList(
DOIIdentifierProvider.UPDATE_BEFORE_REGISTRATION,
@@ -255,7 +270,6 @@ public class DOIOrganiser {
}
if (line.hasOption('d')) {
try {
List<DOI> dois = doiService
.getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_DELETED));
@@ -277,7 +291,6 @@ public class DOIOrganiser {
}
}
if (line.hasOption("reserve-doi")) {
String identifier = line.getOptionValue("reserve-doi");
@@ -339,7 +352,14 @@ public class DOIOrganiser {
}
public void list(String processName, PrintStream out, PrintStream err, Integer... status) {
/**
* list DOIs queued for reservation or registration
* @param processName - process name for display
* @param out - output stream (eg. STDOUT)
* @param err - error output stream (eg. STDERR)
* @param status - status codes
*/
public void list(String processName, PrintStream out, PrintStream err, Integer ... status) {
String indent = " ";
if (null == out) {
out = System.out;
@@ -371,7 +391,14 @@ public class DOIOrganiser {
}
}
public void register(DOI doiRow) throws SQLException {
/**
* Register DOI with the provider
* @param doiRow - doi to register
* @param skipFilter - whether filters should be skipped before registration
* @throws SQLException
* @throws DOIIdentifierException
*/
public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIdentifierException {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
@@ -438,20 +465,49 @@ public class DOIOrganiser {
}
}
public void reserve(DOI doiRow) throws SQLException {
/**
* Register DOI with the provider, always applying (ie. never skipping) any configured filters
* @param doiRow - doi to register
* @throws SQLException
* @throws DOIIdentifierException
*/
public void register(DOI doiRow) throws SQLException, DOIIdentifierException {
if (this.skipFilter) {
System.out.println("Skipping the filter for " + doiRow.getDoi());
}
register(doiRow, this.skipFilter);
}
/**
* Reserve DOI with the provider, always applying (ie. never skipping) any configured filters
* @param doiRow - doi to reserve
* @throws SQLException
* @throws DOIIdentifierException
*/
public void reserve(DOI doiRow) {
if (this.skipFilter) {
System.out.println("Skipping the filter for " + doiRow.getDoi());
}
reserve(doiRow, this.skipFilter);
}
/**
* Reserve DOI with the provider
* @param doiRow - doi to reserve
* @throws SQLException
* @throws DOIIdentifierException
*/
public void reserve(DOI doiRow, boolean skipFilter) {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only.");
}
try {
provider.reserveOnline(context, dso,
DOI.SCHEME + doiRow.getDoi());
provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), skipFilter);
if (!quiet) {
System.out.println("This identifier : "
+ DOI.SCHEME + doiRow.getDoi()
+ " is successfully reserved.");
System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved.");
}
} catch (IdentifierException ex) {
if (!(ex instanceof DOIIdentifierException)) {
@@ -477,16 +533,14 @@ public class DOIOrganiser {
.codeToString(doiIdentifierException.getCode()), ex);
if (!quiet) {
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi());
}
} catch (IllegalArgumentException ex) {
LOG.error("Database table DOI contains a DOI that is not valid: "
+ DOI.SCHEME + doiRow.getDoi() + "!", ex);
if (!quiet) {
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi());
}
throw new IllegalStateException("Database table DOI contains a DOI "
+ " that is not valid: "
@@ -495,19 +549,21 @@ public class DOIOrganiser {
LOG.error("Error while trying to get data from database", ex);
if (!quiet) {
System.err.println("It wasn't possible to reserve this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi());
}
throw new RuntimeException("Error while trying to get data from database", ex);
}
}
/**
* Update metadata for a DOI
* @param doiRow - DOI to update
*/
public void update(DOI doiRow) {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currenty DSpace supports DOIs "
+ "for Items only.");
throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only.");
}
try {
@@ -541,8 +597,7 @@ public class DOIOrganiser {
.codeToString(doiIdentifierException.getCode()), ex);
if (!quiet) {
System.err.println("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
System.err.println("It wasn't possible to update this identifier: " + DOI.SCHEME + doiRow.getDoi());
}
} catch (IllegalArgumentException ex) {
@@ -550,8 +605,7 @@ public class DOIOrganiser {
+ DOI.SCHEME + doiRow.getDoi() + "!", ex);
if (!quiet) {
System.err.println("It wasn't possible to update this identifier: "
+ DOI.SCHEME + doiRow.getDoi());
System.err.println("It wasn't possible to update this identifier: " + DOI.SCHEME + doiRow.getDoi());
}
throw new IllegalStateException("Database table DOI contains a DOI "
@@ -562,8 +616,12 @@ public class DOIOrganiser {
}
}
public void delete(String identifier)
throws SQLException {
/**
* Delete a DOI
* @param identifier - DOI to delete
* @throws SQLException
*/
public void delete(String identifier) throws SQLException {
String doi = null;
DOI doiRow = null;
@@ -575,8 +633,7 @@ public class DOIOrganiser {
doi.substring(DOI.SCHEME.length()));
if (null == doiRow) {
throw new IllegalStateException("You specified a valid DOI,"
+ " that is not stored in our database.");
throw new IllegalStateException("You specified a valid DOI, that is not stored in our database.");
}
provider.deleteOnline(context, doi);
@@ -642,15 +699,14 @@ public class DOIOrganiser {
//Check if this Item has an Identifier, mint one if it doesn't
if (null == doiRow) {
doi = provider.mint(context, dso);
doi = provider.mint(context, dso, this.skipFilter);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
return doiRow;
}
return doiRow;
} else {
throw new IllegalStateException("You specified an ItemID, "
+ "that is not stored in our database.");
throw new IllegalStateException("You specified an ItemID, that is not stored in our database.");
}
}
@@ -667,7 +723,7 @@ public class DOIOrganiser {
doiRow = doiService.findDOIByDSpaceObject(context, dso);
if (null == doiRow) {
doi = provider.mint(context, dso);
doi = provider.mint(context, dso, this.skipFilter);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
}
@@ -680,8 +736,7 @@ public class DOIOrganiser {
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
if (null == doiRow) {
throw new IllegalStateException("You specified a valid DOI,"
+ " that is not stored in our database.");
throw new IllegalStateException("You specified a valid DOI, that is not stored in our database.");
}
} catch (DOIIdentifierException ex) {
// Identifier was not recognized as DOI.
@@ -699,6 +754,14 @@ public class DOIOrganiser {
return doiRow;
}
/**
* Send an alert email to the configured recipient when DOI operations encounter an error
* @param action - action being attempted (eg. reserve, register, update)
* @param dso - DSpaceObject associated with the DOI
* @param doi - DOI for this operation
* @param reason - failure reason or error message
* @throws IOException
*/
private void sendAlertMail(String action, DSpaceObject dso, String doi, String reason)
throws IOException {
String recipient = configurationService.getProperty("alert.recipient");
@@ -728,6 +791,9 @@ public class DOIOrganiser {
}
}
/**
* Set this runner to be in quiet mode, suppressing console output
*/
private void setQuiet() {
this.quiet = true;
}

View File

@@ -0,0 +1,17 @@
/**
* 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.util;
/**
* Make System.console mock-able for testing.
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public interface ConsoleService {
public char[] readPassword(String prompt, Object... args);
}

View File

@@ -0,0 +1,22 @@
/**
* 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.util;
/**
* Standard implementation of console IO using {@code System.console()}.
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class ConsoleServiceImpl
implements ConsoleService {
@Override
public char[] readPassword(String prompt, Object... args) {
return System.console().readPassword(prompt, args);
}
}

View File

@@ -0,0 +1,652 @@
/**
* 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.content.logic;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.dspace.AbstractUnitTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.condition.BitstreamCountCondition;
import org.dspace.content.logic.condition.Condition;
import org.dspace.content.logic.condition.InCollectionCondition;
import org.dspace.content.logic.condition.InCommunityCondition;
import org.dspace.content.logic.condition.IsWithdrawnCondition;
import org.dspace.content.logic.condition.MetadataValueMatchCondition;
import org.dspace.content.logic.condition.MetadataValuesMatchCondition;
import org.dspace.content.logic.condition.ReadableByGroupCondition;
import org.dspace.content.logic.operator.And;
import org.dspace.content.logic.operator.Nand;
import org.dspace.content.logic.operator.Nor;
import org.dspace.content.logic.operator.Not;
import org.dspace.content.logic.operator.Or;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.MetadataFieldService;
import org.dspace.content.service.MetadataValueService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Unit tests for logical filters, conditions and operators
* @author Kim Shepherd
*/
public class LogicalFilterTest extends AbstractUnitTest {
// Required services
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService();
protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService();
private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
private AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
// Logger
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LogicalFilterTest.class);
// Items and repository structure for testing
Community communityOne;
Community communityTwo;
Collection collectionOne;
Collection collectionTwo;
Item itemOne;
Item itemTwo;
Item itemThree;
// Some simple statement lists for testing
List<LogicalStatement> trueStatements;
List<LogicalStatement> trueFalseStatements;
List<LogicalStatement> falseStatements;
LogicalStatement trueStatementOne;
LogicalStatement falseStatementOne;
// Field and values used to set title metadata
String element = "title";
String qualifier = null;
MetadataField metadataField;
/**
* This method will be run before every test as per @Before. It will
* initialize resources required for the tests.
*
* Other methods can be annotated with @Before here or in subclasses
* but no execution order is guaranteed
*/
@Before
@Override
public void init() {
super.init();
try {
context.turnOffAuthorisationSystem();
// Set up logical statement lists for operator testing
setUpStatements();
// Set up DSpace resources for condition and filter testing
// Set up first community, collection and item
this.communityOne = communityService.create(null, context);
this.collectionOne = collectionService.create(context, communityOne);
WorkspaceItem workspaceItem = workspaceItemService.create(context, collectionOne, false);
this.itemOne = installItemService.installItem(context, workspaceItem);
// Add one bitstream to item one, but put it in THUMBNAIL bundle
bundleService.addBitstream(context, bundleService.create(context, itemOne, "THUMBNAIL"),
bitstreamService.create(context,
new ByteArrayInputStream("Item 1 Thumbnail 1".getBytes(StandardCharsets.UTF_8))));
// Set up second community, collection and item, and third item
this.communityTwo = communityService.create(null, context);
this.collectionTwo = collectionService.create(context, communityTwo);
// Item two
workspaceItem = workspaceItemService.create(context, collectionTwo, false);
this.itemTwo = installItemService.installItem(context, workspaceItem);
// Add two bitstreams to item two
Bundle bundleTwo = bundleService.create(context, itemTwo, "ORIGINAL");
bundleService.addBitstream(context, bundleTwo, bitstreamService.create(context,
new ByteArrayInputStream("Item 2 Bitstream 1".getBytes(StandardCharsets.UTF_8))));
bundleService.addBitstream(context, bundleTwo, bitstreamService.create(context,
new ByteArrayInputStream("Item 2 Bitstream 2".getBytes(StandardCharsets.UTF_8))));
// Item three
workspaceItem = workspaceItemService.create(context, collectionTwo, false);
this.itemThree = installItemService.installItem(context, workspaceItem);
// Add three bitstreams to item three
Bundle bundleThree = bundleService.create(context, itemThree, "ORIGINAL");
bundleService.addBitstream(context, bundleThree, bitstreamService.create(context,
new ByteArrayInputStream("Item 3 Bitstream 1".getBytes(StandardCharsets.UTF_8))));
bundleService.addBitstream(context, bundleThree, bitstreamService.create(context,
new ByteArrayInputStream("Item 3 Bitstream 2".getBytes(StandardCharsets.UTF_8))));
bundleService.addBitstream(context, bundleThree, bitstreamService.create(context,
new ByteArrayInputStream("Item 3 Bitstream 2".getBytes(StandardCharsets.UTF_8))));
// Withdraw the second item for later testing
itemService.withdraw(context, itemTwo);
// Initialise metadata field for later testing with both items
this.metadataField = metadataFieldService.findByElement(context,
MetadataSchemaEnum.DC.getName(), element, qualifier);
context.restoreAuthSystemState();
} catch (AuthorizeException | SQLException | IOException e) {
log.error("Error encountered during init", e);
fail("Error encountered during init: " + e.getMessage());
}
}
/**
* This method will be run after every test as per @After. It will
* clean resources initialized by the @Before methods.
*
* Other methods can be annotated with @After here or in subclasses
* but no execution order is guaranteed
*/
@After
@Override
public void destroy() {
context.turnOffAuthorisationSystem();
// Delete resources
try {
itemService.delete(context, itemOne);
itemService.delete(context, itemTwo);
itemService.delete(context, itemThree);
collectionService.delete(context, collectionOne);
collectionService.delete(context, collectionTwo);
communityService.delete(context, communityOne);
communityService.delete(context, communityTwo);
} catch (Exception e) {
// ignore
log.error("Error cleaning up test resources: " + e.getMessage());
}
context.restoreAuthSystemState();
// Set all class members to null
communityOne = null;
communityTwo = null;
collectionOne = null;
collectionTwo = null;
itemOne = null;
itemTwo = null;
itemThree = null;
trueStatements = null;
trueFalseStatements = null;
falseStatements = null;
trueStatementOne = null;
falseStatementOne = null;
element = null;
qualifier = null;
metadataField = null;
super.destroy();
}
/**
* Test the AND operator with simple lists of logical statements
*/
@Test
public void testAndOperator() {
// Blank operator
And and = new And();
// Try tests
try {
// Set to True, True (expect True)
and.setStatements(trueStatements);
assertTrue("AND operator did not return true for a list of true statements",
and.getResult(context, itemOne));
// Set to True, False (expect False)
and.setStatements(trueFalseStatements);
assertFalse("AND operator did not return false for a list of statements with at least one false",
and.getResult(context, itemOne));
// Set to False, False (expect False)
and.setStatements(falseStatements);
assertFalse("AND operator did not return false for a list of false statements",
and.getResult(context, itemOne));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the AND operator" + e.getMessage());
}
}
/**
* Test the OR operator with simple lists of logical statements
*/
@Test
public void testOrOperator() {
// Blank operator
Or or = new Or();
// Try tests
try {
// Set to True, True (expect True)
or.setStatements(trueStatements);
assertTrue("OR operator did not return true for a list of true statements",
or.getResult(context, itemOne));
// Set to True, False (expect True)
or.setStatements(trueFalseStatements);
assertTrue("OR operator did not return true for a list of statements with at least one false",
or.getResult(context, itemOne));
// Set to False, False (expect False)
or.setStatements(falseStatements);
assertFalse("OR operator did not return false for a list of false statements",
or.getResult(context, itemOne));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the OR operator" + e.getMessage());
}
}
/**
* Test the NAND operator with simple lists of logical statements
*/
@Test
public void testNandOperator() {
// Blank operator
Nand nand = new Nand();
// Try tests
try {
// Set to True, True (expect False)
nand.setStatements(trueStatements);
assertFalse("NAND operator did not return false for a list of true statements",
nand.getResult(context, itemOne));
// Set to True, False (expect True)
nand.setStatements(trueFalseStatements);
assertTrue("NAND operator did not return true for a list of statements with at least one false",
nand.getResult(context, itemOne));
// Set to False, False (expect True)
nand.setStatements(falseStatements);
assertTrue("NAND operator did not return true for a list of false statements",
nand.getResult(context, itemOne));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the NAND operator" + e.getMessage());
}
}
/**
* Test the NOR operator with simple lists of logical statements
*/
@Test
public void testNorOperator() {
// Blank operator
Nor nor = new Nor();
// Try tests
try {
// Set to True, True (expect False)
nor.setStatements(trueStatements);
assertFalse("NOR operator did not return false for a list of true statements",
nor.getResult(context, itemOne));
// Set to True, False (expect False)
nor.setStatements(trueFalseStatements);
assertFalse("NOR operator did not return false for a list of statements with a true and a false",
nor.getResult(context, itemOne));
// Set to False, False (expect True)
nor.setStatements(falseStatements);
assertTrue("NOR operator did not return true for a list of false statements",
nor.getResult(context, itemOne));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the NOR operator" + e.getMessage());
}
}
/**
* Test the NOT operator with simple individual true/false statements
*/
@Test
public void testNotOperator() {
// Blank operator
Not not = new Not();
// Try tests
try {
// Set to True (expect False)
not.setStatements(trueStatementOne);
assertFalse("NOT operator did not return false for a true statement",
not.getResult(context, itemOne));
// Set to False (expect True)
not.setStatements(falseStatementOne);
assertTrue("NOT operator did not return true for a false statement",
not.getResult(context, itemOne));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the NOT operator" + e.getMessage());
}
}
/**
* Test a simple filter with a single logical statement: the MetadataValueMatchCondition
* looking for a dc.title field beginning with "TEST", and an item that doesn't match this test
*/
@Test
public void testMetadataValueMatchCondition() {
try {
MetadataValue metadataValueOne = metadataValueService.create(context, itemOne, metadataField);
MetadataValue metadataValueTwo = metadataValueService.create(context, itemTwo, metadataField);
metadataValueOne.setValue("TEST title should match the condition");
metadataValueTwo.setValue("This title should not match the condition");
} catch (SQLException e) {
fail("Encountered SQL error creating metadata value on item: " + e.getMessage());
}
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
// Create condition to match pattern on dc.title metadata
Condition condition = new MetadataValueMatchCondition();
condition.setItemService(ContentServiceFactory.getInstance().getItemService());
Map<String, Object> parameters = new HashMap<>();
// Match on the dc.title field
parameters.put("field", "dc.title");
// "Starts with "TEST" (case sensitive)
parameters.put("pattern", "^TEST");
// Set up condition with these parameters and add it as the sole statement to the metadata filter
try {
condition.setParameters(parameters);
filter.setStatement(condition);
// Test the filter on the first item - expected outcome is true
assertTrue("itemOne unexpectedly did not match the 'dc.title starts with TEST' test",
filter.getResult(context, itemOne));
// Test the filter on the second item - expected outcome is false
assertFalse("itemTwo unexpectedly matched the 'dc.title starts with TEST' test",
filter.getResult(context, itemTwo));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the MetadataValueMatchCondition filter" + e.getMessage());
}
}
/**
* Test a simple filter with a single logical statement: the MetadataValuesMatchCondition
* looking for a dc.title field beginning with "TEST" or "ALSO", and an item that doesn't match this test
*/
@Test
public void testMetadataValuesMatchCondition() {
try {
MetadataValue metadataValueOne = metadataValueService.create(context, itemOne, metadataField);
MetadataValue metadataValueTwo = metadataValueService.create(context, itemTwo, metadataField);
MetadataValue metadataValueThree = metadataValueService.create(context, itemThree, metadataField);
metadataValueOne.setValue("TEST this title should match the condition");
metadataValueTwo.setValue("This title should match the condition, yEs");
metadataValueThree.setValue("This title should not match the condition");
} catch (SQLException e) {
fail("Encountered SQL error creating metadata value on item: " + e.getMessage());
}
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
// Create condition to match pattern on dc.title metadata
Condition condition = new MetadataValuesMatchCondition();
Map<String, Object> parameters = new HashMap<>();
// Match on the dc.title field
parameters.put("field", "dc.title");
List<String> patterns = new ArrayList<>();
// "Starts with "TEST" (case sensitive)
patterns.add("^TEST");
// "Ends with 'yes' (case insensitive)
patterns.add("(?i)yes$");
// Add the list of possible patterns
parameters.put("patterns", patterns);
// Alternate parameters to test for a field where the item has no values
Map<String, Object> missingParameters = new HashMap<>();
// Match on the dc.subject field - none of our test items have this field set
missingParameters.put("field", "dc.subject");
// Add a pattern to the missing parameters
missingParameters.put("patterns", new ArrayList<>().add("TEST"));
// Set up condition with these parameters and add it as the sole statement to the metadata filter
try {
condition.setParameters(parameters);
filter.setStatement(condition);
// Test the filter on the first item - expected outcome is true
assertTrue("itemOne unexpectedly did not match the " +
"'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemOne));
// Test the filter on the second item - expected outcome is true
assertTrue("itemTwo unexpectedly did not match the " +
"'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemTwo));
// Test the filter on the third item - expected outcome is false
assertFalse("itemThree unexpectedly matched the " +
"'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemThree));
// Set condition and filter to use the missing field instead
condition.setParameters(missingParameters);
filter.setStatement(condition);
// Test this updated filter against the first item - expected outcome is false
assertFalse("itemOne unexpectedly matched the 'dc.subject contains TEST' test" +
"(it has no dc.subject metadata value)", filter.getResult(context, itemOne));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the MetadataValuesMatchCondition filter" + e.getMessage());
}
}
/**
* Test a simple filter with a single logical statement: the InCollectionCondition
* looking for an item that is in collectionOne, and one that is not in collectionOne
*/
@Test
public void testInCollectionCondition() {
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
Condition condition = new InCollectionCondition();
Map<String, Object> parameters = new HashMap<>();
// Add collectionOne handle to the collections parameter - ie. we are testing to see if the item is
// in collectionOne only
List<String> collections = new ArrayList<>();
collections.add(collectionOne.getHandle());
parameters.put("collections", collections);
try {
// Set parameters and condition
condition.setParameters(parameters);
filter.setStatement(condition);
// Test the filter on the first item - this item is in collectionOne: expected outcome is true
assertTrue("itemOne unexpectedly did not match the 'item in collectionOne' test",
filter.getResult(context, itemOne));
// Test the filter on the second item - this item is NOT in collectionOne: expected outcome is false
assertFalse("itemTwo unexpectedly matched the 'item in collectionOne' test",
filter.getResult(context, itemTwo));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the InCollectionCondition filter" + e.getMessage());
}
}
/**
* Test a simple filter with a single logical statement: the InCommunityCondition
* looking for an item that is in communityOne, and one that is not in communityOne
*/
@Test
public void testInCommunityCondition() {
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
Condition condition = new InCommunityCondition();
condition.setItemService(ContentServiceFactory.getInstance().getItemService());
Map<String, Object> parameters = new HashMap<>();
// Add communitynOne handle to the communities parameter - ie. we are testing to see if the item is
// in communityOne only
List<String> communities = new ArrayList<>();
communities.add(communityOne.getHandle());
parameters.put("communities", communities);
try {
// Set parameters and condition
condition.setParameters(parameters);
filter.setStatement(condition);
// Test the filter on the first item - this item is in communityOne: expected outcome is true
assertTrue("itemOne unexpectedly did not match the 'item in communityOne' test",
filter.getResult(context, itemOne));
// Test the filter on the second item - this item is NOT in communityOne: expected outcome is false
assertFalse("itemTwo unexpectedly matched the 'item in communityOne' test",
filter.getResult(context, itemTwo));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the InCommunityCondition filter" + e.getMessage());
}
}
/**
* Test a simple filter with the IsWithdrawnCondition. During setup, itemTwo was withdrawn.
*/
@Test
public void testIsWithdrawnCondition() {
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
Condition condition = new IsWithdrawnCondition();
try {
condition.setItemService(ContentServiceFactory.getInstance().getItemService());
condition.setParameters(new HashMap<>());
filter.setStatement(condition);
// Test the filter on itemOne - this item is not withdrawn: expected outcome is false
assertFalse("itemOne unexpectedly matched the 'item is withdrawn' test",
filter.getResult(context, itemOne));
// Test the filter on itemTwo - this item was withdrawn in setup: expected outcome is true
assertTrue("itemTwo unexpectedly did NOT match the 'item is withdrawn' test",
filter.getResult(context, itemTwo));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the IsWithdrawnCondition filter" + e.getMessage());
}
}
/**
* Test a simple filter with the BitstreamCountCondition.
*/
@Test
public void testBitstreamCountCondition() {
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
Condition condition = new BitstreamCountCondition();
try {
condition.setItemService(ContentServiceFactory.getInstance().getItemService());
// Set parameters to check for items with at least 1 and at most 2 bitstreams in the ORIGINAL bundle
Map<String, Object> parameters = new HashMap<>();
parameters.put("bundle", "ORIGINAL");
parameters.put("min", String.valueOf(1));
parameters.put("max", String.valueOf(2));
condition.setParameters(parameters);
filter.setStatement(condition);
// Test the filter on itemOne - this item has one THUMBNAIL but zero ORIGINAL bitstreams: expect false
assertFalse("itemOne unexpectedly matched the '>=1 and <=2 ORIGINAL bitstreams' test" +
" (it has zero ORIGINAL bitstreams)", filter.getResult(context, itemOne));
// Test the filter on itemTwo - this item has two ORIGINAL bitstreams: expect true
assertTrue("itemTwo unexpectedly did NOT match the '>=1 and <=2 ORIGINAL bitstreams' test" +
" (it has 2 ORIGINAL bitstreams)", filter.getResult(context, itemTwo));
// Test the filter on itemTwo - this item has three ORIGINAL bitstreams: expect false
assertFalse("itemThree unexpectedly did NOT match the '>=1 and <=2 ORIGINAL bitstreams' test" +
" (it has 3 ORIGINAL bitstreams)", filter.getResult(context, itemThree));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the IsWithdrawnCondition filter: " + e.getMessage());
}
}
/**
* Test a simple filter using the ReadableByGroupCondition
*/
@Test
public void testReadableByGroupCondition() {
// Instantiate new filter for testing this condition
DefaultFilter filter = new DefaultFilter();
Condition condition = new ReadableByGroupCondition();
try {
condition.setItemService(ContentServiceFactory.getInstance().getItemService());
// Make item one readable by Test Group
try {
context.turnOffAuthorisationSystem();
Group g = groupService.create(context);
groupService.setName(g, "Test Group");
groupService.update(context, g);
authorizeService.addPolicy(context, itemOne, Constants.READ, g);
context.restoreAuthSystemState();
} catch (AuthorizeException | SQLException e) {
fail("Exception thrown adding group READ policy to item: " + itemOne + ": " + e.getMessage());
}
// Set parameters to check for items with Anonymous READ permission
Map<String, Object> parameters = new HashMap<>();
parameters.put("group", "Test Group");
parameters.put("action", "READ");
condition.setParameters(parameters);
filter.setStatement(condition);
// Test the filter on itemOne - this item was explicitly set with expected group READ policy
assertTrue("itemOne unexpectedly did not match the 'is readable by Test Group' test",
filter.getResult(context, itemOne));
// Test the filter on itemTwo - this item has no policies: expect false
assertFalse("itemTwo unexpectedly matched the 'is readable by Test Group' test",
filter.getResult(context, itemTwo));
} catch (LogicalStatementException e) {
log.error(e.getMessage());
fail("LogicalStatementException thrown testing the ReadableByGroup filter" + e.getMessage());
}
}
/**
* Set up some simple statements for testing out operators
*/
private void setUpStatements() {
// Simple lambdas to define statements
// The two class members are used elsewhere, as direct statements for NOT testing
trueStatementOne = (context, item) -> true;
LogicalStatement trueStatementTwo = (context, item) -> true;
falseStatementOne = (context, item) -> false;
LogicalStatement falseStatementTwo = (context, item) -> false;
// Create lists and add the statements
// True, True
trueStatements = new ArrayList<>();
trueStatements.add(trueStatementOne);
trueStatements.add(trueStatementTwo);
// True, False
trueFalseStatements = new ArrayList<>();
trueFalseStatements.add(trueStatementOne);
trueFalseStatements.add(falseStatementOne);
// False, False
falseStatements = new ArrayList<>();
falseStatements.add(falseStatementOne);
falseStatements.add(falseStatementTwo);
}
}

View File

@@ -0,0 +1,153 @@
/**
* 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.eperson;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import org.dspace.AbstractIntegrationTest;
import org.dspace.util.FakeConsoleServiceImpl;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
import org.junit.contrib.java.lang.system.SystemErrRule;
/**
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class EPersonCLIToolIT
extends AbstractIntegrationTest {
private static final String NEW_PASSWORD = "secret";
private static final String BAD_PASSWORD = "not secret";
// Handle System.exit() from unit under test.
@Rule
public final ExpectedSystemExit exit = ExpectedSystemExit.none();
// Capture System.err() output.
@Rule
public final SystemErrRule sysErr = new SystemErrRule().enableLog();
/**
* Test --modify --newPassword
* @throws Exception passed through.
*/
@Test
@SuppressWarnings("static-access")
public void testSetPassword()
throws Exception {
exit.expectSystemExitWithStatus(0);
System.out.println("main");
// Create a source of "console" input.
FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl();
consoleService.setPassword(NEW_PASSWORD.toCharArray());
// Make certain that we know the eperson's email and old password hash.
String email = eperson.getEmail();
String oldPasswordHash = eperson.getPassword();
// Instantiate the unit under test.
EPersonCLITool instance = new EPersonCLITool();
instance.setConsoleService(consoleService);
// Test!
String[] argv = {
"--modify",
"--email", email,
"--newPassword"
};
instance.main(argv);
String newPasswordHash = eperson.getPassword();
assertNotEquals("Password hash did not change", oldPasswordHash, newPasswordHash);
}
/**
* Test --modify --newPassword with an empty password
* @throws Exception passed through.
*/
@Test
@SuppressWarnings("static-access")
public void testSetEmptyPassword()
throws Exception {
exit.expectSystemExitWithStatus(0);
System.out.println("main");
// Create a source of "console" input.
FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl();
consoleService.setPassword(new char[0]);
// Make certain that we know the eperson's email and old password hash.
String email = eperson.getEmail();
String oldPasswordHash = eperson.getPassword();
// Instantiate the unit under test.
EPersonCLITool instance = new EPersonCLITool();
instance.setConsoleService(consoleService);
// Test!
String[] argv = {
"--modify",
"--email", email,
"--newPassword"
};
instance.main(argv);
String newPasswordHash = eperson.getPassword();
assertEquals("Password hash changed", oldPasswordHash, newPasswordHash);
String stderr = sysErr.getLog();
assertTrue("Standard error did not mention 'empty'",
stderr.contains(EPersonCLITool.ERR_PASSWORD_EMPTY));
}
/**
* Test --modify --newPassword with mismatched confirmation.
* This tests what happens when the user enters different strings at the
* first and second new-password prompts.
* @throws Exception passed through.
*/
@Test
@SuppressWarnings("static-access")
public void testSetMismatchedPassword()
throws Exception {
exit.expectSystemExitWithStatus(0);
System.out.println("main");
// Create a source of "console" input.
FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl();
consoleService.setPassword1(NEW_PASSWORD.toCharArray());
consoleService.setPassword2(BAD_PASSWORD.toCharArray());
// Make certain that we know the eperson's email and old password hash.
String email = eperson.getEmail();
String oldPasswordHash = eperson.getPassword();
// Instantiate the unit under test.
EPersonCLITool instance = new EPersonCLITool();
instance.setConsoleService(consoleService);
// Test!
String[] argv = {
"--modify",
"--email", email,
"--newPassword"
};
instance.main(argv);
String newPasswordHash = eperson.getPassword();
assertEquals("Password hash changed", oldPasswordHash, newPasswordHash);
String stderr = sysErr.getLog();
assertTrue("Standard error did not indicate password mismatch",
stderr.contains(EPersonCLITool.ERR_PASSWORD_NOMATCH));
}
}

View File

@@ -34,12 +34,15 @@ import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.DefaultFilter;
import org.dspace.content.logic.LogicalStatement;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
import org.dspace.identifier.doi.DOIIdentifierNotApplicableException;
import org.dspace.identifier.factory.IdentifierServiceFactory;
import org.dspace.identifier.service.DOIService;
import org.dspace.services.ConfigurationService;
@@ -125,6 +128,7 @@ public class DOIIdentifierProviderTest
provider.itemService = itemService;
provider.setConfigurationService(config);
provider.setDOIConnector(connector);
provider.setFilterService(null);
} catch (AuthorizeException ex) {
log.error("Authorization Error in init", ex);
fail("Authorization Error in init: " + ex.getMessage());
@@ -499,8 +503,8 @@ public class DOIIdentifierProviderTest
Item item = newItem();
String doi = null;
try {
// get a DOI:
doi = provider.mint(context, item);
// get a DOI (skipping any filters)
doi = provider.mint(context, item, true);
} catch (IdentifierException e) {
e.printStackTrace(System.err);
fail("Got an IdentifierException: " + e.getMessage());
@@ -530,6 +534,82 @@ public class DOIIdentifierProviderTest
assertEquals("Mint did not returned an existing DOI!", doi, retrievedDOI);
}
/**
* Test minting a DOI with a filter that always returns false and therefore never mints the DOI
*/
@Test
public void testMint_DOI_withNonMatchingFilter()
throws SQLException, AuthorizeException, IOException, IllegalAccessException, IdentifierException,
WorkflowException {
Item item = newItem();
boolean wasFiltered = false;
try {
// Temporarily set the provider to have a filter that always returns false for an item
// (therefore, the item should be 'filtered' out and not apply to this minting request)
DefaultFilter doiFilter = new DefaultFilter();
LogicalStatement alwaysFalse = (context, i) -> false;
doiFilter.setStatement(alwaysFalse);
provider.setFilterService(doiFilter);
// get a DOI with the method that applies filters by default
provider.mint(context, item);
} catch (DOIIdentifierNotApplicableException e) {
// This is what we wanted to see - we can return safely
wasFiltered = true;
} catch (IdentifierException e) {
e.printStackTrace();
fail("Got an IdentifierException: " + e.getMessage());
} finally {
// Set filter service back to null
provider.setFilterService(null);
}
// Fail the test if the filter didn't throw a "not applicable" exception
assertTrue("DOI minting attempt was not filtered by filter service", wasFiltered);
}
/**
* Test minting a DOI with a filter that always returns true and therefore allows the DOI to be minted
* (this should have hte same results as base testMint_DOI, but here we use an explicit filter rather than null)
*/
@Test
public void testMint_DOI_withMatchingFilter()
throws SQLException, AuthorizeException, IOException, IllegalAccessException, IdentifierException,
WorkflowException {
Item item = newItem();
String doi = null;
boolean wasFiltered = false;
try {
// Temporarily set the provider to have a filter that always returns true for an item
// (therefore, the item is allowed to have a DOI minted)
DefaultFilter doiFilter = new DefaultFilter();
LogicalStatement alwaysTrue = (context, i) -> true;
doiFilter.setStatement(alwaysTrue);
provider.setFilterService(doiFilter);
// get a DOI with the method that applies filters by default
doi = provider.mint(context, item);
} catch (DOIIdentifierNotApplicableException e) {
// This is what we wanted to see - we can return safely
wasFiltered = true;
} catch (IdentifierException e) {
e.printStackTrace();
fail("Got an IdentifierException: " + e.getMessage());
} finally {
provider.setFilterService(null);
}
// If the attempt was filtered, fail
assertFalse("DOI minting attempt was incorrectly filtered by filter service", wasFiltered);
// Continue with regular minting tests
assertNotNull("Minted DOI is null!", doi);
assertFalse("Minted DOI is empty!", doi.isEmpty());
try {
doiService.formatIdentifier(doi);
} catch (Exception e) {
e.printStackTrace();
fail("Minted an unrecognizable DOI: " + e.getMessage());
}
}
@Test
public void testReserve_DOI()
throws SQLException, SQLException, AuthorizeException, IOException,
@@ -584,7 +664,8 @@ public class DOIIdentifierProviderTest
IdentifierException, WorkflowException, IllegalAccessException {
Item item = newItem();
String doi = provider.register(context, item);
// Register, skipping the filter
String doi = provider.register(context, item, true);
// we want the created DOI to be returned in the following format:
// doi:10.<prefix>/<suffix>.

View File

@@ -0,0 +1,74 @@
/**
* 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.util;
/**
* A test version of ConsoleService which supplies any password input that we
* want.
*
* <p>This can return different passwords on even/odd calls, to test
* confirmation dialogs. See {@link setPassword1} and {@link setPassword2}.
* Use {@link setPassword} to set both identically.
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class FakeConsoleServiceImpl
implements ConsoleService {
private String prompt;
private Object[] args;
private char[] password1;
private char[] password2;
private int passwordCalls = 0;
@Override
public char[] readPassword(String prompt, Object... args) {
this.prompt = prompt;
this.args = args;
passwordCalls++;
if (passwordCalls % 2 != 0) {
return password1;
} else {
return password2;
}
}
public String getPasswordPrompt() {
return prompt;
}
public Object[] getArgs() {
return this.args;
}
/**
* Set both passwords identically.
* @param password the password to be returned each time.
*/
public void setPassword(char[] password) {
setPassword1(password);
setPassword2(password);
}
/**
* Set the password returned on odd calls to {@link readPassword}.
* @param password the password to be returned.
*/
public void setPassword1(char[] password) {
password1 = password;
}
/**
* Set the password returned on even calls to {@link readPassword},
* and reset the call counter.
* @param password the password to be returned.
*/
public void setPassword2(char[] password) {
password2 = password;
passwordCalls = 0;
}
}

View File

@@ -21,7 +21,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.matcher.MetadataFieldMatcher;
@@ -967,9 +970,13 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration
@Test
public void findAllPaginationTest() throws Exception {
// Determine number of metadata fields from database
int numberOfMdFields = ContentServiceFactory.getInstance().getMetadataFieldService().findAll(context).size();
List<MetadataField> alphabeticMdFields =
ContentServiceFactory.getInstance()
.getMetadataFieldService()
.findAll(context).stream()
.sorted(Comparator.comparing(mdf -> mdf.toString('.')))
.collect(Collectors.toList());
int numberOfMdFields = alphabeticMdFields.size();
// If we return 3 fields per page, determine number of pages we expect
int pageSize = 3;
@@ -983,9 +990,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration
.andExpect(content().contentType(contentType))
// Metadata fields are returned alphabetically. So, look for the first 3 alphabetically
.andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems(
MetadataFieldMatcher.matchMetadataFieldByKeys("creativework","datePublished", null),
MetadataFieldMatcher.matchMetadataFieldByKeys("creativework", "editor", null),
MetadataFieldMatcher.matchMetadataFieldByKeys("creativework", "keywords", null)
MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(0)),
MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(1)),
MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(2))
)))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/core/metadatafields?"),
@@ -1012,9 +1019,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration
.andExpect(content().contentType(contentType))
// Metadata fields are returned alphabetically. So, look for the next 3 alphabetically
.andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems(
MetadataFieldMatcher.matchMetadataFieldByKeys("creativework","publisher", null),
MetadataFieldMatcher.matchMetadataFieldByKeys("creativeworkseries", "issn", null),
MetadataFieldMatcher.matchMetadataFieldByKeys("dc", "contributor", null)
MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(3)),
MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(4)),
MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(5))
)))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/core/metadatafields?"),

View File

@@ -22,6 +22,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.io.IOException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -81,24 +82,14 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
getClient(token).perform(get("/api/system/scripts"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.scripts", containsInAnyOrder(
ScriptMatcher.matchScript(scriptConfigurations.get(0).getName(),
scriptConfigurations.get(0).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(1).getName(),
scriptConfigurations.get(1).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(),
scriptConfigurations.get(2).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(3).getName(),
scriptConfigurations.get(3).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(4).getName(),
scriptConfigurations.get(4).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(5).getName(),
scriptConfigurations.get(5).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(6).getName(),
scriptConfigurations.get(6).getDescription()),
ScriptMatcher.matchScript(scriptConfigurations.get(7).getName(),
scriptConfigurations.get(7).getDescription())
scriptConfigurations
.stream()
.map(scriptConfiguration -> ScriptMatcher.matchScript(
scriptConfiguration.getName(),
scriptConfiguration.getDescription()
))
.collect(Collectors.toList())
)));
}
@@ -113,18 +104,26 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void findAllScriptsPaginationTest() throws Exception {
List<ScriptConfiguration> alphabeticScripts =
scriptConfigurations.stream()
.sorted(Comparator.comparing(s -> s.getClass().getName()))
.collect(Collectors.toList());
int totalPages = scriptConfigurations.size();
int lastPage = totalPages - 1;
String token = getAuthToken(admin.getEmail(), password);
// NOTE: the scripts are always returned in alphabetical order by fully qualified class name.
getClient(token).perform(get("/api/system/scripts").param("size", "1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.scripts", Matchers.not(Matchers.hasItem(
ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(),
scriptConfigurations.get(2).getDescription())
ScriptMatcher.matchScript(alphabeticScripts.get(1).getName(),
alphabeticScripts.get(1).getDescription())
))))
.andExpect(jsonPath("$._embedded.scripts", hasItem(
ScriptMatcher.matchScript(scriptConfigurations.get(5).getName(),
scriptConfigurations.get(5).getDescription())
ScriptMatcher.matchScript(alphabeticScripts.get(0).getName(),
alphabeticScripts.get(0).getDescription())
)))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/system/scripts?"),
@@ -137,22 +136,22 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
Matchers.containsString("page=1"), Matchers.containsString("size=1"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/system/scripts?"),
Matchers.containsString("page=7"), Matchers.containsString("size=1"))))
Matchers.containsString("page=" + lastPage), Matchers.containsString("size=1"))))
.andExpect(jsonPath("$.page.size", is(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$.page.totalPages", is(8)))
.andExpect(jsonPath("$.page.totalElements", is(8)));
.andExpect(jsonPath("$.page.totalPages", is(totalPages)))
.andExpect(jsonPath("$.page.totalElements", is(totalPages)));
getClient(token).perform(get("/api/system/scripts").param("size", "1").param("page", "1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.scripts", hasItem(
ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(),
scriptConfigurations.get(2).getDescription())
ScriptMatcher.matchScript(alphabeticScripts.get(1).getName(),
alphabeticScripts.get(1).getDescription())
)))
.andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem(
ScriptMatcher.matchScript(scriptConfigurations.get(5).getName(),
scriptConfigurations.get(5).getDescription())
ScriptMatcher.matchScript(alphabeticScripts.get(0).getName(),
alphabeticScripts.get(0).getDescription())
))))
.andExpect(jsonPath("$._links.first.href", Matchers.allOf(
Matchers.containsString("/api/system/scripts?"),
@@ -168,11 +167,11 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
Matchers.containsString("page=2"), Matchers.containsString("size=1"))))
.andExpect(jsonPath("$._links.last.href", Matchers.allOf(
Matchers.containsString("/api/system/scripts?"),
Matchers.containsString("page=7"), Matchers.containsString("size=1"))))
Matchers.containsString("page=" + lastPage), Matchers.containsString("size=1"))))
.andExpect(jsonPath("$.page.size", is(1)))
.andExpect(jsonPath("$.page.number", is(1)))
.andExpect(jsonPath("$.page.totalPages", is(8)))
.andExpect(jsonPath("$.page.totalElements", is(8)));
.andExpect(jsonPath("$.page.totalPages", is(totalPages)))
.andExpect(jsonPath("$.page.totalElements", is(totalPages)));
}
@Test
@@ -183,7 +182,15 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$", ScriptMatcher
.matchMockScript(
scriptConfigurations.get(scriptConfigurations.size() - 1).getOptions())));
scriptConfigurations
.stream()
.filter(scriptConfiguration
-> scriptConfiguration.getName().equals("mock-script"))
.findAny()
.orElseThrow()
.getOptions()
)
));
}
@Test

View File

@@ -7,8 +7,6 @@
*/
package org.dspace.sword;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.util.List;
@@ -54,6 +52,12 @@ public class SWORDUrlManager {
private final ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
private String swordPath = configurationService.getProperty(
"sword-server.path", "sword");
private String dspaceUrl = configurationService.getProperty(
"dspace.server.url");
public SWORDUrlManager(SWORDConfiguration config, Context context) {
this.config = config;
this.context = context;
@@ -307,24 +311,12 @@ public class SWORDUrlManager {
String depositUrl = configurationService.getProperty(
"sword-server.servicedocument.url");
if (depositUrl == null || "".equals(depositUrl)) {
String dspaceUrl = configurationService.getProperty(
"dspace.server.url");
if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSWORDException(
"Unable to construct service document urls, due to missing/invalid " +
"config in sword.servicedocument.url and/or dspace.server.url");
}
try {
URL url = new URL(dspaceUrl);
depositUrl = new URL(url.getProtocol(), url.getHost(),
url.getPort(), "/sword/servicedocument").toString();
} catch (MalformedURLException e) {
throw new DSpaceSWORDException(
"Unable to construct service document urls, due to invalid dspace.server.url " +
e.getMessage(), e);
}
depositUrl = buildSWORDUrl("servicedocument");
}
return depositUrl;
}
@@ -350,24 +342,12 @@ public class SWORDUrlManager {
String depositUrl = configurationService.getProperty(
"sword-server.deposit.url");
if (depositUrl == null || "".equals(depositUrl)) {
String dspaceUrl = configurationService.getProperty(
"dspace.server.url");
if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSWORDException(
"Unable to construct deposit urls, due to missing/invalid config in " +
"sword.deposit.url and/or dspace.server.url");
}
try {
URL url = new URL(dspaceUrl);
depositUrl = new URL(url.getProtocol(), url.getHost(),
url.getPort(), "/sword/deposit").toString();
} catch (MalformedURLException e) {
throw new DSpaceSWORDException(
"Unable to construct deposit urls, due to invalid dspace.server.url " +
e.getMessage(), e);
}
depositUrl = buildSWORDUrl("deposit");
}
return depositUrl;
}
@@ -454,24 +434,12 @@ public class SWORDUrlManager {
String mlUrl = configurationService.getProperty(
"sword-server", "media-link.url");
if (StringUtils.isBlank(mlUrl)) {
String dspaceUrl = configurationService.getProperty(
"dspace.server.url");
if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSWORDException(
"Unable to construct media-link urls, due to missing/invalid config in " +
"media-link.url and/or dspace.server.url");
}
try {
URL url = new URL(dspaceUrl);
mlUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(),
"/sword/media-link").toString();
} catch (MalformedURLException e) {
throw new DSpaceSWORDException(
"Unable to construct media-link urls, due to invalid dspace.server.url " +
e.getMessage(), e);
}
mlUrl = buildSWORDUrl("media-link");
}
return mlUrl;
}
@@ -530,4 +498,14 @@ public class SWORDUrlManager {
throw new DSpaceSWORDException(e);
}
}
/**
* Return configured server path for SWORD url
*
* @param path the target SWORD endpoint
* @return a sword URL
*/
private String buildSWORDUrl(String path) {
return dspaceUrl + "/" + swordPath + "/" + path;
}
}

View File

@@ -7,8 +7,6 @@
*/
package org.dspace.sword2;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.util.List;
@@ -48,6 +46,12 @@ public class SwordUrlManager {
protected ConfigurationService configurationService =
DSpaceServicesFactory.getInstance().getConfigurationService();
private String swordPath = configurationService.getProperty(
"swordv2-server.path", "swordv2");
private String dspaceUrl = configurationService.getProperty(
"dspace.server.url");
/**
* the SWORD configuration
*/
@@ -98,23 +102,12 @@ public class SwordUrlManager {
throws DSpaceSwordException {
String sUrl = configurationService.getProperty("swordv2-server.url");
if (sUrl == null || "".equals(sUrl)) {
String dspaceUrl = configurationService
.getProperty("dspace.server.url");
if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSwordException(
"Unable to construct service document urls, due to missing/invalid " +
"config in sword2.url and/or dspace.server.url");
}
try {
URL url = new URL(dspaceUrl);
sUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(),
"/swordv2").toString();
} catch (MalformedURLException e) {
throw new DSpaceSwordException(
"Unable to construct service document urls, due to invalid dspace.server.url " +
e.getMessage(), e);
}
sUrl = buildSWORDUrl("swordv2");
}
return sUrl;
}
@@ -283,7 +276,8 @@ public class SwordUrlManager {
return dso;
} else {
throw new SwordError(DSpaceUriRegistry.BAD_URL,
"Service Document request does not refer to a DSpace Collection or Community");
"Service Document request does not refer to a DSpace Collection " +
"or Community");
}
} else {
throw new SwordError(DSpaceUriRegistry.BAD_URL,
@@ -306,23 +300,12 @@ public class SwordUrlManager {
String sdUrl = configurationService
.getProperty("swordv2-server.servicedocument.url");
if (sdUrl == null || "".equals(sdUrl)) {
String dspaceUrl = configurationService
.getProperty("dspace.server.url");
if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSwordException(
"Unable to construct service document urls, due to missing/invalid " +
"config in swordv2-server.cfg servicedocument.url and/or dspace.server.url");
}
try {
URL url = new URL(dspaceUrl);
sdUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(),
"/swordv2/servicedocument").toString();
} catch (MalformedURLException e) {
throw new DSpaceSwordException(
"Unable to construct service document urls, due to invalid dspace.server.url " +
e.getMessage(), e);
}
sdUrl = buildSWORDUrl("servicedocument");
}
return sdUrl;
}
@@ -348,24 +331,12 @@ public class SwordUrlManager {
String depositUrl = configurationService
.getProperty("swordv2-server.collection.url");
if (depositUrl == null || "".equals(depositUrl)) {
String dspaceUrl = configurationService
.getProperty("dspace.server.url");
if (dspaceUrl == null || "".equals(dspaceUrl)) {
throw new DSpaceSwordException(
"Unable to construct deposit urls, due to missing/invalid config in " +
"swordv2-server.cfg deposit.url and/or dspace.server.url");
}
try {
URL url = new URL(dspaceUrl);
depositUrl = new URL(url.getProtocol(), url.getHost(),
url.getPort(), "/swordv2/collection").toString();
} catch (MalformedURLException e) {
throw new DSpaceSwordException(
"Unable to construct deposit urls, due to invalid dspace.server.url " +
e.getMessage(), e);
}
depositUrl = buildSWORDUrl("collection");
}
return depositUrl;
}
@@ -515,4 +486,14 @@ public class SwordUrlManager {
return new IRI(this.getSwordBaseUrl() + "/edit-media/" + item.getID() +
".atom");
}
/**
* Return configured server path for SWORD url
*
* @param path the target SWORD endpoint
* @return a sword URL
*/
private String buildSWORDUrl(String path) {
return dspaceUrl + "/" + swordPath + "/" + path;
}
}

View File

@@ -1563,6 +1563,7 @@ include = ${module_dir}/citation-page.cfg
include = ${module_dir}/clamav.cfg
include = ${module_dir}/curate.cfg
include = ${module_dir}/discovery.cfg
include = ${module_dir}/doi-curation.cfg
include = ${module_dir}/google-analytics.cfg
include = ${module_dir}/healthcheck.cfg
include = ${module_dir}/irus-statistics.cfg

View File

@@ -14,6 +14,7 @@ plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RequiredM
#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ClamScan = vscan
#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate
plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MetadataValueLinkChecker = checklinks
plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RegisterDOI = registerdoi
# add new tasks here (or in additional config files)
## task queue implementation

View File

@@ -0,0 +1,12 @@
### DOI registration curation task configuration module
##
# Should any logical filters be skipped when registering DOIs? (ie. *always* register, never filter out the item)
# Default: true
#doi-curation.skip-filter = true
##
# Should we allow the curation task to be distributed over communities / collections of items or the whole repository?
# This *could* be dangerous if run accidentally over more items than intended.
# Default: false
#doi-curation.distributed = false

View File

@@ -68,6 +68,21 @@
</bean>
-->
<!-- An optional logical item filter can be included in provider configuration based
on the filters defined in item-filters.xml, eg. -->
<!--
<bean id="org.dspace.identifier.DOIIdentifierProvider"
class="org.dspace.identifier.DOIIdentifierProvider"
scope="singleton">
<property name="configurationService"
ref="org.dspace.services.ConfigurationService" />
<property name="DOIConnector"
ref="org.dspace.identifier.doi.DOIConnector" />
<property name="filterService" ref="openaire_filter" />
</bean>
-->
<!-- The DOIConnector will handle the API calls to your DOI registration
agency for the DOIIdentifierProvider. If your registration agency
tells you to use the DataCite API directly, you can use the

View File

@@ -0,0 +1,339 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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/
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
>
<!-- default-autowire-candidates="*Service,*DAO,javax.sql.DataSource"> -->
<context:annotation-config /> <!-- allows us to use spring annotations in beans -->
<!-- DEFINE CONDITIONS
Define condition beans below for use as sub-statements in operator and filter beans
-->
<!--
The MetadataValueMatchCondition takes a regular expression, not an exact value.
For an exact value match (rather than 'contains'), make sure to anchor the string
like "^Exact Match$".
Special characters used in Java regular expressions will need escaping.
The below condition returns true if dc.title contains "demo" (case insensitive)
-->
<bean id="title-contains-demo_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value="(?i)demo" />
</map>
</property>
</bean>
<bean id="item-is-public_condition"
class="org.dspace.content.logic.condition.ReadableByGroupCondition">
<property name="parameters">
<map>
<entry key="group" value="Anonymous" />
<entry key="action" value="READ" />
</map>
</property>
</bean>
<!-- dc.title starts with Pattern -->
<bean id="title-starts-with-pattern_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value="^Pattern" />
</map>
</property>
</bean>
<!-- dc.type is exactly Journal Article -->
<bean id="type-equals-journal-article_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.type" />
<entry key="pattern" value="^Journal Article$" />
</map>
</property>
</bean>
<!-- dc.type is exactly Dataset -->
<bean id="type-equals-dataset_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.type" />
<entry key="pattern" value="^Dataset$" />
</map>
</property>
</bean>
<!--
A filter that checks if any value of dc.identifier.uri contains "10.12345/".
-->
<bean id="dc-identifier-uri-contains-doi_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.identifier.uri" />
<entry key="pattern" value="10.12345/" />
</map>
</property>
</bean>
<!-- dc.type ends with any of the listed values, as per XOAI "driverDocumentTypeCondition" -->
<bean id="driver-document-type_condition"
class="org.dspace.content.logic.condition.MetadataValuesMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.type" />
<entry key="patterns">
<list>
<value>article$</value>
<value>bachelorThesis$</value>
<value>masterThesis$</value>
<value>doctoralThesis$</value>
<value>book$</value>
<value>bookPart$</value>
<value>review$</value>
<value>conferenceObject$</value>
<value>lecture$</value>
<value>workingPaper$</value>
<value>preprint$</value>
<value>report$</value>
<value>annotation$</value>
<value>contributionToPeriodical$</value>
<value>patent$</value>
<value>dataset$</value>
<value>other$</value>
</list>
</entry>
</map>
</property>
</bean>
<!-- is in collection 123456789/20 (note, list parameter map means multiple collections can be passed) -->
<bean id="in-outfit-collection_condition"
class="org.dspace.content.logic.condition.InCollectionCondition">
<property name="parameters">
<map>
<entry key="collections">
<list>
<value>123456789/20</value>
</list>
</entry>
</map>
</property>
</bean>
<!-- has exactly one bitstream in ORIGINAL bundle -->
<bean id="has-one-bitstream_condition"
class="org.dspace.content.logic.condition.BitstreamCountCondition">
<property name="parameters">
<map>
<entry key="bundle" value="ORIGINAL"/>
<entry key="min" value="1"/>
<entry key="max" value="1"/>
</map>
</property>
</bean>
<!-- has at least one bitstream in ORIGINAL bundle -->
<bean id="has-at-least-one-bitstream_condition"
class="org.dspace.content.logic.condition.BitstreamCountCondition">
<property name="parameters">
<map>
<entry key="bundle" value="ORIGINAL"/>
<entry key="min" value="1"/>
</map>
</property>
</bean>
<!-- DEFINE OPERATORS
Operators can be defined too, if a particular AND or OR statement needs to be re-used a lot, though
it may be easier in most cases to turn that into a filter and reference the filter in other sub-statements
-->
<bean class="org.dspace.content.logic.operator.Or" id="a-common-or_statement">
<property name="statements">
<list>
<ref bean="type-equals-journal-article_condition"/>
<ref bean="type-equals-dataset_condition"/>
</list>
</property>
</bean>
<!-- DEFINE FILTERS -->
<!-- Note that this filter is almost the same as the above "or" bean but the advantage is we
can reference the type_filter directly for item logic *and* as a sub-statement of other filters
whereas the operator class can only be a sub-statement
-->
<!-- Example DOI Filter. An item has to pass the filter (filter returns true) to get a DOI.
If the filter returns false on an item, minting of a new DOI for that item is prevented. -->
<bean id="doi-filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<property name="statements">
<list>
<!-- Don't create new DOIs for items that already have one -->
<bean class="org.dspace.content.logic.operator.Not">
<property name="statements">
<ref bean="dc-identifier-uri-contains-doi_condition"/>
</property>
</bean>
<!-- Create DOIs for items only that do have at least one bitstream. -->
<ref bean="has-at-least-one-bitstream_condition"/>
</list>
</property>
</bean>
</property>
</bean>
<bean id="type_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<!-- the below is the same as referencing the above, eg:
<ref bean="a-common-r_statement"/> -->
<bean class="org.dspace.content.logic.operator.Or">
<property name="statements">
<list>
<ref bean="type-equals-journal-article_condition"/>
<ref bean="type-equals-dataset_condition"/>
</list>
</property>
</bean>
</property>
</bean>
<!-- A very simple demonstration filter, using the metadata match condition -->
<bean id="simple-demo_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<ref bean="title-contains-demo_condition"/>
</property>
</bean>
<!-- A very simple filter for items with at least one bitstream -->
<bean id="has-bitstream_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<ref bean="has-at-least-one-bitstream_condition"/>
</property>
</bean>
<!--
a more complex example:
title contains 'demo' AND (title starts with 'Pattern' OR item is in one of the listed collections)
-->
<bean id="demo_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<!-- title contains 'demo' AND (the result of the OR substatement is true) -->
<property name="statements">
<list>
<ref bean="title-contains-demo_condition"/>
<bean class="org.dspace.content.logic.operator.Or">
<!-- title starts with Lily OR the item in one of the listed collections -->
<property name="statements">
<list>
<ref bean="title-starts-with-pattern_condition"/>
<bean class="org.dspace.content.logic.condition.InCollectionCondition">
<property name="parameters">
<map>
<entry key="collections">
<list>
<value>123456789/3</value>
<value>123456789/4</value>
</list>
</entry>
</map>
</property>
</bean>
</list>
</property>
</bean>
</list>
</property>
</bean>
</property>
</bean>
<!-- An example of an OpenAIRE compliance filter based on the same rules in xoai.xml
some sub-statements are defined within this bean, and some are referenced from earlier definitions
-->
<bean id="openaire_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<property name="statements">
<list>
<!-- Has a non-empty title -->
<bean id="has-title_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value=".*" />
</map>
</property>
</bean>
<!-- AND has a non-empty author -->
<bean id="has-author_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.contributor.author" />
<entry key="pattern" value=".*" />
</map>
</property>
</bean>
<!-- AND has a valid DRIVER document type (defined earlier) -->
<ref bean="driver-document-type_condition" />
<!-- AND (the item is publicly accessible OR withdrawn) -->
<bean class="org.dspace.content.logic.operator.Or">
<property name="statements">
<list>
<!-- item is public, defined earlier -->
<ref bean="item-is-public_condition" />
<!-- OR item is withdrawn, for tombstoning -->
<bean class="org.dspace.content.logic.condition.IsWithdrawnCondition">
<property name="parameters"><map></map></property>
</bean>
</list>
</property>
</bean>
<!-- AND the dc.relation is a valid OpenAIRE identifier
(starts with "info:eu-repo/grantAgreement/") -->
<bean id="has-openaire-relation_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.relation" />
<entry key="pattern" value="^info:eu-repo/grantAgreement/" />
</map>
</property>
</bean>
</list>
</property>
</bean>
</property>
</bean>
</beans>