manager = getDSpaceObjectService(dso.getType());
- return manager;
+ return getDSpaceObjectService(dso.getType());
}
@SuppressWarnings("unchecked")
diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java
index 6f123ae1ba..e970f0bdab 100644
--- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java
@@ -28,8 +28,8 @@ import org.dspace.content.service.MetadataValueService;
import org.dspace.content.service.RelationshipService;
import org.dspace.content.service.RelationshipTypeService;
import org.dspace.content.service.SiteService;
-import org.dspace.content.service.SupervisedItemService;
import org.dspace.content.service.WorkspaceItemService;
+import org.dspace.eperson.service.SubscribeService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -68,10 +68,9 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory {
@Autowired(required = true)
private InstallItemService installItemService;
@Autowired(required = true)
- private SupervisedItemService supervisedItemService;
- @Autowired(required = true)
private SiteService siteService;
-
+ @Autowired(required = true)
+ private SubscribeService subscribeService;
@Autowired(required = true)
private RelationshipService relationshipService;
@Autowired(required = true)
@@ -149,13 +148,13 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory {
}
@Override
- public SupervisedItemService getSupervisedItemService() {
- return supervisedItemService;
+ public SiteService getSiteService() {
+ return siteService;
}
@Override
- public SiteService getSiteService() {
- return siteService;
+ public SubscribeService getSubscribeService() {
+ return subscribeService ;
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java
index 490c3949ea..1ac3930952 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java
@@ -18,10 +18,10 @@ import org.dspace.core.Context;
* 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 String name;
private final static Logger log = LogManager.getLogger();
/**
@@ -44,4 +44,15 @@ public class DefaultFilter implements Filter {
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return this.statement.getResult(context, item);
}
+
+ @Override
+ public void setBeanName(String name) {
+ log.debug("Initialize bean " + name);
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java
index 84e9d6bc08..f789860e77 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java
@@ -9,6 +9,7 @@ package org.dspace.content.logic;
import org.dspace.content.Item;
import org.dspace.core.Context;
+import org.springframework.beans.factory.BeanNameAware;
/**
* The interface for Filter currently doesn't add anything to LogicalStatement but inherits from it
@@ -22,7 +23,7 @@ import org.dspace.core.Context;
* @author Kim Shepherd
* @see org.dspace.content.logic.DefaultFilter
*/
-public interface Filter extends LogicalStatement {
+public interface Filter extends LogicalStatement, BeanNameAware {
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
@@ -32,4 +33,11 @@ public interface Filter extends LogicalStatement {
*/
@Override
boolean getResult(Context context, Item item) throws LogicalStatementException;
+
+ /**
+ * Get the name of a filter. This can be used by filters which make use of BeanNameAware
+ * to return the bean name.
+ * @return the id/name of this spring bean
+ */
+ String getName();
}
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java
new file mode 100644
index 0000000000..a878d69e6e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java
@@ -0,0 +1,85 @@
+/**
+ * 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.util.HashMap;
+import java.util.Map;
+
+import org.dspace.identifier.DOI;
+import org.dspace.identifier.Handle;
+import org.dspace.identifier.Identifier;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * General utility methods for logical item filtering
+ *
+ * @author Kim Shepherd
+ */
+public class FilterUtils {
+
+ @Autowired(required = true)
+ ConfigurationService configurationService;
+
+ /**
+ * Get a Filter by configuration property name
+ * For example, if a module has implemented "my-feature.filter" configuration property
+ * this method will return a filter with the ID specified by the configuration property
+ * @param property DSpace configuration property name (Apache Commons config)
+ * @return Filter object, with a bean ID configured for this property key, or null
+ */
+ public static Filter getFilterFromConfiguration(String property) {
+ String filterName = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty(property);
+ if (filterName != null) {
+ return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(filterName, Filter.class);
+ }
+ return null;
+ }
+
+ /**
+ * Get a Filter by configuration property name
+ * For example, if a module has implemented "my-feature.filter" configuration property
+ * this method will return a filter with the ID specified by the configuration property
+ * @param property DSpace configuration property name (Apache Commons config)
+ * @return Filter object, with a bean ID configured for this property key, or default filter
+ */
+ public static Filter getFilterFromConfiguration(String property, Filter defaultFilter) {
+ Filter filter = getFilterFromConfiguration(property);
+ if (filter != null) {
+ return filter;
+ }
+ return defaultFilter;
+ }
+
+ /**
+ * Get a map of identifier types and filters to use when creating workspace or archived items
+ * This is used by services installing new archived or workspace items to filter by identifier type
+ * as some filters should apply to DOI creation but not Handle creation, and so on.
+ * The in progress or archived status will be used to load the appropriate filter from configuration
+ *
+ * @param inProgress
+ * @return
+ */
+ public static Map, Filter> getIdentifierFilters(boolean inProgress) {
+ String configurationSuffix = "install";
+ if (inProgress) {
+ configurationSuffix = "workspace";
+ }
+ Map, Filter> filters = new HashMap<>();
+ // Put DOI 'can we create DOI on install / workspace?' filter
+ Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter." + configurationSuffix);
+ // A null filter should be handled safely by the identifier provier (default, or "always true")
+ filters.put(DOI.class, filter);
+ // This won't have an affect until handle providers implement filtering, but is an example of
+ // how the filters can be used for other types
+ filters.put(Handle.class, DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "always_true_filter", TrueFilter.class));
+ return filters;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java
index 5fc3e76cd5..0119f48b51 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java
@@ -17,7 +17,6 @@ import org.dspace.core.Context;
* used as sub-statements in other Filters and Operators.
*
* @author Kim Shepherd
- * @version $Revision$
*/
public interface LogicalStatement {
/**
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java
index 758a0a7124..4e3b3e3b7d 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java
@@ -12,7 +12,6 @@ package org.dspace.content.logic;
* defined as spring beans.
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class LogicalStatementException extends RuntimeException {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java
index b78de7f190..bf218eaa8a 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java
@@ -33,7 +33,6 @@ 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 {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java
new file mode 100644
index 0000000000..b15ab4eaaa
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java
@@ -0,0 +1,41 @@
+/**
+ * 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.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Extremely simple filter that always returns true!
+ * Useful to pass to methods that expect a filter, in order to effectively say "all items".
+ * This could be configured in Spring XML but it is more stable and reliable to have it hard-coded here
+ * so that any broken configuration doesn't silently break parts of DSpace that expect it to work.
+ *
+ * @author Kim Shepherd
+ */
+public class TrueFilter implements Filter {
+ private String name;
+ private final static Logger log = LogManager.getLogger();
+
+ public boolean getResult(Context context, Item item) throws LogicalStatementException {
+ return true;
+ }
+
+ @Override
+ public void setBeanName(String name) {
+ log.debug("Initialize bean " + name);
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java
index 0202243265..ce5b274a8d 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java
@@ -23,7 +23,6 @@ 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 {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java
index 635f0997d3..36e506122e 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java
@@ -18,7 +18,6 @@ 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 {
/**
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java
index c86509899f..7647dce4a4 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java
@@ -22,7 +22,6 @@ import org.dspace.core.Context;
* operator is not a condition but also a logical statement.
*
* @author Kim Shepherd
- * @version $Revision$
*/
public interface Condition extends LogicalStatement {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java
index 0aaa1bff1d..df94f183d1 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java
@@ -23,7 +23,6 @@ import org.dspace.core.Context;
* 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);
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java
index 9f588f9c3b..6a72011e73 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java
@@ -24,7 +24,6 @@ import org.dspace.core.Context;
* if the item belongs to any of them.
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class InCommunityCondition extends AbstractCondition {
private final static Logger log = LogManager.getLogger();
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java
new file mode 100644
index 0000000000..4f50d2b6f6
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java
@@ -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.logging.log4j.LogManager;
+import org.apache.logging.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 archived
+ *
+ * @author Kim Shepherd
+ */
+public class IsArchivedCondition extends AbstractCondition {
+ private final static Logger log = LogManager.getLogger();
+
+ /**
+ * Return true if item is archived
+ * 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 isArchived is " + item.isArchived());
+ return item.isArchived();
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java
index 6424e6f35f..850b69bda0 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java
@@ -17,7 +17,6 @@ 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 final static Logger log = LogManager.getLogger();
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java
index 4e30c75a2a..e87c479de6 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java
@@ -23,7 +23,6 @@ import org.dspace.core.Context;
* in a given metadata field
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class MetadataValueMatchCondition extends AbstractCondition {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java
index 74ccfa4ca8..c6ca9dfb9f 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java
@@ -23,7 +23,6 @@ import org.dspace.core.Context;
* in a given metadata field
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class MetadataValuesMatchCondition extends AbstractCondition {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java
index 65f9925222..20138beb47 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java
@@ -25,7 +25,6 @@ import org.dspace.core.Context;
* can perform the action on a given item
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class ReadableByGroupCondition extends AbstractCondition {
private final static Logger log = LogManager.getLogger();
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java
index 99ece622f7..3882414def 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java
@@ -22,7 +22,6 @@ import org.dspace.core.Context;
* as a logical result
*
* @author Kim Shepherd
- * @version $Revision$
*/
public abstract class AbstractOperator implements LogicalStatement {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java
index 26606f2099..79bc5c381e 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java
@@ -19,7 +19,6 @@ import org.dspace.core.Context;
* true if all sub-statements return true
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class And extends AbstractOperator {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java
index 1021ec6722..2a4b6823b6 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java
@@ -18,7 +18,6 @@ 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 {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java
index 35c7bb22a7..277acdfd01 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java
@@ -19,7 +19,6 @@ import org.dspace.core.Context;
* 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 {
diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java
index 5110ac31ba..e5697f8cc3 100644
--- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java
+++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java
@@ -19,7 +19,6 @@ import org.dspace.core.Context;
* true if one or more sub-statements return true
*
* @author Kim Shepherd
- * @version $Revision$
*/
public class Or extends AbstractOperator {
diff --git a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java
index 4621c95e7c..8effabf284 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java
@@ -183,7 +183,7 @@ public interface BitstreamService extends DSpaceObjectService, DSpace
* @return a list of all bitstreams that have been "deleted"
* @throws SQLException if database error
*/
- public List findDeletedBitstreams(Context context) throws SQLException;
+ public List findDeletedBitstreams(Context context, int limit, int offset) throws SQLException;
/**
diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java
index 522bdac224..a5b2b7d8d8 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java
@@ -33,6 +33,11 @@ import org.dspace.eperson.Group;
public interface CollectionService
extends DSpaceObjectService, DSpaceObjectLegacySupportService {
+ /*
+ * Field used to sort community and collection lists at solr
+ */
+ public static final String SOLR_SORT_FIELD = "dc.title_sort";
+
/**
* Create a new collection with a new ID.
* Once created the collection is added to the given community
@@ -46,7 +51,6 @@ public interface CollectionService
public Collection create(Context context, Community community) throws SQLException,
AuthorizeException;
-
/**
* Create a new collection with the supplied handle and with a new ID.
* Once created the collection is added to the given community
diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java
index 8b7badf223..b7a479469b 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java
@@ -28,6 +28,7 @@ import org.dspace.content.MetadataValue;
import org.dspace.content.Thumbnail;
import org.dspace.content.WorkspaceItem;
import org.dspace.core.Context;
+import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
@@ -768,6 +769,27 @@ public interface ItemService
*/
int countWithdrawnItems(Context context) throws SQLException;
+ /**
+ * finds all items for which the current user has editing rights
+ * @param context DSpace context object
+ * @param offset page offset
+ * @param limit page size limit
+ * @return list of items for which the current user has editing rights
+ * @throws SQLException
+ * @throws SearchServiceException
+ */
+ public List- findItemsWithEdit(Context context, int offset, int limit)
+ throws SQLException, SearchServiceException;
+
+ /**
+ * counts all items for which the current user has editing rights
+ * @param context DSpace context object
+ * @return list of items for which the current user has editing rights
+ * @throws SQLException
+ * @throws SearchServiceException
+ */
+ public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException;
+
/**
* Check if the supplied item is an inprogress submission
*
diff --git a/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java b/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java
deleted file mode 100644
index 883e0f9fd2..0000000000
--- a/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * 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.service;
-
-import java.sql.SQLException;
-import java.util.List;
-
-import org.dspace.content.WorkspaceItem;
-import org.dspace.core.Context;
-import org.dspace.eperson.EPerson;
-
-/**
- * Class to handle WorkspaceItems which are being supervised.
- *
- * @author Richard Jones
- * @version $Revision$
- */
-public interface SupervisedItemService {
- /**
- * Get all workspace items which are being supervised
- *
- * @param context the context this object exists in
- * @return array of SupervisedItems
- * @throws SQLException if database error
- */
- public List getAll(Context context) throws SQLException;
-
-
- /**
- * Get items being supervised by given EPerson
- *
- * @param ep the eperson who's items to supervise we want
- * @param context the dspace context
- * @return the items eperson is supervising in an array
- * @throws SQLException if database error
- */
- public List findbyEPerson(Context context, EPerson ep)
- throws SQLException;
-}
diff --git a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java
index 8f572f6108..c8df68e434 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java
@@ -127,10 +127,6 @@ public interface WorkspaceItemService extends InProgressSubmissionService findAllSupervisedItems(Context context) throws SQLException;
-
- public List findSupervisedItemsByEPerson(Context context, EPerson ePerson) throws SQLException;
-
/**
* Get all workspace items in the whole system
*
diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java
index 6db27c9e4f..64da629bcc 100644
--- a/dspace-api/src/main/java/org/dspace/core/Email.java
+++ b/dspace-api/src/main/java/org/dspace/core/Email.java
@@ -355,7 +355,7 @@ public class Email {
for (String headerName : config.getArrayProperty("mail.message.headers")) {
String headerValue = (String) vctx.get(headerName);
if ("subject".equalsIgnoreCase(headerName)) {
- if (null != subject) {
+ if (null != headerValue) {
subject = headerValue;
}
} else if ("charset".equalsIgnoreCase(headerName)) {
diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java
index 4e777d70a8..0765d7b000 100644
--- a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java
+++ b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java
@@ -13,11 +13,15 @@ import java.sql.SQLException;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
+import org.dspace.content.logic.Filter;
+import org.dspace.content.logic.FilterUtils;
+import org.dspace.content.logic.TrueFilter;
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.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
/**
@@ -39,6 +43,7 @@ public class RegisterDOI extends AbstractCurationTask {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegisterDOI.class);
// DOI provider
private DOIIdentifierProvider provider;
+ private Filter trueFilter;
/**
* Initialise the curation task and read configuration, instantiate the DOI provider
@@ -46,14 +51,14 @@ public class RegisterDOI extends AbstractCurationTask {
@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);
+ trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "always_true_filter", TrueFilter.class);
}
/**
@@ -118,8 +123,9 @@ public class RegisterDOI extends AbstractCurationTask {
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);
+ Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.curation",
+ trueFilter);
+ doi = provider.register(Curator.curationContext(), item, filter);
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";
diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java
index b2bd0fc5ff..00236d2bfe 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java
@@ -7,6 +7,8 @@
*/
package org.dspace.discovery;
+import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -109,6 +111,9 @@ public class DiscoverResult {
if (facetValues.size() == 0 && field.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) {
facetValues = getFacetResult(field.getIndexFieldName() + ".year");
}
+ if (facetValues.isEmpty()) {
+ facetValues = getFacetResult(field.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES);
+ }
return ListUtils.emptyIfNull(facetValues);
}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
index fcb3e79d1d..661c48d91c 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
@@ -56,37 +56,18 @@ public class IndexClient extends DSpaceRunnable indexableObject = Optional.empty();
+
+ if (indexClientOptions == IndexClientOptions.REMOVE || indexClientOptions == IndexClientOptions.INDEX) {
+ final String param = indexClientOptions == IndexClientOptions.REMOVE ? commandLine.getOptionValue('r') :
+ commandLine.getOptionValue('i');
UUID uuid = null;
try {
uuid = UUID.fromString(param);
} catch (Exception e) {
- // nothing to do, it should be an handle
+ // nothing to do, it should be a handle
}
- Optional indexableObject = Optional.empty();
+
if (uuid != null) {
final Item item = ContentServiceFactory.getInstance().getItemService().find(context, uuid);
if (item != null) {
@@ -118,7 +99,32 @@ public class IndexClient extends DSpaceRunnable
+ *
+ * @param context DSpace context object
+ * @param community Community for which we search the ancestors
+ * @return A list of ancestor communities.
+ * @throws SQLException if database error
+ */
+ static List getAncestorCommunities(Context context, Community community) throws SQLException {
+ ArrayList communities = new ArrayList<>();
+ while (community != null) {
+ communities.add(community);
+ community = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(community)
+ .getParentObject(context, community);
+ }
+ return communities;
+ }
+
+ /**
+ * Retrieve the ids of all groups that have ADMIN rights to the given community, either directly
+ * (through direct resource policy) or indirectly (through a policy on an ancestor community).
+ *
+ * @param context DSpace context object
+ * @param community Community for which we search the admin group IDs
+ * @return A list of admin group IDs
+ * @throws SQLException if database error
+ */
+ static List findTransitiveAdminGroupIds(Context context, Community community) throws SQLException {
+ return getAncestorCommunities(context, community).stream()
+ .filter(parent -> parent.getAdministrators() != null)
+ .map(parent -> parent.getAdministrators().getID())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Retrieve the ids of all groups that have ADMIN rights to the given collection, either directly
+ * (through direct resource policy) or indirectly (through a policy on its community, or one of
+ * its ancestor communities).
+ *
+ * @param context DSpace context object
+ * @param collection Collection for which we search the admin group IDs
+ * @return A list of admin group IDs
+ * @throws SQLException if database error
+ */
+ static List findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException {
+ List ids = new ArrayList<>();
+ if (collection.getAdministrators() != null) {
+ ids.add(collection.getAdministrators().getID());
+ }
+ for (Community community : collection.getCommunities()) {
+ for (UUID id : findTransitiveAdminGroupIds(context, community)) {
+ ids.add(id);
+ }
+ }
+ return ids;
+ }
+
+ /**
+ * Retrieve group and eperson IDs for all groups and eperson who have _any_ of the given authorizations
+ * on the given DSpaceObject. The resulting IDs are prefixed with "e" in the case of an eperson ID, and "g" in the
+ * case of a group ID.
+ *
+ * @param authService The authentication service
+ * @param context DSpace context object
+ * @param obj DSpaceObject for which we search the admin group IDs
+ * @return A stream of admin group IDs as Strings, prefixed with either "e" or "g", depending on whether it is a
+ * group or eperson ID.
+ * @throws SQLException if database error
+ */
+ static List findDirectlyAuthorizedGroupAndEPersonPrefixedIds(
+ AuthorizeService authService, Context context, DSpaceObject obj, int[] authorizations)
+ throws SQLException {
+ ArrayList prefixedIds = new ArrayList<>();
+ for (int auth : authorizations) {
+ for (ResourcePolicy policy : authService.getPoliciesActionFilter(context, obj, auth)) {
+ // Avoid NPE in cases where the policy does not have group or eperson
+ if (policy.getGroup() == null && policy.getEPerson() == null) {
+ continue;
+ }
+ String prefixedId = policy.getGroup() == null
+ ? "e" + policy.getEPerson().getID()
+ : "g" + policy.getGroup().getID();
+ prefixedIds.add(prefixedId);
+ context.uncacheEntity(policy);
+ }
+ }
+ return prefixedIds;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
index 383121072d..0cf2aa50af 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
@@ -105,6 +105,10 @@ public class SolrServiceImpl implements SearchService, IndexingService {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class);
+ // Suffix of the solr field used to index the facet/filter so that the facet search can search all word in a
+ // facet by indexing "each word to end of value' partial value
+ public static final String SOLR_FIELD_SUFFIX_FACET_PREFIXES = "_prefix";
+
@Autowired
protected ContentServiceFactory contentServiceFactory;
@Autowired
@@ -252,7 +256,12 @@ public class SolrServiceImpl implements SearchService, IndexingService {
try {
if (solrSearchCore.getSolr() != null) {
- indexObjectServiceFactory.getIndexableObjectFactory(searchUniqueID).delete(searchUniqueID);
+ IndexFactory index = indexObjectServiceFactory.getIndexableObjectFactory(searchUniqueID);
+ if (index != null) {
+ index.delete(searchUniqueID);
+ } else {
+ log.warn("Object not found in Solr index: " + searchUniqueID);
+ }
if (commit) {
solrSearchCore.getSolr().commit();
}
@@ -905,6 +914,9 @@ public class SolrServiceImpl implements SearchService, IndexingService {
//Only add facet information if there are any facets
for (DiscoverFacetField facetFieldConfig : facetFields) {
String field = transformFacetField(facetFieldConfig, facetFieldConfig.getField(), false);
+ if (facetFieldConfig.getPrefix() != null) {
+ field = transformPrefixFacetField(facetFieldConfig, facetFieldConfig.getField(), false);
+ }
solrQuery.addFacetField(field);
// Setting the facet limit in this fashion ensures that each facet can have its own max
@@ -1344,7 +1356,31 @@ public class SolrServiceImpl implements SearchService, IndexingService {
}
}
+ /**
+ * Gets the solr field that contains the facet value split on each word break to the end, so can be searched
+ * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl
+ * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)}
+ * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular
+ * facet filter field
+ */
+ protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field,
+ boolean removePostfix) {
+ if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT) ||
+ facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) {
+ if (removePostfix) {
+ return field.substring(0, field.lastIndexOf(SOLR_FIELD_SUFFIX_FACET_PREFIXES));
+ } else {
+ return field + SOLR_FIELD_SUFFIX_FACET_PREFIXES;
+ }
+ } else {
+ return this.transformFacetField(facetFieldConfig, field, removePostfix);
+ }
+ }
+
protected String transformFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) {
+ if (field.contains(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) {
+ return this.transformPrefixFacetField(facetFieldConfig, field, removePostfix);
+ }
if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) {
if (removePostfix) {
return field.substring(0, field.lastIndexOf("_filter"));
@@ -1390,7 +1426,7 @@ public class SolrServiceImpl implements SearchService, IndexingService {
if (field.equals("location.comm") || field.equals("location.coll")) {
value = locationToName(context, field, value);
} else if (field.endsWith("_filter") || field.endsWith("_ac")
- || field.endsWith("_acid")) {
+ || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) {
//We have a filter make sure we split !
String separator = DSpaceServicesFactory.getInstance().getConfigurationService()
.getProperty("discovery.solr.facets.split.char");
@@ -1422,7 +1458,7 @@ public class SolrServiceImpl implements SearchService, IndexingService {
return value;
}
if (field.endsWith("_filter") || field.endsWith("_ac")
- || field.endsWith("_acid")) {
+ || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) {
//We have a filter make sure we split !
String separator = DSpaceServicesFactory.getInstance().getConfigurationService()
.getProperty("discovery.solr.facets.split.char");
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java
index 00b70f93d5..ee93f954a5 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java
@@ -7,16 +7,17 @@
*/
package org.dspace.discovery;
+import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds;
+import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds;
+
import java.sql.SQLException;
import java.util.List;
+import java.util.UUID;
import org.apache.logging.log4j.Logger;
import org.apache.solr.common.SolrInputDocument;
-import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
-import org.dspace.content.Community;
-import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
@@ -42,29 +43,21 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn
Collection col = ((IndexableCollection) idxObj).getIndexedObject();
if (col != null) {
try {
- String fieldValue = null;
- Community parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(col)
- .getParentObject(context, col);
- while (parent != null) {
- if (parent.getAdministrators() != null) {
- fieldValue = "g" + parent.getAdministrators().getID();
- document.addField("submit", fieldValue);
- }
- parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(parent)
- .getParentObject(context, parent);
+ // Index groups with ADMIN rights on the Collection, on
+ // Communities containing those Collections, and recursively on any Community containing such a
+ // Community.
+ // TODO: Strictly speaking we should also check for epersons who received admin rights directly,
+ // without being part of the admin group. Finding them may be a lot slower though.
+ for (UUID unprefixedId : findTransitiveAdminGroupIds(context, col)) {
+ document.addField("submit", "g" + unprefixedId);
}
- List policies = authorizeService.getPoliciesActionFilter(context,col,Constants.ADD);
- policies.addAll(authorizeService.getPoliciesActionFilter(context, col, Constants.ADMIN));
- for (ResourcePolicy resourcePolicy : policies) {
- if (resourcePolicy.getGroup() != null) {
- fieldValue = "g" + resourcePolicy.getGroup().getID();
- } else {
- fieldValue = "e" + resourcePolicy.getEPerson().getID();
-
- }
- document.addField("submit", fieldValue);
- context.uncacheEntity(resourcePolicy);
+ // Index groups and epersons with ADD or ADMIN rights on the Collection.
+ List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds(
+ authorizeService, context, col, new int[] {Constants.ADD, Constants.ADMIN}
+ );
+ for (String prefixedId : prefixedIds) {
+ document.addField("submit", prefixedId);
}
} catch (SQLException e) {
log.error(LogHelper.getHeader(context, "Error while indexing resource policies",
@@ -73,5 +66,4 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn
}
}
}
-
-}
\ No newline at end of file
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java
new file mode 100644
index 0000000000..09308be759
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java
@@ -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.discovery;
+
+import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds;
+import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.solr.common.SolrInputDocument;
+import org.dspace.authorize.service.AuthorizeService;
+import org.dspace.content.Collection;
+import org.dspace.content.Item;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.core.LogHelper;
+import org.dspace.discovery.indexobject.IndexableItem;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Indexes policies that yield write access to items.
+ *
+ * @author Koen Pauwels at atmire.com
+ */
+public class SolrServiceIndexItemEditorsPlugin implements SolrServiceIndexPlugin {
+ private static final Logger log = org.apache.logging.log4j.LogManager
+ .getLogger(SolrServiceIndexItemEditorsPlugin.class);
+
+ @Autowired(required = true)
+ protected AuthorizeService authorizeService;
+
+ @Override
+ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) {
+ if (idxObj instanceof IndexableItem) {
+ Item item = ((IndexableItem) idxObj).getIndexedObject();
+ if (item != null) {
+ try {
+ // Index groups with ADMIN rights on Collections containing the Item, on
+ // Communities containing those Collections, and recursively on any Community containing ssuch a
+ // Community.
+ // TODO: Strictly speaking we should also check for epersons who received admin rights directly,
+ // without being part of the admin group. Finding them may be a lot slower though.
+ for (Collection collection : item.getCollections()) {
+ for (UUID unprefixedId : findTransitiveAdminGroupIds(context, collection)) {
+ document.addField("edit", "g" + unprefixedId);
+ }
+ }
+
+ // Index groups and epersons with WRITE or direct ADMIN rights on the Item.
+ List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds(
+ authorizeService, context, item, new int[] {Constants.WRITE, Constants.ADMIN}
+ );
+ for (String prefixedId : prefixedIds) {
+ document.addField("edit", prefixedId);
+ }
+ } catch (SQLException e) {
+ log.error(LogHelper.getHeader(context, "Error while indexing resource policies",
+ "Item: (id " + item.getID() + " name " + item.getName() + ")" ));
+ }
+ }
+ }
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java
index d03ea359f5..746a0cb832 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java
@@ -7,6 +7,8 @@
*/
package org.dspace.discovery;
+import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES;
+
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -261,9 +263,9 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
}
}
}
-
for (String facet : distFValues) {
document.addField(bi.getDistinctTableName() + "_filter", facet);
+ document.addField(bi.getDistinctTableName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, facet);
}
for (String facet : distFAuths) {
document.addField(bi.getDistinctTableName()
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java
new file mode 100644
index 0000000000..116b5ec88d
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java
@@ -0,0 +1,68 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.discovery;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.solr.common.SolrInputDocument;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.discovery.indexobject.IndexableInProgressSubmission;
+import org.dspace.discovery.indexobject.IndexableWorkflowItem;
+import org.dspace.discovery.indexobject.IndexableWorkspaceItem;
+import org.dspace.supervision.SupervisionOrder;
+import org.dspace.supervision.service.SupervisionOrderService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * A Solr Indexing plugin responsible adding a `supervised` field.
+ * When item being indexed is a workspace or workflow item,
+ * and at least one supervision order is defined
+ * the 'supervised' field with value 'true' will be added to the solr document,
+ * if no supervision orders are defined field will be set to 'false'
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science dot it)
+ */
+public class SolrServiceSupervisionOrderIndexingPlugin implements SolrServiceIndexPlugin {
+
+ @Autowired(required = true)
+ private SupervisionOrderService supervisionOrderService;
+
+ @Override
+ public void additionalIndex(Context context, IndexableObject indexableObject, SolrInputDocument document) {
+ try {
+
+ if (!(indexableObject instanceof IndexableWorkspaceItem) &&
+ !(indexableObject instanceof IndexableWorkflowItem)) {
+ return;
+ }
+
+ Item item =
+ (((IndexableInProgressSubmission) indexableObject).getIndexedObject()).getItem();
+
+ if (Objects.isNull(item)) {
+ return;
+ }
+ addSupervisedField(context, item, document);
+ } catch (SQLException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ private void addSupervisedField(Context context, Item item, SolrInputDocument document) throws SQLException {
+ List supervisionOrders = supervisionOrderService.findByItem(context, item);
+ if (CollectionUtils.isNotEmpty(supervisionOrders)) {
+ document.addField("supervised", true);
+ } else {
+ document.addField("supervised", false);
+ }
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java
index fd05be1cb5..1618494756 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java
@@ -40,6 +40,11 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic
*/
public static final String DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME = "workflowAdmin";
+ /**
+ * The name of the discover configuration used by administrators to search for workspace and workflow tasks
+ */
+ public static final String DISCOVER_SUPERVISION_CONFIGURATION_NAME = "supervision";
+
@Autowired(required = true)
protected GroupService groupService;
@@ -60,18 +65,22 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic
);
boolean isWorkflowAdmin = isAdmin(context)
&& DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName());
+
+ boolean isSupervision =
+ DISCOVER_SUPERVISION_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName());
+
EPerson currentUser = context.getCurrentUser();
// extra security check to avoid the possibility that an anonymous user
// get access to workspace or workflow
- if (currentUser == null && (isWorkflow || isWorkspace)) {
+ if (currentUser == null && (isWorkflow || isWorkspace || isSupervision)) {
throw new IllegalStateException(
"An anonymous user cannot perform a workspace or workflow search");
}
if (isWorkspace) {
// insert filter by submitter
solrQuery.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")");
- } else if (isWorkflow && !isWorkflowAdmin) {
+ } else if ((isWorkflow && !isWorkflowAdmin) || (isSupervision && !isAdmin(context))) {
// Retrieve all the groups the current user is a member of !
Set groups;
try {
diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java
index 636e7ccd2a..c02c83ece6 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java
@@ -139,4 +139,23 @@ public class DiscoveryConfigurationService {
}
}
}
+
+ /**
+ * Retrieves a list of all DiscoveryConfiguration objects where key starts with prefixConfigurationName
+ *
+ * @param prefixConfigurationName string as prefix key
+ */
+ public List getDiscoveryConfigurationWithPrefixName(final String prefixConfigurationName) {
+ List discoveryConfigurationList = new ArrayList<>();
+ if (StringUtils.isNotBlank(prefixConfigurationName)) {
+ for (String key : map.keySet()) {
+ if (key.equals(prefixConfigurationName) || key.startsWith(prefixConfigurationName)) {
+ DiscoveryConfiguration config = map.get(key);
+ discoveryConfigurationList.add(config);
+ }
+ }
+ }
+ return discoveryConfigurationList;
+ }
+
}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java
new file mode 100644
index 0000000000..6c24a6bac6
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java
@@ -0,0 +1,16 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.discovery.configuration;
+
+/**
+ * This class extends {@link DiscoveryConfiguration} and add method for set parameters
+ * to filter query list
+ *
+ * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it)
+ */
+public class DiscoveryRelatedItemConfiguration extends DiscoveryConfiguration {}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java
new file mode 100644
index 0000000000..7fb020cd56
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java
@@ -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.discovery.configuration;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ *
+ * Extension of {@link DiscoverySortFieldConfiguration} used to configure sorting
+ * taking advantage of solr function feature.
+ *
+ * Order is evaluated by mean of function parameter value and passed in arguments as input.
+ *
+ * @author Corrado Lombardi (corrado.lombardi at 4science.it)
+ *
+ */
+public class DiscoverySortFunctionConfiguration extends DiscoverySortFieldConfiguration {
+
+ public static final String SORT_FUNCTION = "sort_function";
+ private String function;
+ private List arguments;
+ private String id;
+
+ public void setFunction(final String function) {
+ this.function = function;
+ }
+
+ public void setArguments(final List arguments) {
+ this.arguments = arguments;
+ }
+
+ @Override
+ public String getType() {
+ return SORT_FUNCTION;
+ }
+
+ @Override
+ public String getMetadataField() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the function to be used by solr to sort result
+ * @param functionArgs variable arguments to be inserted in function
+ * @return
+ */
+ public String getFunction(final Serializable... functionArgs) {
+ final String args = String.join(",", Optional.ofNullable(arguments).orElse(Collections.emptyList()));
+ final String result = function + "(" + args + ")";
+ return MessageFormat.format(result, functionArgs);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java
index d0b0f363e6..8a24b997ff 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java
@@ -22,6 +22,8 @@ import org.dspace.discovery.indexobject.factory.CollectionIndexFactory;
import org.dspace.discovery.indexobject.factory.InprogressSubmissionIndexFactory;
import org.dspace.discovery.indexobject.factory.ItemIndexFactory;
import org.dspace.eperson.EPerson;
+import org.dspace.supervision.SupervisionOrder;
+import org.dspace.supervision.service.SupervisionOrderService;
import org.dspace.util.SolrUtils;
import org.dspace.workflow.WorkflowItem;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +41,9 @@ public abstract class InprogressSubmissionIndexFactoryImpl
@Autowired
protected ItemIndexFactory indexableItemService;
+ @Autowired
+ protected SupervisionOrderService supervisionOrderService;
+
@Override
public SolrInputDocument buildDocument(Context context, T indexableObject) throws SQLException, IOException {
@@ -60,6 +65,8 @@ public abstract class InprogressSubmissionIndexFactoryImpl
submitter.getFullName());
}
+ addSupervisedByFacetIndex(context, item, doc);
+
doc.addField("inprogress.item", new IndexableItem(inProgressSubmission.getItem()).getUniqueIndexID());
// get the location string (for searching by collection & community)
@@ -82,4 +89,13 @@ public abstract class InprogressSubmissionIndexFactoryImpl
indexableItemService.addDiscoveryFields(doc, context, item, discoveryConfigurations);
indexableCollectionService.storeCommunityCollectionLocations(doc, locations);
}
+
+ private void addSupervisedByFacetIndex(Context context, Item item, SolrInputDocument doc) throws SQLException {
+ List supervisionOrders = supervisionOrderService.findByItem(context, item);
+ for (SupervisionOrder supervisionOrder : supervisionOrders) {
+ addFacetIndex(doc, "supervisedBy", supervisionOrder.getGroup().getID().toString(),
+ supervisionOrder.getGroup().getName());
+ }
+
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java
index 15a74b45d1..fc024cc524 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java
@@ -7,6 +7,8 @@
*/
package org.dspace.discovery.indexobject;
+import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES;
+
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -20,6 +22,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@@ -27,6 +31,7 @@ import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrInputDocument;
+import org.dspace.authority.service.AuthorityValueService;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
@@ -89,6 +94,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl
+ * E.g. Author "With Multiple Words" gets stored as:
+ *
+ *
+ * with multiple words ||| With Multiple Words,
+ * multiple words ||| With Multiple Words,
+ * words ||| With Multiple Words,
+ *
+ * in the author_prefix field.
+ * @param doc the solr document
+ * @param searchFilter the current discoverySearchFilter
+ * @param value the metadata value
+ * @param separator the separator being used to separate value part and original value
+ */
+ private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value,
+ String separator, String authority, String preferedLabel) {
+ value = StringUtils.normalizeSpace(value);
+ Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(value);
+ while (matcher.find()) {
+ int index = matcher.start();
+ String currentPart = StringUtils.substring(value, index);
+ if (authority != null) {
+ String facetValue = preferedLabel != null ? preferedLabel : currentPart;
+ doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES,
+ facetValue.toLowerCase() + separator + value
+ + SearchUtils.AUTHORITY_SEPARATOR + authority);
+ } else {
+ doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES,
+ currentPart.toLowerCase() + separator + value);
+ }
+ }
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java
index d7a546d936..fa5cc32813 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java
@@ -245,6 +245,7 @@ public class DiscoverQueryBuilder implements InitializingBean {
// "show more" url
int facetLimit = pageSize + 1;
//This should take care of the sorting for us
+ prefix = StringUtils.isNotBlank(prefix) ? prefix.toLowerCase() : null;
queryArgs.addFacetField(new DiscoverFacetField(facet.getIndexFieldName(), facet.getType(), facetLimit,
facet.getSortOrderSidebar(),
StringUtils.trimToNull(prefix)));
diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java
index d873d7230c..3d4eab125f 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java
@@ -14,6 +14,7 @@ import javax.mail.MessagingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.core.Email;
@@ -51,6 +52,9 @@ public class AccountServiceImpl implements AccountService {
@Autowired
private ConfigurationService configurationService;
+ @Autowired
+ private AuthenticationService authenticationService;
+
protected AccountServiceImpl() {
}
@@ -79,6 +83,9 @@ public class AccountServiceImpl implements AccountService {
if (!configurationService.getBooleanProperty("user.registration", true)) {
throw new IllegalStateException("The user.registration parameter was set to false");
}
+ if (!authenticationService.canSelfRegister(context, null, email)) {
+ throw new IllegalStateException("self registration is not allowed with this email address");
+ }
sendInfo(context, email, true, true);
}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java
new file mode 100644
index 0000000000..72822fb871
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java
@@ -0,0 +1,81 @@
+/**
+ * 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 java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * This enum holds all the possible frequency types
+ * that can be used in "subscription-send" script
+ *
+ * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
+ */
+public enum FrequencyType {
+ DAY("D"),
+ WEEK("W"),
+ MONTH("M");
+
+ private String shortName;
+
+ private FrequencyType(String shortName) {
+ this.shortName = shortName;
+ }
+
+ public static String findLastFrequency(String frequency) {
+ String startDate = "";
+ String endDate = "";
+ Calendar cal = Calendar.getInstance();
+ // Full ISO 8601 is e.g.
+ SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'");
+ SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'");
+ switch (frequency) {
+ case "D":
+ cal.add(Calendar.DAY_OF_MONTH, -1);
+ endDate = fullIsoEnd.format(cal.getTime());
+ startDate = fullIsoStart.format(cal.getTime());
+ break;
+ case "M":
+ int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
+ cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth);
+ endDate = fullIsoEnd.format(cal.getTime());
+ cal.add(Calendar.MONTH, -1);
+ cal.add(Calendar.DAY_OF_MONTH, 1);
+ startDate = fullIsoStart.format(cal.getTime());
+ break;
+ case "W":
+ cal.add(Calendar.DAY_OF_WEEK, -1);
+ int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1;
+ cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek);
+ endDate = fullIsoEnd.format(cal.getTime());
+ cal.add(Calendar.DAY_OF_WEEK, -6);
+ startDate = fullIsoStart.format(cal.getTime());
+ break;
+ default:
+ return null;
+ }
+ return "[" + startDate + " TO " + endDate + "]";
+ }
+
+ public static boolean isSupportedFrequencyType(String value) {
+ for (FrequencyType ft : Arrays.asList(FrequencyType.values())) {
+ if (StringUtils.equals(ft.getShortName(), value)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getShortName() {
+ return shortName;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group.java b/dspace-api/src/main/java/org/dspace/eperson/Group.java
index b2d3964895..6cb534146b 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/Group.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java
@@ -23,7 +23,6 @@ import javax.persistence.Transient;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.DSpaceObject;
import org.dspace.content.DSpaceObjectLegacySupport;
-import org.dspace.content.WorkspaceItem;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -83,9 +82,6 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport {
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
private final List parentGroups = new ArrayList<>();
- @ManyToMany(fetch = FetchType.LAZY, mappedBy = "supervisorGroups")
- private final List supervisedItems = new ArrayList<>();
-
@Transient
private boolean groupsChanged;
@@ -218,10 +214,6 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport {
return legacyId;
}
- public List getSupervisedItems() {
- return supervisedItems;
- }
-
/**
* May this Group be renamed or deleted? (The content of any group may be
* changed.)
diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
index 2b23ecfeef..607e57af0b 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
@@ -353,8 +353,6 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements
List groupCache = group2GroupCacheDAO.findByChildren(context, groups);
// now we have all owning groups, also grab all parents of owning groups
- // yes, I know this could have been done as one big query and a union,
- // but doing the Oracle port taught me to keep to simple SQL!
for (Group2GroupCache group2GroupCache : groupCache) {
groups.add(group2GroupCache.getParent());
}
@@ -480,9 +478,6 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements
context.addEvent(new Event(Event.DELETE, Constants.GROUP, group.getID(),
group.getName(), getIdentifiers(context, group)));
- //Remove the supervised group from any workspace items linked to us.
- group.getSupervisedItems().clear();
-
// Remove any ResourcePolicies that reference this group
authorizeService.removeGroupPolicies(context, group);
diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java
deleted file mode 100644
index 9e5ecaa4fb..0000000000
--- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/**
- * 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 java.io.IOException;
-import java.sql.SQLException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.ResourceBundle;
-import java.util.TimeZone;
-import javax.mail.MessagingException;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.Logger;
-import org.dspace.content.Collection;
-import org.dspace.content.DCDate;
-import org.dspace.content.Item;
-import org.dspace.content.MetadataSchemaEnum;
-import org.dspace.content.MetadataValue;
-import org.dspace.content.factory.ContentServiceFactory;
-import org.dspace.content.service.ItemService;
-import org.dspace.core.Context;
-import org.dspace.core.Email;
-import org.dspace.core.I18nUtil;
-import org.dspace.core.LogHelper;
-import org.dspace.eperson.factory.EPersonServiceFactory;
-import org.dspace.eperson.service.SubscribeService;
-import org.dspace.handle.factory.HandleServiceFactory;
-import org.dspace.handle.service.HandleService;
-import org.dspace.search.Harvest;
-import org.dspace.search.HarvestedItemInfo;
-import org.dspace.services.ConfigurationService;
-import org.dspace.services.factory.DSpaceServicesFactory;
-
-/**
- * CLI tool used for sending new item e-mail alerts to users
- *
- * @author Robert Tansley
- * @version $Revision$
- */
-public class SubscribeCLITool {
-
- private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeCLITool.class);
-
- private static final HandleService handleService
- = HandleServiceFactory.getInstance().getHandleService();
- private static final ItemService itemService
- = ContentServiceFactory.getInstance().getItemService();
- private static final SubscribeService subscribeService
- = EPersonServiceFactory.getInstance().getSubscribeService();
- private static final ConfigurationService configurationService
- = DSpaceServicesFactory.getInstance().getConfigurationService();
-
- /**
- * Default constructor
- */
- private SubscribeCLITool() { }
-
- /**
- * Process subscriptions. This must be invoked only once a day. Messages are
- * only sent out when a collection has actually received new items, so that
- * people's mailboxes are not clogged with many "no new items" mails.
- *
- * Yesterday's newly available items are included. If this is run at for
- * example midday, any items that have been made available during the
- * current day will not be included, but will be included in the next day's
- * run.
- *
- * For example, if today's date is 2002-10-10 (in UTC) items made available
- * during 2002-10-09 (UTC) will be included.
- *
- * @param context The relevant DSpace Context.
- * @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt
- * @throws SQLException An exception that provides information on a database access error or other errors.
- * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
- */
- public static void processDaily(Context context, boolean test) throws SQLException,
- IOException {
- // Grab the subscriptions
-
- List subscriptions = subscribeService.findAll(context);
-
- EPerson currentEPerson = null;
- List collections = null; // List of Collections
-
- // Go through the list collating subscriptions for each e-person
- for (Subscription subscription : subscriptions) {
- // Does this row relate to the same e-person as the last?
- if ((currentEPerson == null)
- || (!subscription.getePerson().getID().equals(currentEPerson
- .getID()))) {
- // New e-person. Send mail for previous e-person
- if (currentEPerson != null) {
-
- try {
- sendEmail(context, currentEPerson, collections, test);
- } catch (MessagingException me) {
- log.error("Failed to send subscription to eperson_id="
- + currentEPerson.getID());
- log.error(me);
- }
- }
-
- currentEPerson = subscription.getePerson();
- collections = new ArrayList<>();
- }
-
- collections.add(subscription.getCollection());
- }
-
- // Process the last person
- if (currentEPerson != null) {
- try {
- sendEmail(context, currentEPerson, collections, test);
- } catch (MessagingException me) {
- log.error("Failed to send subscription to eperson_id="
- + currentEPerson.getID());
- log.error(me);
- }
- }
- }
-
- /**
- * Sends an email to the given e-person with details of new items in the
- * given collections, items that appeared yesterday. No e-mail is sent if
- * there aren't any new items in any of the collections.
- *
- * @param context DSpace context object
- * @param eperson eperson to send to
- * @param collections List of collection IDs (Integers)
- * @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt
- * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
- * @throws MessagingException A general class of exceptions for sending email.
- * @throws SQLException An exception that provides information on a database access error or other errors.
- */
- public static void sendEmail(Context context, EPerson eperson,
- List collections, boolean test) throws IOException, MessagingException,
- SQLException {
- // Get a resource bundle according to the eperson language preferences
- Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
- ResourceBundle labels = ResourceBundle.getBundle("Messages", supportedLocale);
-
- // Get the start and end dates for yesterday
-
- // The date should reflect the timezone as well. Otherwise we stand to lose that information
- // in truncation and roll to an earlier date than intended.
- Calendar cal = Calendar.getInstance(TimeZone.getDefault());
- cal.setTime(new Date());
-
- // What we actually want to pass to Harvest is "Midnight of yesterday in my current timezone"
- // Truncation will actually pass in "Midnight of yesterday in UTC", which will be,
- // at least in CDT, "7pm, the day before yesterday, in my current timezone".
- cal.add(Calendar.HOUR, -24);
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- Date midnightYesterday = cal.getTime();
-
-
- // FIXME: text of email should be more configurable from an
- // i18n viewpoint
- StringBuilder emailText = new StringBuilder();
- boolean isFirst = true;
-
- for (int i = 0; i < collections.size(); i++) {
- Collection c = collections.get(i);
-
- try {
- boolean includeAll = configurationService
- .getBooleanProperty("harvest.includerestricted.subscription", true);
-
- // we harvest all the changed item from yesterday until now
- List itemInfos = Harvest
- .harvest(context, c, new DCDate(midnightYesterday).toString(), null, 0, // Limit
- // and
- // offset
- // zero,
- // get
- // everything
- 0, true, // Need item objects
- false, // But not containers
- false, // Or withdrawals
- includeAll);
-
- if (configurationService.getBooleanProperty("eperson.subscription.onlynew", false)) {
- // get only the items archived yesterday
- itemInfos = filterOutModified(itemInfos);
- } else {
- // strip out the item archived today or
- // not archived yesterday and modified today
- itemInfos = filterOutToday(itemInfos);
- }
-
- // Only add to buffer if there are new items
- if (itemInfos.size() > 0) {
- if (!isFirst) {
- emailText
- .append("\n---------------------------------------\n");
- } else {
- isFirst = false;
- }
-
- emailText.append(labels.getString("org.dspace.eperson.Subscribe.new-items")).append(" ").append(
- c.getName()).append(": ").append(
- itemInfos.size()).append("\n\n");
-
- for (int j = 0; j < itemInfos.size(); j++) {
- HarvestedItemInfo hii = (HarvestedItemInfo) itemInfos
- .get(j);
-
- String title = hii.item.getName();
- emailText.append(" ").append(labels.getString("org.dspace.eperson.Subscribe.title"))
- .append(" ");
-
- if (StringUtils.isNotBlank(title)) {
- emailText.append(title);
- } else {
- emailText.append(labels.getString("org.dspace.eperson.Subscribe.untitled"));
- }
-
- List authors = itemService
- .getMetadata(hii.item, MetadataSchemaEnum.DC.getName(), "contributor", Item.ANY, Item.ANY);
-
- if (authors.size() > 0) {
- emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.authors"))
- .append(" ").append(
- authors.get(0).getValue());
-
- for (int k = 1; k < authors.size(); k++) {
- emailText.append("\n ").append(
- authors.get(k).getValue());
- }
- }
-
- emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.id"))
- .append(" ").append(
- handleService.getCanonicalForm(hii.handle)).append(
- "\n\n");
- }
- }
- } catch (ParseException pe) {
- // This should never get thrown as the Dates are auto-generated
- }
- }
-
- // Send an e-mail if there were any new items
- if (emailText.length() > 0) {
-
- if (test) {
- log.info(LogHelper.getHeader(context, "subscription:", "eperson=" + eperson.getEmail()));
- log.info(LogHelper.getHeader(context, "subscription:", "text=" + emailText.toString()));
-
- } else {
-
- Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscription"));
- email.addRecipient(eperson.getEmail());
- email.addArgument(emailText.toString());
- email.send();
-
- log.info(LogHelper.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID()));
-
- }
-
-
- }
- }
-
- /**
- * Method for invoking subscriptions via the command line
- *
- * @param argv the command line arguments given
- */
- public static void main(String[] argv) {
- String usage = "org.dspace.eperson.Subscribe [-t] or nothing to send out subscriptions.";
-
- Options options = new Options();
- HelpFormatter formatter = new HelpFormatter();
- CommandLine line = null;
-
- {
- Option opt = new Option("t", "test", false, "Run test session");
- opt.setRequired(false);
- options.addOption(opt);
- }
-
- {
- Option opt = new Option("h", "help", false, "Print this help message");
- opt.setRequired(false);
- options.addOption(opt);
- }
-
- try {
- line = new DefaultParser().parse(options, argv);
- } catch (org.apache.commons.cli.ParseException e) {
- // automatically generate the help statement
- formatter.printHelp(usage, e.getMessage(), options, "");
- System.exit(1);
- }
-
- if (line.hasOption("h")) {
- // automatically generate the help statement
- formatter.printHelp(usage, options);
- System.exit(1);
- }
-
- boolean test = line.hasOption("t");
-
- Context context = null;
-
- try {
- context = new Context(Context.Mode.READ_ONLY);
- processDaily(context, test);
- context.complete();
- } catch (IOException | SQLException e) {
- log.fatal(e);
- } finally {
- if (context != null && context.isValid()) {
- // Nothing is actually written
- context.abort();
- }
- }
- }
-
- private static List filterOutToday(List completeList) {
- log.debug("Filtering out all today item to leave new items list size="
- + completeList.size());
- List filteredList = new ArrayList<>();
-
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- String today = sdf.format(new Date());
- // Get the start and end dates for yesterday
- Date thisTimeYesterday = new Date(System.currentTimeMillis()
- - (24 * 60 * 60 * 1000));
- String yesterday = sdf.format(thisTimeYesterday);
-
- for (HarvestedItemInfo infoObject : completeList) {
- Date lastUpdate = infoObject.item.getLastModified();
- String lastUpdateStr = sdf.format(lastUpdate);
-
- // has the item modified today?
- if (lastUpdateStr.equals(today)) {
- List dateAccArr = itemService.getMetadata(infoObject.item, "dc",
- "date", "accessioned", Item.ANY);
- // we need only the item archived yesterday
- if (dateAccArr != null && dateAccArr.size() > 0) {
- for (MetadataValue date : dateAccArr) {
- if (date != null && date.getValue() != null) {
- // if it hasn't been archived today
- if (date.getValue().startsWith(yesterday)) {
- filteredList.add(infoObject);
- log.debug("adding : " + dateAccArr.get(0).getValue()
- + " : " + today + " : "
- + infoObject.handle);
- break;
- } else {
- log.debug("ignoring : " + dateAccArr.get(0).getValue()
- + " : " + today + " : "
- + infoObject.handle);
- }
- }
- }
- } else {
- log.debug("no date accessioned, adding : "
- + infoObject.handle);
- filteredList.add(infoObject);
- }
- } else {
- // the item has been modified yesterday...
- filteredList.add(infoObject);
- }
- }
-
- return filteredList;
- }
-
- private static List filterOutModified(List completeList) {
- log.debug("Filtering out all modified to leave new items list size=" + completeList.size());
- List filteredList = new ArrayList<>();
-
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- // Get the start and end dates for yesterday
- Date thisTimeYesterday = new Date(System.currentTimeMillis()
- - (24 * 60 * 60 * 1000));
- String yesterday = sdf.format(thisTimeYesterday);
-
- for (HarvestedItemInfo infoObject : completeList) {
- List dateAccArr = itemService
- .getMetadata(infoObject.item, "dc", "date", "accessioned", Item.ANY);
-
- if (dateAccArr != null && dateAccArr.size() > 0) {
- for (MetadataValue date : dateAccArr) {
- if (date != null && date.getValue() != null) {
- // if it has been archived yesterday
- if (date.getValue().startsWith(yesterday)) {
- filteredList.add(infoObject);
- log.debug("adding : " + dateAccArr.get(0)
- .getValue() + " : " + yesterday + " : " + infoObject
- .handle);
- break;
- } else {
- log.debug("ignoring : " + dateAccArr.get(0)
- .getValue() + " : " + yesterday + " : " + infoObject
- .handle);
- }
- }
- }
-
-
- } else {
- log.debug("no date accessioned, adding : " + infoObject.handle);
- filteredList.add(infoObject);
- }
- }
-
- return filteredList;
- }
-}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java
index 81c367f0ea..2e4d94f443 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java
@@ -9,11 +9,16 @@ package org.dspace.eperson;
import java.sql.SQLException;
import java.util.List;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
+import org.dspace.content.Community;
+import org.dspace.content.DSpaceObject;
import org.dspace.content.service.CollectionService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -29,106 +34,177 @@ import org.springframework.beans.factory.annotation.Autowired;
* @version $Revision$
*/
public class SubscribeServiceImpl implements SubscribeService {
- /**
- * log4j logger
- */
- private Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeServiceImpl.class);
+
+ private Logger log = LogManager.getLogger(SubscribeServiceImpl.class);
@Autowired(required = true)
- protected SubscriptionDAO subscriptionDAO;
-
+ private SubscriptionDAO subscriptionDAO;
@Autowired(required = true)
- protected AuthorizeService authorizeService;
+ private AuthorizeService authorizeService;
@Autowired(required = true)
- protected CollectionService collectionService;
-
- protected SubscribeServiceImpl() {
-
- }
+ private CollectionService collectionService;
@Override
- public List findAll(Context context) throws SQLException {
- return subscriptionDAO.findAllOrderedByEPerson(context);
- }
-
- @Override
- public void subscribe(Context context, EPerson eperson,
- Collection collection) throws SQLException, AuthorizeException {
- // Check authorisation. Must be administrator, or the eperson.
- if (authorizeService.isAdmin(context)
- || ((context.getCurrentUser() != null) && (context
- .getCurrentUser().getID().equals(eperson.getID())))) {
- if (!isSubscribed(context, eperson, collection)) {
- Subscription subscription = subscriptionDAO.create(context, new Subscription());
- subscription.setCollection(collection);
- subscription.setePerson(eperson);
- }
+ public List findAll(Context context, String resourceType, Integer limit, Integer offset)
+ throws Exception {
+ if (StringUtils.isBlank(resourceType)) {
+ return subscriptionDAO.findAllOrderedByDSO(context, limit, offset);
} else {
- throw new AuthorizeException(
- "Only admin or e-person themselves can subscribe");
+ if (resourceType.equals(Collection.class.getSimpleName()) ||
+ resourceType.equals(Community.class.getSimpleName())) {
+ return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset);
+ } else {
+ log.error("Resource type must be Collection or Community");
+ throw new Exception("Resource type must be Collection or Community");
+ }
}
}
@Override
- public void unsubscribe(Context context, EPerson eperson,
- Collection collection) throws SQLException, AuthorizeException {
+ public Subscription subscribe(Context context, EPerson eperson,
+ DSpaceObject dSpaceObject,
+ List subscriptionParameterList,
+ String type) throws SQLException, AuthorizeException {
// Check authorisation. Must be administrator, or the eperson.
if (authorizeService.isAdmin(context)
- || ((context.getCurrentUser() != null) && (context
- .getCurrentUser().getID().equals(eperson.getID())))) {
- if (collection == null) {
+ || ((context.getCurrentUser() != null) && (context
+ .getCurrentUser().getID().equals(eperson.getID())))) {
+ Subscription newSubscription = subscriptionDAO.create(context, new Subscription());
+ subscriptionParameterList.forEach(subscriptionParameter ->
+ newSubscription.addParameter(subscriptionParameter));
+ newSubscription.setEPerson(eperson);
+ newSubscription.setDSpaceObject(dSpaceObject);
+ newSubscription.setSubscriptionType(type);
+ return newSubscription;
+ } else {
+ throw new AuthorizeException("Only admin or e-person themselves can subscribe");
+ }
+ }
+
+ @Override
+ public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject)
+ throws SQLException, AuthorizeException {
+ // Check authorisation. Must be administrator, or the eperson.
+ if (authorizeService.isAdmin(context)
+ || ((context.getCurrentUser() != null) && (context
+ .getCurrentUser().getID().equals(eperson.getID())))) {
+ if (dSpaceObject == null) {
// Unsubscribe from all
subscriptionDAO.deleteByEPerson(context, eperson);
} else {
- subscriptionDAO.deleteByCollectionAndEPerson(context, collection, eperson);
+ subscriptionDAO.deleteByDSOAndEPerson(context, dSpaceObject, eperson);
log.info(LogHelper.getHeader(context, "unsubscribe",
"eperson_id=" + eperson.getID() + ",collection_id="
- + collection.getID()));
+ + dSpaceObject.getID()));
}
} else {
- throw new AuthorizeException(
- "Only admin or e-person themselves can unsubscribe");
+ throw new AuthorizeException("Only admin or e-person themselves can unsubscribe");
}
}
@Override
- public List getSubscriptions(Context context, EPerson eperson)
- throws SQLException {
- return subscriptionDAO.findByEPerson(context, eperson);
+ public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset)
+ throws SQLException {
+ return subscriptionDAO.findByEPerson(context, eperson, limit, offset);
}
@Override
- public List getAvailableSubscriptions(Context context)
- throws SQLException {
- return getAvailableSubscriptions(context, null);
+ public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson,
+ DSpaceObject dSpaceObject,
+ Integer limit, Integer offset) throws SQLException {
+ return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, limit, offset);
}
@Override
- public List getAvailableSubscriptions(Context context, EPerson eperson)
- throws SQLException {
- List collections;
- if (eperson != null) {
+ public List findAvailableSubscriptions(Context context) throws SQLException {
+ return findAvailableSubscriptions(context, null);
+ }
+
+ @Override
+ public List findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException {
+ if (Objects.nonNull(eperson)) {
context.setCurrentUser(eperson);
}
- collections = collectionService.findAuthorized(context, null, Constants.ADD);
-
- return collections;
+ return collectionService.findAuthorized(context, null, Constants.ADD);
}
@Override
- public boolean isSubscribed(Context context, EPerson eperson,
- Collection collection) throws SQLException {
- return subscriptionDAO.findByCollectionAndEPerson(context, eperson, collection) != null;
+ public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException {
+ return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null;
}
@Override
- public void deleteByCollection(Context context, Collection collection) throws SQLException {
- subscriptionDAO.deleteByCollection(context, collection);
+ public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException {
+ subscriptionDAO.deleteByDspaceObject(context, dSpaceObject);
}
@Override
public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException {
subscriptionDAO.deleteByEPerson(context, ePerson);
}
+
+ @Override
+ public Subscription findById(Context context, int id) throws SQLException {
+ return subscriptionDAO.findByID(context, Subscription.class, id);
+ }
+
+ @Override
+ public Subscription updateSubscription(Context context, Integer id, String subscriptionType,
+ List subscriptionParameterList)
+ throws SQLException {
+ Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id);
+ subscriptionDB.removeParameterList();
+ subscriptionDB.setSubscriptionType(subscriptionType);
+ subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x));
+ subscriptionDAO.save(context, subscriptionDB);
+ return subscriptionDB;
+ }
+
+ @Override
+ public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParam)
+ throws SQLException {
+ Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id);
+ subscriptionDB.addParameter(subscriptionParam);
+ subscriptionDAO.save(context, subscriptionDB);
+ return subscriptionDB;
+ }
+
+ @Override
+ public Subscription removeSubscriptionParameter(Context context,Integer id, SubscriptionParameter subscriptionParam)
+ throws SQLException {
+ Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id);
+ subscriptionDB.removeParameter(subscriptionParam);
+ subscriptionDAO.save(context, subscriptionDB);
+ return subscriptionDB;
+ }
+
+ @Override
+ public void deleteSubscription(Context context, Subscription subscription) throws SQLException {
+ subscriptionDAO.delete(context, subscription);
+ }
+
+ @Override
+ public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
+ String subscriptionType, String frequencyValue) throws SQLException {
+ return subscriptionDAO.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType,
+ frequencyValue);
+ }
+
+ @Override
+ public Long countAll(Context context) throws SQLException {
+ return subscriptionDAO.countAll(context);
+ }
+
+ @Override
+ public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException {
+ return subscriptionDAO.countAllByEPerson(context, ePerson);
+ }
+
+ @Override
+ public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject)
+ throws SQLException {
+ return subscriptionDAO.countAllByEPersonAndDso(context, ePerson, dSpaceObject);
+ }
+
}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java
index 1719888ca8..5db63740f4 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java
@@ -7,6 +7,9 @@
*/
package org.dspace.eperson;
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -15,10 +18,11 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
-import org.dspace.content.Collection;
+import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.core.ReloadableEntity;
@@ -37,40 +41,78 @@ public class Subscription implements ReloadableEntity {
@SequenceGenerator(name = "subscription_seq", sequenceName = "subscription_seq", allocationSize = 1)
private Integer id;
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "collection_id")
- private Collection collection;
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "dspace_object_id")
+ private DSpaceObject dSpaceObject;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "eperson_id")
private EPerson ePerson;
/**
- * Protected constructor, create object using:
- * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, Collection)}
+ * Represent subscription type, for example, "content" or "statistics".
+ *
+ * NOTE: Currently, in DSpace we use only one "content"
*/
- protected Subscription() {
+ @Column(name = "type")
+ private String subscriptionType;
- }
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List subscriptionParameterList = new ArrayList<>();
+
+ /**
+ * Protected constructor, create object using:
+ * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)}
+ */
+ protected Subscription() {}
@Override
public Integer getID() {
return id;
}
- public Collection getCollection() {
- return collection;
+ public DSpaceObject getDSpaceObject() {
+ return this.dSpaceObject;
}
- void setCollection(Collection collection) {
- this.collection = collection;
+ void setDSpaceObject(DSpaceObject dSpaceObject) {
+ this.dSpaceObject = dSpaceObject;
}
- public EPerson getePerson() {
+ public EPerson getEPerson() {
return ePerson;
}
- void setePerson(EPerson ePerson) {
+ public void setEPerson(EPerson ePerson) {
this.ePerson = ePerson;
}
-}
+
+ public String getSubscriptionType() {
+ return subscriptionType;
+ }
+
+ public void setSubscriptionType(String subscriptionType) {
+ this.subscriptionType = subscriptionType;
+ }
+
+ public List getSubscriptionParameterList() {
+ return subscriptionParameterList;
+ }
+
+ public void setSubscriptionParameterList(List subscriptionList) {
+ this.subscriptionParameterList = subscriptionList;
+ }
+
+ public void addParameter(SubscriptionParameter subscriptionParameter) {
+ subscriptionParameterList.add(subscriptionParameter);
+ subscriptionParameter.setSubscription(this);
+ }
+
+ public void removeParameterList() {
+ subscriptionParameterList.clear();
+ }
+
+ public void removeParameter(SubscriptionParameter subscriptionParameter) {
+ subscriptionParameterList.remove(subscriptionParameter);
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java
new file mode 100644
index 0000000000..7526535d7f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java
@@ -0,0 +1,98 @@
+/**
+ * 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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Database entity representation of the subscription_parameter table
+ * SubscriptionParameter represents a frequency with which an user wants to be notified.
+ *
+ * @author Alba Aliu at atis.al
+ */
+@Entity
+@Table(name = "subscription_parameter")
+public class SubscriptionParameter implements ReloadableEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq")
+ @SequenceGenerator(name = "subscription_parameter_seq", sequenceName = "subscription_parameter_seq",
+ allocationSize = 1)
+ @Column(name = "subscription_parameter_id", unique = true)
+ private Integer id;
+
+ @ManyToOne
+ @JoinColumn(name = "subscription_id", nullable = false)
+ private Subscription subscription;
+
+ /*
+ * Currently, we have only one use case for this attribute: "frequency"
+ */
+ @Column
+ private String name;
+
+ /*
+ * Currently, we use this attribute only with following values: "D", "W", "M".
+ * Where D stand for Day, W stand for Week and M stand for Month
+ */
+ @Column
+ private String value;
+
+ public SubscriptionParameter() {}
+
+ public SubscriptionParameter(Integer id, Subscription subscription, String name, String value) {
+ this.id = id;
+ this.subscription = subscription;
+ this.name = name;
+ this.value = value;
+ }
+
+ public Subscription getSubscription() {
+ return subscription;
+ }
+
+ public void setSubscription(Subscription subscription) {
+ this.subscription = subscription;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java
deleted file mode 100644
index 64180a5e22..0000000000
--- a/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * 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 java.sql.SQLException;
-
-import org.dspace.authorize.AuthorizeException;
-import org.dspace.authorize.ResourcePolicy;
-import org.dspace.authorize.service.ResourcePolicyService;
-import org.dspace.content.Item;
-import org.dspace.content.WorkspaceItem;
-import org.dspace.content.service.ItemService;
-import org.dspace.core.Constants;
-import org.dspace.core.Context;
-import org.dspace.eperson.service.SupervisorService;
-import org.springframework.beans.factory.annotation.Autowired;
-
-public class SupervisorServiceImpl implements SupervisorService {
-
- @Autowired(required = true)
- protected ItemService itemService;
- @Autowired(required = true)
- protected ResourcePolicyService resourcePolicyService;
-
- protected SupervisorServiceImpl() {
- }
-
- @Override
- public boolean isOrder(Context context, WorkspaceItem workspaceItem, Group group)
- throws SQLException {
- return workspaceItem.getSupervisorGroups().contains(group);
- }
-
- @Override
- public void remove(Context context, WorkspaceItem workspaceItem, Group group)
- throws SQLException, AuthorizeException {
- // get the workspace item and the group from the request values
- workspaceItem.getSupervisorGroups().remove(group);
-
- // get the item and have it remove the policies for the group
- Item item = workspaceItem.getItem();
- itemService.removeGroupPolicies(context, item, group);
- }
-
- @Override
- public void add(Context context, Group group, WorkspaceItem workspaceItem, int policy)
- throws SQLException, AuthorizeException {
- // make a table row in the database table, and update with the relevant
- // details
- workspaceItem.getSupervisorGroups().add(group);
- group.getSupervisedItems().add(workspaceItem);
-
- // If a default policy type has been requested, apply the policies using
- // the DSpace API for doing so
- if (policy != POLICY_NONE) {
- Item item = workspaceItem.getItem();
-
- // "Editor" implies READ, WRITE, ADD permissions
- // "Observer" implies READ permissions
- if (policy == POLICY_EDITOR) {
- ResourcePolicy r = resourcePolicyService.create(context);
- r.setdSpaceObject(item);
- r.setGroup(group);
- r.setAction(Constants.READ);
- resourcePolicyService.update(context, r);
-
- r = resourcePolicyService.create(context);
- r.setdSpaceObject(item);
- r.setGroup(group);
- r.setAction(Constants.WRITE);
- resourcePolicyService.update(context, r);
-
- r = resourcePolicyService.create(context);
- r.setdSpaceObject(item);
- r.setGroup(group);
- r.setAction(Constants.ADD);
- resourcePolicyService.update(context, r);
-
- } else if (policy == POLICY_OBSERVER) {
- ResourcePolicy r = resourcePolicyService.create(context);
- r.setdSpaceObject(item);
- r.setGroup(group);
- r.setAction(Constants.READ);
- resourcePolicyService.update(context, r);
- }
- }
- }
-}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java
index e9f2d57059..4d762c1775 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java
@@ -10,7 +10,7 @@ package org.dspace.eperson.dao;
import java.sql.SQLException;
import java.util.List;
-import org.dspace.content.Collection;
+import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.core.GenericDAO;
import org.dspace.eperson.EPerson;
@@ -26,17 +26,125 @@ import org.dspace.eperson.Subscription;
*/
public interface SubscriptionDAO extends GenericDAO {
- public void deleteByCollection(Context context, Collection collection) throws SQLException;
+ /**
+ * Delete all subscription of provided dSpaceObject
+ *
+ * @param context DSpace context object
+ * @param dSpaceObject DSpace resource
+ * @throws SQLException If database error
+ */
+ public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException;
- public List findByEPerson(Context context, EPerson eperson) throws SQLException;
+ /**
+ * Return a paginated list of all subscriptions of the eperson
+ *
+ * @param context DSpace context object
+ * @param eperson ePerson whose subscriptions want to find
+ * @param limit Paging limit
+ * @param offset The position of the first result to return
+ * @return
+ * @throws SQLException If database error
+ */
+ public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset)
+ throws SQLException;
- public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection)
- throws SQLException;
+ /**
+ * Return a paginated list of subscriptions related to a DSpaceObject belong to an ePerson
+ *
+ * @param context DSpace context object
+ * @param eperson ePerson whose subscriptions want to find
+ * @param dSpaceObject DSpaceObject of whom subscriptions want to find
+ * @param limit Paging limit
+ * @param offset The position of the first result to return
+ * @return
+ * @throws SQLException If database error
+ */
+ public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject,
+ Integer limit, Integer offset) throws SQLException;
+ /**
+ * Delete all subscription of provided ePerson
+ *
+ * @param context DSpace context object
+ * @param eperson ePerson whose subscriptions want to delete
+ * @throws SQLException If database error
+ */
public void deleteByEPerson(Context context, EPerson eperson) throws SQLException;
- public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson)
- throws SQLException;
+ /**
+ * Delete all subscriptions related to a DSpaceObject belong to an ePerson
+ *
+ * @param context DSpace context object
+ * @param dSpaceObject DSpaceObject of whom subscriptions want to delete
+ * @param eperson ePerson whose subscriptions want to delete
+ * @throws SQLException If database error
+ */
+ public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException;
+
+ /**
+ * Return a paginated list of all subscriptions ordered by ID and resourceType
+ *
+ * @param context DSpace context object
+ * @param resourceType Could be Collection or Community
+ * @param limit Paging limit
+ * @param offset The position of the first result to return
+ * @return
+ * @throws SQLException If database error
+ */
+ public List findAllOrderedByIDAndResourceType(Context context, String resourceType,
+ Integer limit, Integer offset) throws SQLException;
+
+ /**
+ * Return a paginated list of subscriptions ordered by DSpaceObject
+ *
+ * @param context DSpace context object
+ * @param limit Paging limit
+ * @param offset The position of the first result to return
+ * @return
+ * @throws SQLException If database error
+ */
+ public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException;
+
+ /**
+ * Return a list of all subscriptions by subscriptionType and frequency
+ *
+ * @param context DSpace context object
+ * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content"
+ * @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month
+ * @return
+ * @throws SQLException If database error
+ */
+ public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
+ String subscriptionType, String frequencyValue) throws SQLException;
+
+ /**
+ * Count all subscriptions
+ *
+ * @param context DSpace context object
+ * @return Total of all subscriptions
+ * @throws SQLException If database error
+ */
+ public Long countAll(Context context) throws SQLException;
+
+ /**
+ * Count all subscriptions belong to an ePerson
+ *
+ * @param context DSpace context object
+ * @param ePerson ePerson whose subscriptions want count
+ * @return Total of all subscriptions belong to an ePerson
+ * @throws SQLException If database error
+ */
+ public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException;
+
+ /**
+ * Count all subscriptions related to a DSpaceObject belong to an ePerson
+ *
+ * @param context DSpace context object
+ * @param ePerson ePerson whose subscriptions want count
+ * @param dSpaceObject DSpaceObject of whom subscriptions want count
+ * @return
+ * @throws SQLException If database error
+ */
+ public Long countAllByEPersonAndDso(Context context, EPerson ePerson,DSpaceObject dSpaceObject) throws SQLException;
- public List findAllOrderedByEPerson(Context context) throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java
new file mode 100644
index 0000000000..ea9c7b0bbd
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java
@@ -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.eperson.dao;
+import org.dspace.core.GenericDAO;
+import org.dspace.eperson.SubscriptionParameter;
+
+
+/**
+ * Database Access Object interface class for the SubscriptionParamter object.
+ * The implementation of this class is responsible for all database calls for the SubscriptionParameter object and is
+ * autowired by spring
+ * This class should only be accessed from a single service and should never be exposed outside of the API
+ *
+ * @author Alba Aliu at atis.al
+ */
+public interface SubscriptionParameterDAO extends GenericDAO {
+}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java
index 6f2cb4b4fb..6c36211f31 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java
@@ -9,17 +9,21 @@ package org.dspace.eperson.dao.impl;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Join;
import javax.persistence.criteria.Root;
-import org.dspace.content.Collection;
+import org.dspace.content.DSpaceObject;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Subscription;
+import org.dspace.eperson.SubscriptionParameter;
+import org.dspace.eperson.SubscriptionParameter_;
import org.dspace.eperson.Subscription_;
import org.dspace.eperson.dao.SubscriptionDAO;
@@ -31,42 +35,50 @@ import org.dspace.eperson.dao.SubscriptionDAO;
* @author kevinvandevelde at atmire.com
*/
public class SubscriptionDAOImpl extends AbstractHibernateDAO implements SubscriptionDAO {
+
protected SubscriptionDAOImpl() {
super();
}
@Override
- public List findByEPerson(Context context, EPerson eperson) throws SQLException {
+ public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset)
+ throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
Root subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson));
- return list(context, criteriaQuery, false, Subscription.class, -1, -1);
-
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject)));
+ criteriaQuery.orderBy(orderList);
+ return list(context, criteriaQuery, false, Subscription.class, limit, offset);
}
@Override
- public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection)
- throws SQLException {
+ public List findByEPersonAndDso(Context context, EPerson eperson,
+ DSpaceObject dSpaceObject,
+ Integer limit, Integer offset) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
- javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
+ javax.persistence.criteria.CriteriaQuery criteriaQuery =
+ getCriteriaQuery(criteriaBuilder, Subscription.class);
Root subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
- criteriaQuery
- .where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson),
- criteriaBuilder.equal(subscriptionRoot.get(Subscription_.collection), collection)
- )
- );
- return singleResult(context, criteriaQuery);
+ criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(
+ subscriptionRoot.get(Subscription_.ePerson), eperson),
+ criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject)
+ ));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject)));
+ criteriaQuery.orderBy(orderList);
+ return list(context, criteriaQuery, false, Subscription.class, limit, offset);
}
@Override
- public void deleteByCollection(Context context, Collection collection) throws SQLException {
- String hqlQuery = "delete from Subscription where collection=:collection";
+ public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException {
+ String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject";
Query query = createQuery(context, hqlQuery);
- query.setParameter("collection", collection);
+ query.setParameter("dSpaceObject", dSpaceObject);
query.executeUpdate();
}
@@ -79,28 +91,98 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl
}
@Override
- public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson)
- throws SQLException {
- String hqlQuery = "delete from Subscription where collection=:collection AND ePerson=:ePerson";
+ public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson)
+ throws SQLException {
+ String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject AND ePerson=:ePerson";
Query query = createQuery(context, hqlQuery);
- query.setParameter("collection", collection);
+ query.setParameter("dSpaceObject", dSpaceObject);
query.setParameter("ePerson", eperson);
query.executeUpdate();
}
@Override
- public List findAllOrderedByEPerson(Context context) throws SQLException {
-
+ public List findAllOrderedByIDAndResourceType(Context context, String resourceType,
+ Integer limit, Integer offset) throws SQLException {
+ String hqlQuery = "select s from Subscription s join %s dso " +
+ "ON dso.id = s.dSpaceObject ORDER BY subscription_id";
+ if (resourceType != null) {
+ hqlQuery = String.format(hqlQuery, resourceType);
+ }
+ Query query = createQuery(context, hqlQuery);
+ if (limit != -1) {
+ query.setMaxResults(limit);
+ }
+ if (offset != -1) {
+ query.setFirstResult(offset);
+ }
+ query.setHint("org.hibernate.cacheable", false);
+ return query.getResultList();
+ }
+ @Override
+ public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
Root subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject)));
+ criteriaQuery.orderBy(orderList);
+ return list(context, criteriaQuery, false, Subscription.class, limit, offset);
+ }
+ @Override
+ public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
+ String subscriptionType, String frequencyValue) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
+ Root subscriptionRoot = criteriaQuery.from(Subscription.class);
+ criteriaQuery.select(subscriptionRoot);
+ Join childJoin = subscriptionRoot.join("subscriptionParameterList");
+ criteriaQuery.where(
+ criteriaBuilder.and(
+ criteriaBuilder.equal(subscriptionRoot.get(Subscription_.SUBSCRIPTION_TYPE), subscriptionType),
+ criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"),
+ criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue)
+ ));
List orderList = new ArrayList<>(1);
orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson)));
+ orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id)));
criteriaQuery.orderBy(orderList);
-
- return list(context, criteriaQuery, false, Subscription.class, -1, -1);
+ return list(context, criteriaQuery, false, Subscription.class, 10000, -1);
}
+
+ @Override
+ public Long countAll(Context context) throws SQLException {
+ CriteriaBuilder qb = getCriteriaBuilder(context);
+ CriteriaQuery cq = qb.createQuery(Long.class);
+ cq.select(qb.count(cq.from(Subscription.class)));
+ Query query = this.getHibernateSession(context).createQuery(cq);
+ return (Long) query.getSingleResult();
+ }
+
+ @Override
+ public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException {
+ CriteriaBuilder qb = getCriteriaBuilder(context);
+ CriteriaQuery cq = qb.createQuery(Long.class);
+ Root subscriptionRoot = cq.from(Subscription.class);
+ cq.select(qb.count(subscriptionRoot));
+ cq.where(qb.equal(subscriptionRoot.get(Subscription_.ePerson), ePerson));
+ Query query = this.getHibernateSession(context).createQuery(cq);
+ return (Long) query.getSingleResult();
+ }
+
+ @Override
+ public Long countAllByEPersonAndDso(Context context,
+ EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException {
+ CriteriaBuilder qb = getCriteriaBuilder(context);
+ CriteriaQuery cq = qb.createQuery(Long.class);
+ Root subscriptionRoot = cq.from(Subscription.class);
+ cq.select(qb.count(subscriptionRoot));
+ cq.where(qb.and(qb.equal(subscriptionRoot.get(Subscription_.ePerson)
+ , ePerson), qb.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject)));
+ Query query = this.getHibernateSession(context).createQuery(cq);
+ return (Long) query.getSingleResult();
+ }
+
}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java
new file mode 100644
index 0000000000..37af787ed3
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java
@@ -0,0 +1,28 @@
+/**
+ * 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.dao.impl;
+
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.eperson.SubscriptionParameter;
+import org.dspace.eperson.dao.SubscriptionParameterDAO;
+
+/**
+ * Hibernate implementation of the Database Access Object interface class for the SubscriptionParameter object.
+ * This class is responsible for all database calls for the SubscriptionParameter object and is autowired by spring
+ * This class should never be accessed directly.
+ *
+ * @author Alba Aliu at atis.al
+ */
+public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO
+ implements SubscriptionParameterDAO {
+
+ protected SubscriptionParameterDAOImpl() {
+ super();
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java
index f7ce13a8a3..b80c37f13f 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java
@@ -12,7 +12,6 @@ import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.eperson.service.RegistrationDataService;
import org.dspace.eperson.service.SubscribeService;
-import org.dspace.eperson.service.SupervisorService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
@@ -33,8 +32,6 @@ public abstract class EPersonServiceFactory {
public abstract SubscribeService getSubscribeService();
- public abstract SupervisorService getSupervisorService();
-
public static EPersonServiceFactory getInstance() {
return DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("ePersonServiceFactory", EPersonServiceFactory.class);
diff --git a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java
index 33d9249b6b..c4a6cbe996 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java
@@ -12,7 +12,6 @@ import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.eperson.service.RegistrationDataService;
import org.dspace.eperson.service.SubscribeService;
-import org.dspace.eperson.service.SupervisorService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -33,8 +32,6 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory {
private AccountService accountService;
@Autowired(required = true)
private SubscribeService subscribeService;
- @Autowired(required = true)
- private SupervisorService supervisorService;
@Override
public EPersonService getEPersonService() {
@@ -61,8 +58,4 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory {
return subscribeService;
}
- @Override
- public SupervisorService getSupervisorService() {
- return supervisorService;
- }
}
diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java
index 347c69bf5b..e70f40e0ed 100644
--- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java
+++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java
@@ -12,9 +12,11 @@ import java.util.List;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
+import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Subscription;
+import org.dspace.eperson.SubscriptionParameter;
/**
* Service interface class for the Subscription object.
@@ -31,49 +33,74 @@ public interface SubscribeService {
* new item appears in the collection.
*
* @param context DSpace context
+ * @param limit Number of subscriptions to return
+ * @param offset Offset number
* @return list of Subscription objects
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
- public List findAll(Context context) throws SQLException;
+ public List findAll(Context context, String resourceType, Integer limit, Integer offset)
+ throws Exception;
/**
- * Subscribe an e-person to a collection. An e-mail will be sent every day a
- * new item appears in the collection.
- *
- * @param context DSpace context
- * @param eperson EPerson to subscribe
- * @param collection Collection to subscribe to
- * @throws SQLException An exception that provides information on a database access error or other errors.
- * @throws AuthorizeException Exception indicating the current user of the context does not have permission
- * to perform a particular action.
+ * Subscribe an EPerson to a dSpaceObject (Collection or Community). An e-mail will be sent every day a
+ * new item appears in the Collection or Community.
+ *
+ * @param context DSpace context object
+ * @param eperson EPerson to subscribe
+ * @param dSpaceObject DSpaceObject to subscribe
+ * @param subscriptionParameters list of @SubscriptionParameter
+ * @param subscriptionType Currently supported only "content"
+ * @return
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ * @throws AuthorizeException Exception indicating the current user of the context does not have permission
+ * to perform a particular action.
*/
- public void subscribe(Context context, EPerson eperson,
- Collection collection) throws SQLException, AuthorizeException;
+ public Subscription subscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject,
+ List subscriptionParameters,
+ String subscriptionType) throws SQLException, AuthorizeException;
/**
* Unsubscribe an e-person to a collection. Passing in null
* for the collection unsubscribes the e-person from all collections they
* are subscribed to.
*
- * @param context DSpace context
- * @param eperson EPerson to unsubscribe
- * @param collection Collection to unsubscribe from
+ * @param context DSpace context
+ * @param eperson EPerson to unsubscribe
+ * @param dSpaceObject DSpaceObject to unsubscribe from
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
- public void unsubscribe(Context context, EPerson eperson,
- Collection collection) throws SQLException, AuthorizeException;
+ public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject)
+ throws SQLException, AuthorizeException;
/**
* Find out which collections an e-person is subscribed to
*
* @param context DSpace context
* @param eperson EPerson
+ * @param limit Number of subscriptions to return
+ * @param offset Offset number
* @return array of collections e-person is subscribed to
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
- public List getSubscriptions(Context context, EPerson eperson) throws SQLException;
+ public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset)
+ throws SQLException;
+
+ /**
+ * Find out which collections an e-person is subscribed to and related with dso
+ *
+ * @param context DSpace context
+ * @param eperson EPerson
+ * @param dSpaceObject DSpaceObject
+ * @param limit Number of subscriptions to return
+ * @param offset Offset number
+ * @return array of collections e-person is subscribed to and related with dso
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson,
+ DSpaceObject dSpaceObject,
+ Integer limit, Integer offset) throws SQLException;
/**
* Find out which collections the currently logged in e-person can subscribe to
@@ -82,8 +109,7 @@ public interface SubscribeService {
* @return array of collections the currently logged in e-person can subscribe to
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
- public List getAvailableSubscriptions(Context context)
- throws SQLException;
+ public List findAvailableSubscriptions(Context context) throws SQLException;
/**
* Find out which collections an e-person can subscribe to
@@ -93,29 +119,27 @@ public interface SubscribeService {
* @return array of collections e-person can subscribe to
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
- public List getAvailableSubscriptions(Context context, EPerson eperson)
- throws SQLException;
+ public List findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException;
/**
* Is that e-person subscribed to that collection?
*
- * @param context DSpace context
- * @param eperson find out if this e-person is subscribed
- * @param collection find out if subscribed to this collection
+ * @param context DSpace context
+ * @param eperson find out if this e-person is subscribed
+ * @param dSpaceObject find out if subscribed to this dSpaceObject
* @return true
if they are subscribed
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
- public boolean isSubscribed(Context context, EPerson eperson,
- Collection collection) throws SQLException;
+ public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException;
/**
* Delete subscription by collection.
*
- * @param context DSpace context
- * @param collection find out if subscribed to this collection
+ * @param context DSpace context
+ * @param dSpaceObject find out if subscribed to this dSpaceObject
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
- public void deleteByCollection(Context context, Collection collection) throws SQLException;
+ public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException;
/**
* Delete subscription by eperson (subscriber).
@@ -125,4 +149,92 @@ public interface SubscribeService {
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException;
-}
+
+ /**
+ * Finds a subscription by id
+ *
+ * @param context DSpace context
+ * @param id the id of subscription to be searched
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public Subscription findById(Context context, int id) throws SQLException;
+
+ /**
+ * Updates a subscription by id
+ *
+ * @param context DSpace context
+ * @param id Integer id
+ * @param subscriptionParameterList List subscriptionParameterList
+ * @param subscriptionType type
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public Subscription updateSubscription(Context context, Integer id, String subscriptionType,
+ List subscriptionParameterList) throws SQLException;
+
+ /**
+ * Adds a parameter to a subscription
+ *
+ * @param context DSpace context
+ * @param id Integer id
+ * @param subscriptionParameter SubscriptionParameter subscriptionParameter
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public Subscription addSubscriptionParameter(Context context,Integer id,
+ SubscriptionParameter subscriptionParameter) throws SQLException;
+
+ /**
+ * Deletes a parameter from subscription
+ *
+ * @param context DSpace context
+ * @param id Integer id
+ * @param subscriptionParam SubscriptionParameter subscriptionParameter
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public Subscription removeSubscriptionParameter(Context context, Integer id,
+ SubscriptionParameter subscriptionParam) throws SQLException;
+
+ /**
+ * Deletes a subscription
+ *
+ * @param context DSpace context
+ * @param subscription The subscription to delete
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public void deleteSubscription(Context context, Subscription subscription) throws SQLException;
+
+ /**
+ * Finds all subscriptions by subscriptionType and frequency
+ *
+ * @param context DSpace context
+ * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content"
+ * @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
+ String subscriptionType, String frequencyValue) throws SQLException;
+
+ /**
+ * Counts all subscriptions
+ *
+ * @param context DSpace context
+ */
+ public Long countAll(Context context) throws SQLException;
+
+ /**
+ * Counts all subscriptions by ePerson
+ *
+ * @param context DSpace context
+ * @param ePerson EPerson ePerson
+ */
+ public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException;
+
+ /**
+ * Counts all subscriptions by ePerson and DSO
+ *
+ * @param context DSpace context
+ * @param ePerson EPerson ePerson
+ * @param dSpaceObject DSpaceObject dSpaceObject
+ */
+ public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException;
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java
deleted file mode 100644
index 470c9133e5..0000000000
--- a/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * 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.service;
-
-import java.sql.SQLException;
-
-import org.dspace.authorize.AuthorizeException;
-import org.dspace.content.WorkspaceItem;
-import org.dspace.core.Context;
-import org.dspace.eperson.Group;
-
-/**
- * Class to represent the supervisor, primarily for use in applying supervisor
- * activities to the database, such as setting and unsetting supervision
- * orders and so forth.
- *
- * @author Richard Jones
- * @version $Revision$
- */
-public interface SupervisorService {
-
- /**
- * value to use for no policy set
- */
- public static final int POLICY_NONE = 0;
-
- /**
- * value to use for editor policies
- */
- public static final int POLICY_EDITOR = 1;
-
- /**
- * value to use for observer policies
- */
- public static final int POLICY_OBSERVER = 2;
-
- /**
- * finds out if there is a supervision order that matches this set
- * of values
- *
- * @param context the context this object exists in
- * @param workspaceItem the workspace item to be supervised
- * @param group the group to be doing the supervising
- * @return boolean true if there is an order that matches, false if not
- * @throws SQLException An exception that provides information on a database access error or other errors.
- */
- public boolean isOrder(Context context, WorkspaceItem workspaceItem, Group group)
- throws SQLException;
-
- /**
- * removes the requested group from the requested workspace item in terms
- * of supervision. This also removes all the policies that group has
- * associated with the item
- *
- * @param context the context this object exists in
- * @param workspaceItem the ID of the workspace item
- * @param group the ID of the group to be removed from the item
- * @throws SQLException An exception that provides information on a database access error or other errors.
- * @throws AuthorizeException Exception indicating the current user of the context does not have permission
- * to perform a particular action.
- */
- public void remove(Context context, WorkspaceItem workspaceItem, Group group)
- throws SQLException, AuthorizeException;
-
- /**
- * adds a supervision order to the database
- *
- * @param context the context this object exists in
- * @param group the ID of the group which will supervise
- * @param workspaceItem the ID of the workspace item to be supervised
- * @param policy String containing the policy type to be used
- * @throws SQLException An exception that provides information on a database access error or other errors.
- * @throws AuthorizeException Exception indicating the current user of the context does not have permission
- * to perform a particular action.
- */
- public void add(Context context, Group group, WorkspaceItem workspaceItem, int policy)
- throws SQLException, AuthorizeException;
-}
diff --git a/dspace-api/src/main/java/org/dspace/event/Consumer.java b/dspace-api/src/main/java/org/dspace/event/Consumer.java
index 1a8b16e98a..f56efcc7ba 100644
--- a/dspace-api/src/main/java/org/dspace/event/Consumer.java
+++ b/dspace-api/src/main/java/org/dspace/event/Consumer.java
@@ -10,18 +10,16 @@ package org.dspace.event;
import org.dspace.core.Context;
/**
- * Interface for content event consumers. Note that the consumer cannot tell if
- * it is invoked synchronously or asynchronously; the consumer interface and
- * sequence of calls is the same for both. Asynchronous consumers may see more
- * consume() calls between the start and end of the event stream, if they are
- * invoked asynchronously, once in a long time period, rather than synchronously
- * after every Context.commit().
- *
- * @version $Revision$
+ * Interface for content event consumers. Note that the consumer cannot tell
+ * if it is invoked synchronously or asynchronously; the consumer interface
+ * and sequence of calls is the same for both. Asynchronous consumers may see
+ * more consume() calls between the start and end of the event stream, if they
+ * are invoked asynchronously, once in a long time period, rather than
+ * synchronously after every Context.commit().
*/
public interface Consumer {
/**
- * Initialize - allocate any resources required to operate. This may include
+ * Allocate any resources required to operate. This may include
* initializing any pooled JMS resources. Called ONCE when created by the
* dispatcher pool. This should be used to set up expensive resources that
* will remain for the lifetime of the consumer.
@@ -31,12 +29,17 @@ public interface Consumer {
public void initialize() throws Exception;
/**
- * Consume an event; events may get filtered at the dispatcher level, hiding
- * it from the consumer. This behavior is based on the dispatcher/consumer
- * configuration. Should include logic to initialize any resources required
- * for a batch of events.
+ * Consume an event. Events may be filtered by a dispatcher, hiding them
+ * from the consumer. This behavior is based on the dispatcher/consumer
+ * configuration. Should include logic to initialize any resources
+ * required for a batch of events.
*
- * @param ctx the execution context object
+ * This method must not commit the context. Committing causes
+ * re-dispatch of the event queue, which can result in infinite recursion
+ * leading to memory exhaustion as seen in
+ * {@link https://github.com/DSpace/DSpace/pull/8756}.
+ *
+ * @param ctx the current DSpace session
* @param event the content event
* @throws Exception if error
*/
diff --git a/dspace-api/src/main/java/org/dspace/event/package-info.java b/dspace-api/src/main/java/org/dspace/event/package-info.java
new file mode 100644
index 0000000000..544dfb271a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/event/package-info.java
@@ -0,0 +1,20 @@
+/**
+ * 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/
+ */
+/**
+ * Actions which alter DSpace model objects can queue {@link Event}s, which
+ * are presented to {@link Consumer}s by a {@link Dispatcher}. A pool of
+ * {@code Dispatcher}s is managed by an {@link service.EventService}, guided
+ * by configuration properties {@code event.dispatcher.*}.
+ *
+ *
One must be careful not to commit the current DSpace {@code Context}
+ * during event dispatch. {@code commit()} triggers event dispatching, and
+ * doing this during event dispatch can lead to infinite recursion and
+ * memory exhaustion.
+ */
+
+package org.dspace.event;
diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java
index 5e45d6324d..b0aa4aba13 100644
--- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java
+++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java
@@ -335,7 +335,7 @@ public class OpenAIRERestConnector {
/**
* tokenUsage true to enable the usage of an access token
*
- * @param tokenUsage
+ * @param tokenEnabled true/false
*/
@Autowired(required = false)
public void setTokenEnabled(boolean tokenEnabled) {
diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java
index 962183fa6f..2e934462c9 100644
--- a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java
+++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java
@@ -57,7 +57,7 @@ public class LiveImportDataProvider extends AbstractExternalDataProvider {
/**
* This method set the MetadataSource for the ExternalDataProvider
- * @param metadataSource {@link org.dspace.importer.external.service.components.MetadataSource} implementation used to process the input data
+ * @param querySource Source {@link org.dspace.importer.external.service.components.QuerySource} implementation used to process the input data
*/
public void setMetadataSource(QuerySource querySource) {
this.querySource = querySource;
diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java
index 0efaea75e0..aa730fe2b1 100644
--- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java
@@ -9,6 +9,7 @@ package org.dspace.handle;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -211,17 +212,17 @@ public class HandleServiceImpl implements HandleService {
@Override
public void unbindHandle(Context context, DSpaceObject dso)
throws SQLException {
- List handles = getInternalHandles(context, dso);
- if (CollectionUtils.isNotEmpty(handles)) {
- for (Handle handle : handles) {
+ Iterator handles = dso.getHandles().iterator();
+ if (handles.hasNext()) {
+ while (handles.hasNext()) {
+ final Handle handle = handles.next();
+ handles.remove();
//Only set the "resouce_id" column to null when unbinding a handle.
// We want to keep around the "resource_type_id" value, so that we
// can verify during a restore whether the same *type* of resource
// is reusing this handle!
handle.setDSpaceObject(null);
- //Also remove the handle from the DSO list to keep a consistent model
- dso.getHandles().remove(handle);
handleDAO.save(context, handle);
@@ -256,7 +257,7 @@ public class HandleServiceImpl implements HandleService {
@Override
public String findHandle(Context context, DSpaceObject dso)
throws SQLException {
- List handles = getInternalHandles(context, dso);
+ List handles = dso.getHandles();
if (CollectionUtils.isEmpty(handles)) {
return null;
} else {
@@ -328,20 +329,6 @@ public class HandleServiceImpl implements HandleService {
////////////////////////////////////////
// Internal methods
////////////////////////////////////////
-
- /**
- * Return the handle for an Object, or null if the Object has no handle.
- *
- * @param context DSpace context
- * @param dso DSpaceObject for which we require our handles
- * @return The handle for object, or null if the object has no handle.
- * @throws SQLException If a database error occurs
- */
- protected List getInternalHandles(Context context, DSpaceObject dso)
- throws SQLException {
- return handleDAO.getHandlesByDSpaceObject(context, dso);
- }
-
/**
* Find the database row corresponding to handle.
*
diff --git a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java
index 5a6b26da93..5aeb40bdd9 100644
--- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java
+++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java
@@ -455,6 +455,7 @@ public class OAIHarvester {
harvestRow.setHarvestStartTime(startTime);
harvestRow.setHarvestMessage("Harvest from " + oaiSource + " successful");
harvestRow.setHarvestStatus(HarvestedCollection.STATUS_READY);
+ harvestRow.setLastHarvested(startTime);
log.info(
"Harvest from " + oaiSource + " successful. The process took " + timeTaken + " milliseconds. Harvested "
+ currentRecord + " items.");
@@ -567,11 +568,7 @@ public class OAIHarvester {
// Import the actual bitstreams
if (harvestRow.getHarvestType() == 3) {
log.info("Running ORE ingest on: " + item.getHandle());
-
- List allBundles = item.getBundles();
- for (Bundle bundle : allBundles) {
- itemService.removeBundle(ourContext, item, bundle);
- }
+ itemService.removeAllBundles(ourContext, item);
ORExwalk.ingest(ourContext, item, oreREM, true);
}
} else {
diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java
index 66e7b94a4b..b70eda960d 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java
@@ -21,6 +21,7 @@ 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.logic.TrueFilter;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -28,6 +29,7 @@ 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.dspace.services.factory.DSpaceServicesFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +46,7 @@ import org.springframework.beans.factory.annotation.Autowired;
* Any identifier a method of this class returns is a string in the following format: doi:10.123/456.
*
* @author Pascal-Nicolas Becker
+ * @author Kim Shepherd
*/
public class DOIIdentifierProvider extends FilteredIdentifierProvider {
private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class);
@@ -71,16 +74,44 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
public static final String MD_SCHEMA = "dc";
public static final String DOI_ELEMENT = "identifier";
public static final String DOI_QUALIFIER = "uri";
-
+ // The DOI is queued for registered with the service provider
public static final Integer TO_BE_REGISTERED = 1;
+ // The DOI is queued for reservation with the service provider
public static final Integer TO_BE_RESERVED = 2;
+ // The DOI has been registered online
public static final Integer IS_REGISTERED = 3;
+ // The DOI has been reserved online
public static final Integer IS_RESERVED = 4;
+ // The DOI is reserved and requires an updated metadata record to be sent to the service provider
public static final Integer UPDATE_RESERVED = 5;
+ // The DOI is registered and requires an updated metadata record to be sent to the service provider
public static final Integer UPDATE_REGISTERED = 6;
+ // The DOI metadata record should be updated before performing online registration
public static final Integer UPDATE_BEFORE_REGISTRATION = 7;
+ // The DOI will be deleted locally and marked as deleted in the DOI service provider
public static final Integer TO_BE_DELETED = 8;
+ // The DOI has been deleted and is no longer associated with an item
public static final Integer DELETED = 9;
+ // The DOI is created in the database and is waiting for either successful filter check on item install or
+ // manual intervention by an administrator to proceed to reservation or registration
+ public static final Integer PENDING = 10;
+ // The DOI is created in the database, but no more context is known
+ public static final Integer MINTED = 11;
+
+ public static final String[] statusText = {
+ "UNKNOWN", // 0
+ "TO_BE_REGISTERED", // 1
+ "TO_BE_RESERVED", // 2
+ "IS_REGISTERED", // 3
+ "IS_RESERVED", // 4
+ "UPDATE_RESERVED", // 5
+ "UPDATE_REGISTERED", // 6
+ "UPDATE_BEFORE_REGISTRATION", // 7
+ "TO_BE_DELETED", // 8
+ "DELETED", // 9
+ "PENDING", // 10
+ "MINTED", // 11
+ };
@Autowired(required = true)
protected DOIService doiService;
@@ -89,8 +120,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Autowired(required = true)
protected ItemService itemService;
- protected Filter filterService;
-
/**
* Empty / default constructor for Spring
*/
@@ -153,16 +182,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
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
- */
- @Override
- public void setFilterService(Filter filterService) {
- this.filterService = filterService;
- }
-
/**
* This identifier provider supports identifiers of type
* {@link org.dspace.identifier.DOI}.
@@ -206,7 +225,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public String register(Context context, DSpaceObject dso)
throws IdentifierException {
- return register(context, dso, false);
+ return register(context, dso, this.filter);
}
/**
@@ -219,29 +238,29 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public void register(Context context, DSpaceObject dso, String identifier)
throws IdentifierException {
- register(context, dso, identifier, false);
+ register(context, dso, identifier, this.filter);
}
/**
* 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
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @throws IdentifierException
*/
@Override
- public String register(Context context, DSpaceObject dso, boolean skipFilter)
+ public String register(Context context, DSpaceObject dso, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
return null;
}
- String doi = mint(context, dso, skipFilter);
+ String doi = mint(context, dso, filter);
// register tries to reserve doi if it's not already.
// So we don't have to reserve it here.
- register(context, dso, doi, skipFilter);
+ register(context, dso, doi, filter);
return doi;
}
@@ -250,11 +269,11 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @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
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @throws IdentifierException
*/
@Override
- public void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
+ public void register(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
@@ -265,7 +284,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
// search DOI in our db
try {
- doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
+ doiRow = loadOrCreateDOI(context, dso, doi, filter);
} catch (SQLException ex) {
log.error("Error in databse connection: " + ex.getMessage());
throw new RuntimeException("Error in database conncetion.", ex);
@@ -277,7 +296,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
- // Check status of DOI
if (IS_REGISTERED.equals(doiRow.getStatus())) {
return;
}
@@ -290,6 +308,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
log.warn("SQLException while changing status of DOI {} to be registered.", doi);
throw new RuntimeException(sqle);
}
+
}
/**
@@ -309,7 +328,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public void reserve(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException {
- reserve(context, dso, identifier, false);
+ reserve(context, dso, identifier, this.filter);
}
/**
@@ -317,20 +336,18 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @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
+ * @param filter - Logical item filter to determine whether this identifier should be reserved
* @throws IdentifierException
* @throws IllegalArgumentException
*/
@Override
- public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
+ public void reserve(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException, IllegalArgumentException {
String doi = doiService.formatIdentifier(identifier);
DOI doiRow = null;
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, skipFilter);
+ doiRow = loadOrCreateDOI(context, dso, doi, filter);
} catch (SQLException sqle) {
throw new RuntimeException(sqle);
}
@@ -359,7 +376,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
*/
public void reserveOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
- reserveOnline(context, dso, identifier, false);
+ reserveOnline(context, dso, identifier, this.filter);
}
/**
@@ -367,16 +384,16 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
- * @param skipFilter - skip the filters for {@link checkMintable(Context, DSpaceObject)}
+ * @param filter - Logical item filter to determine whether this identifier should be reserved online
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
- public void reserveOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
+ public void reserveOnline(Context context, DSpaceObject dso, String identifier, Filter filter)
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, skipFilter);
+ DOI doiRow = loadOrCreateDOI(context, dso, doi, filter);
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to reserve a DOI that "
@@ -402,7 +419,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
public void registerOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
- registerOnline(context, dso, identifier, false);
+ registerOnline(context, dso, identifier, this.filter);
}
@@ -411,18 +428,17 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to register
- * @param skipFilter - skip filters for {@link checkMintable(Context, DSpaceObject)}
+ * @param filter - Logical item filter to determine whether this identifier should be registered online
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
- public void registerOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
+ public void registerOnline(Context context, DSpaceObject dso, String identifier, Filter filter)
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, skipFilter);
+ DOI doiRow = loadOrCreateDOI(context, dso, doi, filter);
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to register a DOI that "
@@ -435,7 +451,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
} 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, skipFilter);
+ this.reserveOnline(context, dso, identifier, filter);
connector.registerDOI(context, dso, doi);
} else {
throw die;
@@ -471,17 +487,23 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
throws IdentifierException, IllegalArgumentException, SQLException {
String doi = doiService.formatIdentifier(identifier);
-
- boolean skipFilter = false;
+ // Use the default filter unless we find the object
+ Filter updateFilter = this.filter;
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;
+ updateFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "always_true_filter", TrueFilter.class);
}
- DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
+ DOI doiRow = loadOrCreateDOI(context, dso, doi, updateFilter);
+
+ if (PENDING.equals(doiRow.getStatus()) || MINTED.equals(doiRow.getStatus())) {
+ log.info("Not updating metadata for PENDING or MINTED doi: " + doi);
+ return;
+ }
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to register a DOI that "
@@ -571,19 +593,19 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public String mint(Context context, DSpaceObject dso)
throws IdentifierException {
- return mint(context, dso, false);
+ return mint(context, dso, this.filter);
}
/**
* 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.
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @return a String containing the new identifier
* @throws IdentifierException
*/
@Override
- public String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException {
+ public String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException {
String doi = null;
try {
@@ -597,7 +619,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
}
if (null == doi) {
try {
- DOI doiRow = loadOrCreateDOI(context, dso, null, skipFilter);
+ DOI doiRow = loadOrCreateDOI(context, dso, null, filter);
doi = DOI.SCHEME + doiRow.getDoi();
} catch (SQLException e) {
@@ -895,7 +917,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
*/
protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier)
throws SQLException, DOIIdentifierException, IdentifierNotApplicableException {
- return loadOrCreateDOI(context, dso, doiIdentifier, false);
+ return loadOrCreateDOI(context, dso, doiIdentifier, this.filter);
}
/**
@@ -910,13 +932,13 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @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
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @return
* @throws SQLException
* @throws DOIIdentifierException
* @throws org.dspace.identifier.IdentifierNotApplicableException passed through.
*/
- protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter)
+ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Filter filter)
throws SQLException, DOIIdentifierException, IdentifierNotApplicableException {
DOI doi = null;
@@ -954,6 +976,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
// doi is assigned to a DSO; is it assigned to our specific dso?
// check if DOI already belongs to dso
if (dso.getID().equals(doi.getDSpaceObject().getID())) {
+ // Before we return this, check the filter
+ checkMintable(context, filter, dso);
return doi;
} else {
throw new DOIIdentifierException("Trying to create a DOI " +
@@ -963,15 +987,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
}
}
- // 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 if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not.
+ checkMintable(context, filter, dso);
// check prefix
if (!doiIdentifier.startsWith(this.getPrefix() + "/")) {
@@ -984,15 +1001,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
doi = doiService.create(context);
}
} else {
- // 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);
- }
+ // Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not.
+ checkMintable(context, filter, dso);
doi = doiService.create(context);
doiIdentifier = this.getPrefix() + "/" + this.getNamespaceSeparator() +
@@ -1002,7 +1012,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
// prepare new doiRow
doi.setDoi(doiIdentifier);
doi.setDSpaceObject(dso);
- doi.setStatus(null);
+ doi.setStatus(MINTED);
try {
doiService.update(context, doi);
} catch (SQLException e) {
@@ -1102,20 +1112,32 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
/**
* Checks to see if an item can have a DOI minted, using the configured logical filter
* @param context
+ * @param filter Logical item filter to apply
* @param dso The item to be evaluated
* @throws DOIIdentifierNotApplicableException
*/
@Override
- public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException {
+ public void checkMintable(Context context, Filter filter, DSpaceObject dso)
+ throws DOIIdentifierNotApplicableException {
+ if (filter == null) {
+ Filter trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "always_true_filter", TrueFilter.class);
+ // If a null filter was passed, and we have a good default filter to apply, apply it.
+ // Otherwise, set to TrueFilter which means "no filtering"
+ if (this.filter != null) {
+ filter = this.filter;
+ } else {
+ filter = trueFilter;
+ }
+ }
// 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")) {
+ if (contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) {
try {
- boolean result = filterService.getResult(context, (Item) dso);
+ boolean result = filter.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");
+ " was evaluated as 'false' by the item filter, not minting");
}
} catch (LogicalStatementException e) {
log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage());
@@ -1125,4 +1147,16 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
log.debug("DOI Identifier Provider: filterService is null (ie. don't prevent DOI minting)");
}
}
+
+ /**
+ * 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 {
+ checkMintable(context, this.filter, dso);
+ }
+
}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java
index e5f222ff29..c2254fa9a6 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java
@@ -12,8 +12,9 @@ import java.sql.SQLException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.logic.Filter;
+import org.dspace.content.logic.TrueFilter;
import org.dspace.core.Context;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.dspace.services.factory.DSpaceServicesFactory;
/**
* This abstract class adds extra method signatures so that implementing IdentifierProviders can
@@ -24,26 +25,28 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public abstract class FilteredIdentifierProvider extends IdentifierProvider {
- protected Filter filterService;
+ protected Filter filter = DSpaceServicesFactory.getInstance()
+ .getServiceManager().getServiceByName("always_true_filter", TrueFilter.class);
/**
- * 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
+ * Setter for spring to set the default filter from the property in configuration XML
+ * @param filter - an object implementing the org.dspace.content.logic.Filter interface
*/
- @Autowired
- public void setFilterService(Filter filterService) {
- this.filterService = filterService;
+ public void setFilter(Filter filter) {
+ if (filter != null) {
+ this.filter = filter;
+ }
}
/**
* 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
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @return identifier
* @throws IdentifierException
*/
- public abstract String register(Context context, DSpaceObject dso, boolean skipFilter)
+ public abstract String register(Context context, DSpaceObject dso, Filter filter)
throws IdentifierException;
/**
@@ -51,10 +54,10 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider {
* @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
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @throws IdentifierException
*/
- public abstract void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
+ public abstract void register(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException;
/**
@@ -62,23 +65,23 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider {
* @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
+ * @param filter - Logical item filter to determine whether this identifier should be reserved
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
- public abstract void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
+ public abstract void reserve(Context context, DSpaceObject dso, String identifier, Filter filter)
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.
+ * @param filter - Logical item filter to determine whether this identifier should be registered
* @return a String containing the new identifier
* @throws IdentifierException
*/
- public abstract String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException;
+ public abstract String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException;
/**
* Check configured item filters to see if this identifier is allowed to be minted
@@ -88,5 +91,13 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider {
*/
public abstract void checkMintable(Context context, DSpaceObject dso) throws IdentifierException;
+ /**
+ * Check configured item filters to see if this identifier is allowed to be minted
+ * @param context - DSpace context
+ * @param filter - Logical item filter
+ * @param dso - DSpaceObject to be inspected
+ * @throws IdentifierException
+ */
+ public abstract void checkMintable(Context context, Filter filter, DSpaceObject dso) throws IdentifierException;
}
diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java
index d0b6e4417e..b98aea24fa 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java
@@ -10,6 +10,7 @@ package org.dspace.identifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
@@ -17,6 +18,7 @@ import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory;
+import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.dspace.handle.service.HandleService;
import org.dspace.identifier.service.IdentifierService;
@@ -44,7 +46,6 @@ public class IdentifierServiceImpl implements IdentifierService {
protected HandleService handleService;
protected IdentifierServiceImpl() {
-
}
@Autowired(required = true)
@@ -98,7 +99,7 @@ public class IdentifierServiceImpl implements IdentifierService {
@Override
public void register(Context context, DSpaceObject dso)
- throws AuthorizeException, SQLException, IdentifierException {
+ throws AuthorizeException, SQLException, IdentifierException {
//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) {
@@ -112,11 +113,99 @@ public class IdentifierServiceImpl implements IdentifierService {
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
}
+ @Override
+ public void register(Context context, DSpaceObject dso, Class extends Identifier> type, Filter filter)
+ throws AuthorizeException, SQLException, IdentifierException {
+ boolean registered = false;
+ // Iterate all services and register identifiers as appropriate
+ for (IdentifierProvider service : providers) {
+ if (service.supports(type)) {
+ try {
+ if (service instanceof FilteredIdentifierProvider) {
+ FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service;
+ filteredService.register(context, dso, filter);
+ } else {
+ service.register(context, dso);
+ }
+ registered = true;
+ } catch (IdentifierNotApplicableException e) {
+ log.warn("Identifier not registered (inapplicable): " + e.getMessage());
+ }
+ }
+ }
+ if (!registered) {
+ throw new IdentifierException("Cannot register identifier: Didn't "
+ + "find a provider that supports this identifier.");
+ }
+ // Update our item / collection / community
+ contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
+ }
+
+ @Override
+ public void register(Context context, DSpaceObject dso, Class extends Identifier> type)
+ throws AuthorizeException, SQLException, IdentifierException {
+ boolean registered = false;
+ // Iterate all services and register identifiers as appropriate
+ for (IdentifierProvider service : providers) {
+ if (service.supports(type)) {
+ try {
+ service.register(context, dso);
+ registered = true;
+ } catch (IdentifierNotApplicableException e) {
+ log.warn("Identifier not registered (inapplicable): " + e.getMessage());
+ }
+ }
+ }
+ if (!registered) {
+ throw new IdentifierException("Cannot register identifier: Didn't "
+ + "find a provider that supports this identifier.");
+ }
+ // Update our item / collection / community
+ contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
+ }
+
+ @Override
+ public void register(Context context, DSpaceObject dso, Map, Filter> typeFilters)
+ throws AuthorizeException, SQLException, IdentifierException {
+ // Iterate all services and register identifiers as appropriate
+ for (IdentifierProvider service : providers) {
+ try {
+ // If the service supports filtering, look through the map and the first supported class
+ // we find, set the filter and break. If no filter was seen for this type, just let the provider
+ // use its own implementation.
+ if (service instanceof FilteredIdentifierProvider) {
+ FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service;
+ Filter filter = null;
+ for (Class extends Identifier> type : typeFilters.keySet()) {
+ if (filteredService.supports(type)) {
+ filter = typeFilters.get(type);
+ break;
+ }
+ }
+ if (filter != null) {
+ // Pass the found filter to the provider
+ filteredService.register(context, dso, filter);
+ } else {
+ // Let the provider use the default filter / behaviour
+ filteredService.register(context, dso);
+ }
+ } else {
+ 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);
+ }
+
+
+
@Override
public void register(Context context, DSpaceObject object, String identifier)
throws AuthorizeException, SQLException, IdentifierException {
- //We need to commit our context because one of the providers might require the handle created above
- // Next resolve all other services
+ // Iterate all services and register identifiers as appropriate
boolean registered = false;
for (IdentifierProvider service : providers) {
if (service.supports(identifier)) {
@@ -132,7 +221,7 @@ public class IdentifierServiceImpl implements IdentifierService {
throw new IdentifierException("Cannot register identifier: Didn't "
+ "find a provider that supports this identifier.");
}
- //Update our item / collection / community
+ // pdate our item / collection / community
contentServiceFactory.getDSpaceObjectService(object).update(context, object);
}
diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java
index a864b4be4b..4ca186eaab 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java
@@ -18,6 +18,7 @@ import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
+import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
@@ -49,7 +50,12 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
protected VersionHistoryService versionHistoryService;
@Override
- public String mint(Context context, DSpaceObject dso)
+ public String mint(Context context, DSpaceObject dso) throws IdentifierException {
+ return mint(context, dso, this.filter);
+ }
+
+ @Override
+ public String mint(Context context, DSpaceObject dso, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
throw new IdentifierException("Currently only Items are supported for DOIs.");
@@ -60,7 +66,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
try {
history = versionHistoryService.findByItem(context, item);
} catch (SQLException ex) {
- throw new RuntimeException("A problem occured while accessing the database.", ex);
+ throw new RuntimeException("A problem occurred while accessing the database.", ex);
}
String doi = null;
@@ -70,7 +76,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
return doi;
}
} catch (SQLException ex) {
- log.error("Error while attemping to retrieve information about a DOI for "
+ log.error("Error while attempting 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 "
@@ -79,6 +85,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
+ " with ID " + dso.getID() + ".", ex);
}
+ // Make a call to the filter here to throw an exception instead of carrying on with removal + creation
+ checkMintable(context, filter, dso);
+
// check whether we have a DOI in the metadata and if we have to remove it
String metadataDOI = getDOIOutOfObject(dso);
if (metadataDOI != null) {
@@ -111,7 +120,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
// ensure DOI exists in our database as well and return.
// this also checks that the doi is not assigned to another dso already.
try {
- loadOrCreateDOI(context, dso, versionedDOI);
+ loadOrCreateDOI(context, dso, versionedDOI, filter);
} catch (SQLException ex) {
log.error(
"A problem with the database connection occurd while processing DOI " + versionedDOI + ".", ex);
@@ -125,9 +134,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
if (history != null) {
// versioning is currently supported for items only
// if we have a history, we have a item
- doi = makeIdentifierBasedOnHistory(context, dso, history);
+ doi = makeIdentifierBasedOnHistory(context, dso, history, filter);
} else {
- doi = loadOrCreateDOI(context, dso, null).getDoi();
+ doi = loadOrCreateDOI(context, dso, null, filter).getDoi();
}
} catch (SQLException ex) {
log.error("SQLException while creating a new DOI: ", ex);
@@ -136,11 +145,31 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
log.error("AuthorizationException while creating a new DOI: ", ex);
throw new IdentifierException(ex);
}
+ return doi.startsWith(DOI.SCHEME) ? doi : DOI.SCHEME + doi;
+ }
+
+ @Override
+ public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException {
+ register(context, dso, identifier, this.filter);
+ }
+
+ @Override
+ public String register(Context context, DSpaceObject dso, Filter filter)
+ throws IdentifierException {
+ if (!(dso instanceof Item)) {
+ // DOIs are currently assigned only to Items
+ return null;
+ }
+
+ String doi = mint(context, dso, filter);
+
+ register(context, dso, doi, filter);
+
return doi;
}
@Override
- public void register(Context context, DSpaceObject dso, String identifier)
+ public void register(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
throw new IdentifierException("Currently only Items are supported for DOIs.");
@@ -148,7 +177,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
Item item = (Item) dso;
if (StringUtils.isEmpty(identifier)) {
- identifier = mint(context, dso);
+ identifier = mint(context, dso, filter);
}
String doiIdentifier = doiService.formatIdentifier(identifier);
@@ -156,10 +185,10 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
// search DOI in our db
try {
- doi = loadOrCreateDOI(context, dso, doiIdentifier);
+ doi = loadOrCreateDOI(context, dso, doiIdentifier, filter);
} catch (SQLException ex) {
- log.error("Error in databse connection: " + ex.getMessage(), ex);
- throw new RuntimeException("Error in database conncetion.", ex);
+ log.error("Error in database connection: " + ex.getMessage(), ex);
+ throw new RuntimeException("Error in database connection.", ex);
}
if (DELETED.equals(doi.getStatus()) ||
@@ -220,8 +249,14 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
return doiPostfix;
}
- // Should never return null!
protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history)
+ throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException {
+ return makeIdentifierBasedOnHistory(context, dso, history, this.filter);
+ }
+
+ // Should never return null!
+ protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history,
+ Filter filter)
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.
@@ -244,6 +279,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
}
if (previousVersionDOI == null) {
+ // Before continuing with any new DOI creation, apply the filter
+ checkMintable(context, filter, dso);
+
// We need to generate a new DOI.
DOI doi = doiService.create(context);
@@ -269,7 +307,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
String.valueOf(versionHistoryService.getVersion(context, history, item).getVersionNumber()));
}
- loadOrCreateDOI(context, dso, identifier);
+ loadOrCreateDOI(context, dso, identifier, filter);
return identifier;
}
diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java
index 7705fd2b57..0fac326ca1 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java
@@ -306,6 +306,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
public DSpaceObject resolve(Context context, String identifier, String... attributes) {
// We can do nothing with this, return null
try {
+ identifier = handleService.parseHandle(identifier);
return handleService.resolveToObject(context, identifier);
} catch (IllegalStateException | SQLException e) {
log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier),
@@ -426,6 +427,19 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
}
}
+ DSpaceObject itemWithCanonicalHandle = handleService.resolveToObject(context, canonical);
+ if (itemWithCanonicalHandle != null) {
+ if (itemWithCanonicalHandle.getID() != previous.getItem().getID()) {
+ log.warn("The previous version's item (" + previous.getItem().getID() +
+ ") does not match with the item containing handle " + canonical +
+ " (" + itemWithCanonicalHandle.getID() + ")");
+ }
+ // Move the original handle from whatever item it's on to the newest version
+ handleService.modifyHandleDSpaceObject(context, canonical, dso);
+ } else {
+ handleService.createHandle(context, dso, canonical);
+ }
+
// add a new Identifier for this item: 12345/100.x
String idNew = canonical + DOT + version.getVersionNumber();
//Make sure we don't have an old handle hanging around (if our previous version was deleted in the workspace)
diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java
index 654d275d87..33ef058e16 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java
@@ -7,22 +7,32 @@
*/
package org.dspace.identifier.doi;
+import java.sql.SQLException;
+
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.logic.Filter;
+import org.dspace.content.logic.FilterUtils;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.event.Consumer;
import org.dspace.event.Event;
+import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
-import org.dspace.identifier.IdentifierNotFoundException;
+import org.dspace.identifier.IdentifierNotApplicableException;
+import org.dspace.identifier.factory.IdentifierServiceFactory;
+import org.dspace.identifier.service.DOIService;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
import org.dspace.workflow.factory.WorkflowServiceFactory;
/**
* @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de)
+ * @author Kim Shepherd
*/
public class DOIConsumer implements Consumer {
/**
@@ -30,12 +40,15 @@ public class DOIConsumer implements Consumer {
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DOIConsumer.class);
+ ConfigurationService configurationService;
+
@Override
public void initialize() throws Exception {
// nothing to do
// we can ask spring to give as a properly setuped instance of
// DOIIdentifierProvider. Doing so we don't have to configure it and
// can load it in consume method as this is not very expensive.
+ configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
}
@@ -62,36 +75,72 @@ public class DOIConsumer implements Consumer {
return;
}
Item item = (Item) dso;
-
- if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null
- || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) {
- // ignore workflow and workspace items, DOI will be minted when item is installed
- return;
+ DOIIdentifierProvider provider = new DSpace().getSingletonService(DOIIdentifierProvider.class);
+ boolean inProgress = (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item)
+ != null || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null);
+ boolean identifiersInSubmission = configurationService.getBooleanProperty("identifiers.submission.register",
+ false);
+ DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService();
+ Filter workspaceFilter = null;
+ if (identifiersInSubmission) {
+ workspaceFilter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.workspace");
}
- DOIIdentifierProvider provider = new DSpace().getSingletonService(
- DOIIdentifierProvider.class);
-
- String doi = null;
+ if (inProgress && !identifiersInSubmission) {
+ // ignore workflow and workspace items, DOI will be minted and updated when item is installed
+ // UNLESS special pending filter is set
+ return;
+ }
+ DOI doi = null;
try {
- doi = provider.lookup(ctx, dso);
- } catch (IdentifierNotFoundException ex) {
+ doi = doiService.findDOIByDSpaceObject(ctx, dso);
+ } catch (SQLException ex) {
// nothing to do here, next if clause will stop us from processing
// items without dois.
}
if (doi == null) {
- log.debug("DOIConsumer cannot handles items without DOIs, skipping: "
- + event.toString());
- return;
- }
- try {
- provider.updateMetadata(ctx, dso, doi);
- } catch (IllegalArgumentException ex) {
- // should not happen, as we got the DOI from the DOIProvider
- log.warn("DOIConsumer caught an IdentifierException.", ex);
- } catch (IdentifierException ex) {
- log.warn("DOIConsumer cannot update metadata for Item with ID "
- + item.getID() + " and DOI " + doi + ".", ex);
+ // No DOI. The only time something should be minted is if we have enabled submission reg'n and
+ // it passes the workspace filter. We also need to update status to PENDING straight after.
+ if (inProgress) {
+ provider.mint(ctx, dso, workspaceFilter);
+ DOI newDoi = doiService.findDOIByDSpaceObject(ctx, dso);
+ if (newDoi != null) {
+ newDoi.setStatus(DOIIdentifierProvider.PENDING);
+ doiService.update(ctx, newDoi);
+ }
+ } else {
+ log.debug("DOIConsumer cannot handles items without DOIs, skipping: " + event.toString());
+ }
+ } else {
+ // If in progress, we can also switch PENDING and MINTED status depending on the latest filter
+ // evaluation
+ if (inProgress) {
+ try {
+ // Check the filter
+ provider.checkMintable(ctx, workspaceFilter, dso);
+ // If we made it here, the existing doi should be back to PENDING
+ if (DOIIdentifierProvider.MINTED.equals(doi.getStatus())) {
+ doi.setStatus(DOIIdentifierProvider.PENDING);
+ }
+ } catch (IdentifierNotApplicableException e) {
+ // Set status to MINTED if configured to downgrade existing DOIs
+ if (configurationService
+ .getBooleanProperty("identifiers.submission.strip_pending_during_submission", true)) {
+ doi.setStatus(DOIIdentifierProvider.MINTED);
+ }
+ }
+ doiService.update(ctx, doi);
+ } else {
+ try {
+ provider.updateMetadata(ctx, dso, doi.getDoi());
+ } catch (IllegalArgumentException ex) {
+ // should not happen, as we got the DOI from the DOIProvider
+ log.warn("DOIConsumer caught an IdentifierException.", ex);
+ } catch (IdentifierException ex) {
+ log.warn("DOIConsumer cannot update metadata for Item with ID "
+ + item.getID() + " and DOI " + doi + ".", ex);
+ }
+ }
}
}
diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java
index e0e0da9440..088e2b1cbc 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java
@@ -30,6 +30,9 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory;
+import org.dspace.content.logic.Filter;
+import org.dspace.content.logic.FilterUtils;
+import org.dspace.content.logic.TrueFilter;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -61,7 +64,8 @@ public class DOIOrganiser {
protected ItemService itemService;
protected DOIService doiService;
protected ConfigurationService configurationService;
- protected boolean skipFilter;
+ // This filter will override the default provider filter / behaviour
+ protected Filter filter;
/**
* Constructor to be called within the main() method
@@ -76,7 +80,8 @@ public class DOIOrganiser {
this.itemService = ContentServiceFactory.getInstance().getItemService();
this.doiService = IdentifierServiceFactory.getInstance().getDOIService();
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
- this.skipFilter = false;
+ this.filter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "always_true_filter", TrueFilter.class);
}
/**
@@ -121,12 +126,13 @@ public class DOIOrganiser {
"Perform online metadata update for all identifiers queued for metadata update.");
options.addOption("d", "delete-all", false,
"Perform online deletion for all identifiers queued for deletion.");
-
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 filterDoi = Option.builder().optionalArg(true).longOpt("filter").hasArg().argName("filterName")
+ .desc("Use the specified filter name instead of the provider's filter. Defaults to a special " +
+ "'always true' filter to force operations").build();
+ options.addOption(filterDoi);
Option registerDoi = Option.builder()
.longOpt("register-doi")
@@ -203,10 +209,12 @@ 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;
+ // Do we get a filter?
+ if (line.hasOption("filter")) {
+ String filter = line.getOptionValue("filter");
+ if (null != filter) {
+ organiser.filter = FilterUtils.getFilterFromConfiguration(filter);
+ }
}
if (line.hasOption('s')) {
@@ -394,19 +402,18 @@ public class DOIOrganiser {
/**
* Register DOI with the provider
* @param doiRow - doi to register
- * @param skipFilter - whether filters should be skipped before registration
+ * @param filter - logical item filter to override
* @throws SQLException
* @throws DOIIdentifierException
*/
- public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIdentifierException {
+ public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifierException {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
}
try {
- provider.registerOnline(context, dso,
- DOI.SCHEME + doiRow.getDoi());
+ provider.registerOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter);
if (!quiet) {
System.out.println("This identifier: "
@@ -466,29 +473,23 @@ public class DOIOrganiser {
}
/**
- * Register DOI with the provider, always applying (ie. never skipping) any configured filters
+ * Register DOI with the provider
* @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);
+ register(doiRow, this.filter);
}
/**
- * Reserve DOI with the provider, always applying (ie. never skipping) any configured filters
+ * Reserve DOI with the provider,
* @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(doiRow, this.filter);
}
/**
@@ -497,14 +498,14 @@ public class DOIOrganiser {
* @throws SQLException
* @throws DOIIdentifierException
*/
- public void reserve(DOI doiRow, boolean skipFilter) {
+ public void reserve(DOI doiRow, Filter filter) {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only.");
}
try {
- provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), skipFilter);
+ provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter);
if (!quiet) {
System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved.");
@@ -699,7 +700,7 @@ public class DOIOrganiser {
//Check if this Item has an Identifier, mint one if it doesn't
if (null == doiRow) {
- doi = provider.mint(context, dso, this.skipFilter);
+ doi = provider.mint(context, dso, this.filter);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
return doiRow;
@@ -723,7 +724,7 @@ public class DOIOrganiser {
doiRow = doiService.findDOIByDSpaceObject(context, dso);
if (null == doiRow) {
- doi = provider.mint(context, dso, this.skipFilter);
+ doi = provider.mint(context, dso, this.filter);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
}
diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java
index 64eee1dfcf..23005b6575 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java
@@ -9,9 +9,11 @@ package org.dspace.identifier.service;
import java.sql.SQLException;
import java.util.List;
+import java.util.Map;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
+import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.dspace.identifier.Identifier;
import org.dspace.identifier.IdentifierException;
@@ -103,6 +105,52 @@ public interface IdentifierService {
*/
void register(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException;
+ /**
+ *
+ * Register identifiers for a DSO, with a map of logical filters for each Identifier class to apply
+ * at the time of local registration.
+ *
+ * @param context The relevant DSpace Context.
+ * @param dso DSpace object to be registered
+ * @param typeFilters If a service supports a given Identifier implementation, apply the associated filter
+ * @throws AuthorizeException if authorization error
+ * @throws SQLException if database error
+ * @throws IdentifierException if identifier error
+ */
+ void register(Context context, DSpaceObject dso, Map, Filter> typeFilters)
+ throws AuthorizeException, SQLException, IdentifierException;
+
+ /**
+ *
+ * Register identifier(s) for the given DSO just with providers that support that Identifier class, and
+ * apply the given filter if that provider extends FilteredIdentifierProvider
+ *
+ * @param context The relevant DSpace Context.
+ * @param dso DSpace object to be registered
+ * @param type Type of identifier to register
+ * @param filter If a service supports a given Identifier implementation, apply this specific filter
+ * @throws AuthorizeException if authorization error
+ * @throws SQLException if database error
+ * @throws IdentifierException if identifier error
+ */
+ void register(Context context, DSpaceObject dso, Class extends Identifier> type, Filter filter)
+ throws AuthorizeException, SQLException, IdentifierException;
+
+ /**
+ *
+ * Register identifier(s) for the given DSO just with providers that support that Identifier class, and
+ * apply the given filter if that provider extends FilteredIdentifierProvider
+ *
+ * @param context The relevant DSpace Context.
+ * @param dso DSpace object to be registered
+ * @param type Type of identifier to register
+ * @throws AuthorizeException if authorization error
+ * @throws SQLException if database error
+ * @throws IdentifierException if identifier error
+ */
+ void register(Context context, DSpaceObject dso, Class extends Identifier> type)
+ throws AuthorizeException, SQLException, IdentifierException;
+
/**
* Used to Register a specific Identifier (for example a Handle, hdl:1234.5/6).
* The provider is responsible for detecting and processing the appropriate
diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java
index 04a08a7781..7c6336ed3c 100644
--- a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java
@@ -23,8 +23,7 @@ import org.dspace.iiif.util.IIIFSharedUtils;
/**
- * Queries the configured IIIF server for image dimensions. Used for
- * formats that cannot be easily read using ImageIO (jpeg 2000).
+ * Queries the configured IIIF image server via the Image API.
*
* @author Michael Spalti mspalti@willamette.edu
*/
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java
index 445dfedebd..0014088c86 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java
@@ -105,10 +105,10 @@ public class BibtexImportMetadataSourceServiceImpl extends AbstractPlainMetadata
/**
- * Retrieve the MetadataFieldMapping containing the mapping between RecordType
+ * Set the MetadataFieldMapping containing the mapping between RecordType
* (in this case PlainMetadataSourceDto.class) and Metadata
*
- * @return The configured MetadataFieldMapping
+ * @param metadataFieldMap The configured MetadataFieldMapping
*/
@Override
@SuppressWarnings("unchecked")
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java
new file mode 100644
index 0000000000..f8540307b9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java
@@ -0,0 +1,38 @@
+/**
+ * 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.importer.external.datacite;
+
+import java.util.Map;
+import javax.annotation.Resource;
+
+import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping;
+
+/**
+ * An implementation of {@link AbstractMetadataFieldMapping}
+ * Responsible for defining the mapping of the datacite metadatum fields on the DSpace metadatum fields
+ *
+ * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
+ * @author Florian Gantner (florian.gantner@uni-bamberg.de)
+ */
+public class DataCiteFieldMapping extends AbstractMetadataFieldMapping {
+
+ /**
+ * Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
+ * only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
+ * what metadatafield is generated.
+ *
+ * @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to
+ * the item.
+ */
+ @Override
+ @Resource(name = "dataciteMetadataFieldMap")
+ public void setMetadataFieldMap(Map metadataFieldMap) {
+ super.setMetadataFieldMap(metadataFieldMap);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java
new file mode 100644
index 0000000000..a11f2bc247
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java
@@ -0,0 +1,168 @@
+/**
+ * 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.importer.external.datacite;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.el.MethodNotFoundException;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.content.Item;
+import org.dspace.importer.external.datamodel.ImportRecord;
+import org.dspace.importer.external.datamodel.Query;
+import org.dspace.importer.external.exception.MetadataSourceException;
+import org.dspace.importer.external.liveimportclient.service.LiveImportClient;
+import org.dspace.importer.external.service.AbstractImportMetadataSourceService;
+import org.dspace.importer.external.service.DoiCheck;
+import org.dspace.importer.external.service.components.QuerySource;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implements a data source for querying Datacite
+ * Mainly copied from CrossRefImportMetadataSourceServiceImpl.
+ *
+ * optional Affiliation informations are not part of the API request.
+ * https://support.datacite.org/docs/can-i-see-more-detailed-affiliation-information-in-the-rest-api
+ *
+ * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
+ * @author Florian Gantner (florian.gantner@uni-bamberg.de)
+ *
+ */
+public class DataCiteImportMetadataSourceServiceImpl
+ extends AbstractImportMetadataSourceService implements QuerySource {
+ private final static Logger log = LogManager.getLogger();
+
+ @Autowired
+ private LiveImportClient liveImportClient;
+
+ @Autowired
+ private ConfigurationService configurationService;
+
+ @Override
+ public String getImportSource() {
+ return "datacite";
+ }
+
+ @Override
+ public void init() throws Exception {
+ }
+
+ @Override
+ public ImportRecord getRecord(String recordId) throws MetadataSourceException {
+ Collection records = getRecords(recordId, 0, 1);
+ if (records.size() == 0) {
+ return null;
+ }
+ return records.stream().findFirst().get();
+ }
+
+ @Override
+ public int getRecordsCount(String query) throws MetadataSourceException {
+ Collection records = getRecords(query, 0, -1);
+ return records == null ? 0 : records.size();
+ }
+
+ @Override
+ public int getRecordsCount(Query query) throws MetadataSourceException {
+ String id = getID(query.toString());
+ return getRecordsCount(StringUtils.isBlank(id) ? query.toString() : id);
+ }
+
+
+ @Override
+ public Collection getRecords(String query, int start, int count) throws MetadataSourceException {
+ List records = new ArrayList<>();
+ String id = getID(query);
+ Map> params = new HashMap<>();
+ Map uriParameters = new HashMap<>();
+ params.put("uriParameters", uriParameters);
+ if (StringUtils.isBlank(id)) {
+ id = query;
+ }
+ uriParameters.put("query", id);
+ int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000);
+ String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/");
+ String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params);
+ JsonNode jsonNode = convertStringJsonToJsonNode(responseString);
+ if (jsonNode == null) {
+ log.warn("DataCite returned invalid JSON");
+ return records;
+ }
+ JsonNode dataNode = jsonNode.at("/data");
+ if (dataNode.isArray()) {
+ Iterator iterator = dataNode.iterator();
+ while (iterator.hasNext()) {
+ JsonNode singleDoiNode = iterator.next();
+ String json = singleDoiNode.at("/attributes").toString();
+ records.add(transformSourceRecords(json));
+ }
+ } else {
+ String json = dataNode.at("/attributes").toString();
+ records.add(transformSourceRecords(json));
+ }
+
+ return records;
+ }
+
+ private JsonNode convertStringJsonToJsonNode(String json) {
+ try {
+ return new ObjectMapper().readTree(json);
+ } catch (JsonProcessingException e) {
+ log.error("Unable to process json response.", e);
+ }
+ return null;
+ }
+
+ @Override
+ public Collection getRecords(Query query) throws MetadataSourceException {
+ String id = getID(query.toString());
+ return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1);
+ }
+
+ @Override
+ public ImportRecord getRecord(Query query) throws MetadataSourceException {
+ String id = getID(query.toString());
+ return getRecord(StringUtils.isBlank(id) ? query.toString() : id);
+ }
+
+ @Override
+ public Collection findMatchingRecords(Query query) throws MetadataSourceException {
+ String id = getID(query.toString());
+ return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1);
+ }
+
+
+ @Override
+ public Collection findMatchingRecords(Item item) throws MetadataSourceException {
+ throw new MethodNotFoundException("This method is not implemented for DataCite");
+ }
+
+ public String getID(String query) {
+ if (DoiCheck.isDoi(query)) {
+ return query;
+ }
+ // Workaround for encoded slashes.
+ if (query.contains("%252F")) {
+ query = query.replace("%252F", "/");
+ }
+ if (DoiCheck.isDoi(query)) {
+ return query;
+ }
+ return StringUtils.EMPTY;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java
index 829b5ed2de..a1132cda9c 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java
@@ -21,7 +21,7 @@ public interface LiveImportClient {
*
* @param timeout The connect timeout in milliseconds
* @param URL URL
- * @param requestParams This map contains the parameters to be included in the request.
+ * @param params This map contains the parameters to be included in the request.
* Each parameter will be added to the url?(key=value)
* @return The response in String type converted from InputStream
*/
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java
index 81a6631127..1a8a7a7861 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java
@@ -60,7 +60,8 @@ public class LiveImportClientImpl implements LiveImportClient {
requestConfigBuilder.setConnectionRequestTimeout(timeout);
RequestConfig defaultRequestConfig = requestConfigBuilder.build();
- method = new HttpGet(buildUrl(URL, params.get(URI_PARAMETERS)));
+ String uri = buildUrl(URL, params.get(URI_PARAMETERS));
+ method = new HttpGet(uri);
method.setConfig(defaultRequestConfig);
Map headerParams = params.get(HEADER_PARAMETERS);
@@ -71,7 +72,9 @@ public class LiveImportClientImpl implements LiveImportClient {
}
configureProxy(method, defaultRequestConfig);
-
+ if (log.isDebugEnabled()) {
+ log.debug("Performing GET request to \"" + uri + "\"...");
+ }
HttpResponse httpResponse = httpClient.execute(method);
if (isNotSuccessfull(httpResponse)) {
throw new RuntimeException("The request failed with: " + getStatusCode(httpResponse) + " code, reason= "
@@ -98,7 +101,8 @@ public class LiveImportClientImpl implements LiveImportClient {
Builder requestConfigBuilder = RequestConfig.custom();
RequestConfig defaultRequestConfig = requestConfigBuilder.build();
- method = new HttpPost(buildUrl(URL, params.get(URI_PARAMETERS)));
+ String uri = buildUrl(URL, params.get(URI_PARAMETERS));
+ method = new HttpPost(uri);
method.setConfig(defaultRequestConfig);
if (StringUtils.isNotBlank(entry)) {
method.setEntity(new StringEntity(entry));
@@ -106,7 +110,9 @@ public class LiveImportClientImpl implements LiveImportClient {
setHeaderParams(method, params);
configureProxy(method, defaultRequestConfig);
-
+ if (log.isDebugEnabled()) {
+ log.debug("Performing POST request to \"" + uri + "\"..." );
+ }
HttpResponse httpResponse = httpClient.execute(method);
if (isNotSuccessfull(httpResponse)) {
throw new RuntimeException();
@@ -185,4 +191,4 @@ public class LiveImportClientImpl implements LiveImportClient {
this.httpClient = httpClient;
}
-}
\ No newline at end of file
+}
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java
index 00b414c485..e32f45a4d5 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java
@@ -156,7 +156,7 @@ public class EpoIdMetadataContributor implements MetadataContributor {
* Depending on the retrieved node (using the query), different types of values will be added to the MetadatumDTO
* list
*
- * @param t A class to retrieve metadata from.
+ * @param element A class to retrieve metadata from.
* @return a collection of import records. Only the identifier of the found records may be put in the record.
*/
@Override
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java
index f739980220..590fc63283 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java
@@ -118,7 +118,7 @@ public class SimpleJsonPathMetadataContributor implements MetadataContributor implements MetadataContributor
int j = 0;
// Use the first dcDate that has been formatted (Config should go from most specific to most lenient)
- while (j < dateFormatsToAttempt.size() && dcDate == null) {
+ while (j < dateFormatsToAttempt.size()) {
String dateFormat = dateFormatsToAttempt.get(j);
try {
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
Date date = formatter.parse(dateString);
dcDate = new DCDate(date);
+ values.add(metadataFieldMapping.toDCValue(field, formatter.format(date)));
+ break;
} catch (ParseException e) {
// Multiple dateformats can be configured, we don't want to print the entire stacktrace every
// time one of those formats fails.
@@ -136,9 +138,7 @@ public class PubmedDateMetadatumContributor implements MetadataContributor
}
j++;
}
- if (dcDate != null) {
- values.add(metadataFieldMapping.toDCValue(field, dcDate.toString()));
- } else {
+ if (dcDate == null) {
log.info(
"Failed parsing " + dateString + ", check " +
"the configured dataformats in config/spring/api/pubmed-integration.xml");
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java
index b30ea22ca4..933d6b1446 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java
@@ -292,7 +292,14 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat
int countAttempt = 0;
while (StringUtils.isBlank(response) && countAttempt <= attempt) {
countAttempt++;
+
+ long time = System.currentTimeMillis() - lastRequest;
+ if ((time) < interRequestTime) {
+ Thread.sleep(interRequestTime - time);
+ }
+
response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params);
+ lastRequest = System.currentTimeMillis();
}
if (StringUtils.isBlank(response)) {
@@ -316,7 +323,13 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat
countAttempt = 0;
while (StringUtils.isBlank(response2) && countAttempt <= attempt) {
countAttempt++;
+ long time = System.currentTimeMillis() - lastRequest;
+ if ((time) < interRequestTime) {
+ Thread.sleep(interRequestTime - time);
+ }
response2 = liveImportClient.executeHttpGetRequest(1000, uriBuilder2.toString(), params2);
+
+ lastRequest = System.currentTimeMillis();
}
if (StringUtils.isBlank(response2)) {
@@ -418,7 +431,13 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat
int countAttempt = 0;
while (StringUtils.isBlank(response) && countAttempt <= attempt) {
countAttempt++;
+ long time = System.currentTimeMillis() - lastRequest;
+ if ((time) < interRequestTime) {
+ Thread.sleep(interRequestTime - time);
+ }
+
response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params);
+ lastRequest = System.currentTimeMillis();
}
if (StringUtils.isBlank(response)) {
@@ -441,7 +460,12 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat
countAttempt = 0;
while (StringUtils.isBlank(response2) && countAttempt <= attempt) {
countAttempt++;
+ long time = System.currentTimeMillis() - lastRequest;
+ if ((time) < interRequestTime) {
+ Thread.sleep(interRequestTime - time);
+ }
response2 = liveImportClient.executeHttpGetRequest(1000, uriBuilder2.toString(), params2);
+ lastRequest = System.currentTimeMillis();
}
if (StringUtils.isBlank(response2)) {
@@ -501,4 +525,4 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat
this.urlSearch = urlSearch;
}
-}
\ No newline at end of file
+}
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java
index 2574e187df..1f460c19e6 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java
@@ -126,10 +126,10 @@ public class RisImportMetadataSourceServiceImpl extends AbstractPlainMetadataSou
}
/**
- * Retrieve the MetadataFieldMapping containing the mapping between RecordType
+ * Set the MetadataFieldMapping containing the mapping between RecordType
* (in this case PlainMetadataSourceDto.class) and Metadata
*
- * @return The configured MetadataFieldMapping
+ * @param metadataFieldMap The configured MetadataFieldMapping
*/
@Override
@SuppressWarnings("unchecked")
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java
index 019cf33177..5d83b9a7cc 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java
@@ -42,7 +42,7 @@ public abstract class AbstractPlainMetadataSource
/**
* Set the file extensions supported by this metadata service
*
- * @param supportedExtensionsthe file extensions (xml,txt,...) supported by this service
+ * @param supportedExtensions the file extensions (xml,txt,...) supported by this service
*/
public void setSupportedExtensions(List supportedExtensions) {
this.supportedExtensions = supportedExtensions;
@@ -57,7 +57,7 @@ public abstract class AbstractPlainMetadataSource
* Return a list of ImportRecord constructed from input file. This list is based on
* the results retrieved from the file (InputStream) parsed through abstract method readData
*
- * @param InputStream The inputStream of the file
+ * @param is The inputStream of the file
* @return A list of {@link ImportRecord}
* @throws FileSourceException if, for any reason, the file is not parsable
*/
@@ -76,7 +76,7 @@ public abstract class AbstractPlainMetadataSource
* the result retrieved from the file (InputStream) parsed through abstract method
* "readData" implementation
*
- * @param InputStream The inputStream of the file
+ * @param is The inputStream of the file
* @return An {@link ImportRecord} matching the file content
* @throws FileSourceException if, for any reason, the file is not parsable
* @throws FileMultipleOccurencesException if the file contains more than one entry
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java
index 38632a1a2b..29801433e3 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractRemoteMetadataSource.java
@@ -183,6 +183,7 @@ public abstract class AbstractRemoteMetadataSource {
log.warn("Error in trying operation " + operationId + " " + retry + " " + warning + ", retrying !", e);
} finally {
+ this.lastRequest = System.currentTimeMillis();
lock.unlock();
}
@@ -262,5 +263,7 @@ public abstract class AbstractRemoteMetadataSource {
*/
public abstract void init() throws Exception;
-
+ public void setInterRequestTime(final long interRequestTime) {
+ this.interRequestTime = interRequestTime;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java
index 5bef0984df..13c81d1516 100644
--- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java
+++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java
@@ -30,7 +30,7 @@ public interface FileSource extends MetadataSource {
/**
* Return a list of ImportRecord constructed from input file.
*
- * @param InputStream The inputStream of the file
+ * @param inputStream The inputStream of the file
* @return A list of {@link ImportRecord}
* @throws FileSourceException if, for any reason, the file is not parsable
*/
@@ -40,7 +40,7 @@ public interface FileSource extends MetadataSource {
/**
* Return an ImportRecord constructed from input file.
*
- * @param InputStream The inputStream of the file
+ * @param inputStream The inputStream of the file
* @return An {@link ImportRecord} matching the file content
* @throws FileSourceException if, for any reason, the file is not parsable
* @throws FileMultipleOccurencesException if the file contains more than one entry
diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java
index 619227432d..cdecadba52 100644
--- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java
@@ -106,7 +106,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService,
for (String license : licenses) {
- String licenseUri = ccLicenseUrl + "/license/" + license;
+ String licenseUri = ccLicenseUrl + "/license/" + license + "?locale=" + language;
HttpGet licenseHttpGet = new HttpGet(licenseUri);
try (CloseableHttpResponse response = client.execute(licenseHttpGet)) {
CCLicense ccLicense = retrieveLicenseObject(license, response);
diff --git a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java
index 96f110c101..c9c8127d18 100644
--- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java
@@ -430,9 +430,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
}
- private void addLicenseField(Context context, Item item, String field, String value) throws SQLException {
+ private void addLicenseField(Context context, Item item, String field, String language, String value)
+ throws SQLException {
String[] params = splitField(field);
- itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value);
+ itemService.addMetadata(context, item, params[0], params[1], params[2], language, value);
}
@@ -605,7 +606,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
}
}
- updateJurisdiction(fullParamMap);
+ // Replace the jurisdiction unless default value is set to none
+ if (!"none".equals(jurisdiction)) {
+ updateJurisdiction(fullParamMap);
+ }
return fullParamMap;
}
@@ -688,12 +692,12 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi
String uriField = getCCField("uri");
String nameField = getCCField("name");
- addLicenseField(context, item, uriField, licenseUri);
+ addLicenseField(context, item, uriField, null, licenseUri);
if (configurationService.getBooleanProperty("cc.submit.addbitstream")) {
setLicenseRDF(context, item, fetchLicenseRDF(doc));
}
if (configurationService.getBooleanProperty("cc.submit.setname")) {
- addLicenseField(context, item, nameField, licenseName);
+ addLicenseField(context, item, nameField, "en", licenseName);
}
}
diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java
index 33edea112e..07a79384c7 100644
--- a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java
+++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java
@@ -79,6 +79,8 @@ public class OrcidHistory implements ReloadableEntity {
/**
* A description of the synchronized resource.
*/
+ @Lob
+ @Type(type = "org.hibernate.type.TextType")
@Column(name = "description")
private String description;
@@ -87,7 +89,7 @@ public class OrcidHistory implements ReloadableEntity {
* the owner itself.
*/
@Lob
- @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType")
+ @Type(type = "org.hibernate.type.TextType")
@Column(name = "metadata")
private String metadata;
@@ -102,7 +104,7 @@ public class OrcidHistory implements ReloadableEntity {
* The response message incoming from ORCID.
*/
@Lob
- @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType")
+ @Type(type = "org.hibernate.type.TextType")
@Column(name = "response_message")
private String responseMessage;
diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java
index 4794e89008..65b66cd20c 100644
--- a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java
+++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java
@@ -64,6 +64,8 @@ public class OrcidQueue implements ReloadableEntity {
/**
* A description of the resource to be synchronized.
*/
+ @Lob
+ @Type(type = "org.hibernate.type.TextType")
@Column(name = "description")
private String description;
@@ -87,7 +89,7 @@ public class OrcidQueue implements ReloadableEntity {
*/
@Lob
@Column(name = "metadata")
- @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType")
+ @Type(type = "org.hibernate.type.TextType")
private String metadata;
/**
diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java
index 15cc1b2fc6..eab3ba460c 100644
--- a/dspace-api/src/main/java/org/dspace/scripts/Process.java
+++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java
@@ -21,6 +21,7 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
+import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
@@ -35,6 +36,7 @@ import org.dspace.content.ProcessStatus;
import org.dspace.core.ReloadableEntity;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
+import org.hibernate.annotations.Type;
/**
* This class is the DB Entity representation of the Process object to be stored in the Database
@@ -68,6 +70,8 @@ public class Process implements ReloadableEntity {
@Enumerated(EnumType.STRING)
private ProcessStatus processStatus;
+ @Lob
+ @Type(type = "org.hibernate.type.TextType")
@Column(name = "parameters")
private String parameters;
@@ -225,15 +229,15 @@ public class Process implements ReloadableEntity {
}
/**
- * This method sets the special groups associated with the Process.
+ * This method will return the special groups associated with the Process.
*/
public List getGroups() {
return groups;
}
/**
- * This method will return special groups associated with the Process.
- * @return The special groups of this process.
+ * This method sets the special groups associated with the Process.
+ * @param groups The special groups of this process.
*/
public void setGroups(List groups) {
this.groups = groups;
diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java
index 4eb7cdbbc1..c8a7812a51 100644
--- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java
@@ -8,6 +8,7 @@
package org.dspace.scripts;
import java.lang.reflect.InvocationTargetException;
+import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@@ -36,7 +37,9 @@ public class ScriptServiceImpl implements ScriptService {
@Override
public List getScriptConfigurations(Context context) {
return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter(
- scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)).collect(Collectors.toList());
+ scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context))
+ .sorted(Comparator.comparing(ScriptConfiguration::getName))
+ .collect(Collectors.toList());
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java
index 4b2ae94e75..c192a25594 100644
--- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java
@@ -17,9 +17,12 @@ import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.URI;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
@@ -174,6 +177,19 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea
@Override
public void afterPropertiesSet() throws Exception {
+ statisticsCoreURL = configurationService.getProperty("solr-statistics.server");
+
+ if (null != statisticsCoreURL) {
+ Path statisticsPath = Paths.get(new URI(statisticsCoreURL).getPath());
+ statisticsCoreBase = statisticsPath
+ .getName(statisticsPath.getNameCount() - 1)
+ .toString();
+ } else {
+ log.warn("Unable to find solr-statistics.server parameter in DSpace configuration. This is required for " +
+ "sharding statistics.");
+ statisticsCoreBase = null;
+ }
+
solr = solrStatisticsCore.getSolr();
// Read in the file so we don't have to do it all the time
@@ -197,7 +213,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea
@Override
public void postView(DSpaceObject dspaceObject, HttpServletRequest request,
EPerson currentUser) {
- if (solr == null || locationService == null) {
+ if (solr == null) {
return;
}
initSolrYearCores();
@@ -238,7 +254,7 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea
@Override
public void postView(DSpaceObject dspaceObject,
String ip, String userAgent, String xforwardedfor, EPerson currentUser) {
- if (solr == null || locationService == null) {
+ if (solr == null) {
return;
}
initSolrYearCores();
diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java
index b1b31c0fe1..e45ce163ed 100644
--- a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java
+++ b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java
@@ -16,6 +16,7 @@ import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.logging.log4j.Logger;
+import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Get;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.statistics.factory.StatisticsServiceFactory;
@@ -136,6 +137,7 @@ public class StatisticsClient {
URL url = new URL(value);
Get get = new Get();
+ get.setProject(new Project());
get.setDest(new File(spiders, url.getHost() + url.getPath().replace("/", "-")));
get.setSrc(url);
get.setUseTimestamp(true);
diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java
index 0bd71088da..977b5b7b32 100644
--- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java
@@ -17,6 +17,7 @@ import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -224,25 +225,62 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini
@Override
public void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLException, IOException, AuthorizeException {
Context context = new Context(Context.Mode.BATCH_EDIT);
- int commitCounter = 0;
+
+ int offset = 0;
+ int limit = 100;
+
+ int cleanedBitstreamCount = 0;
+
+ int deletedBitstreamCount = bitstreamService.countDeletedBitstreams(context);
+ System.out.println("Found " + deletedBitstreamCount + " deleted bistream to cleanup");
try {
context.turnOffAuthorisationSystem();
- List storage = bitstreamService.findDeletedBitstreams(context);
- for (Bitstream bitstream : storage) {
- UUID bid = bitstream.getID();
- Map wantedMetadata = new HashMap();
- wantedMetadata.put("size_bytes", null);
- wantedMetadata.put("modified", null);
- Map receivedMetadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, wantedMetadata);
+ while (cleanedBitstreamCount < deletedBitstreamCount) {
+
+ List storage = bitstreamService.findDeletedBitstreams(context, limit, offset);
+
+ if (CollectionUtils.isEmpty(storage)) {
+ break;
+ }
+
+ for (Bitstream bitstream : storage) {
+ UUID bid = bitstream.getID();
+ Map wantedMetadata = new HashMap();
+ wantedMetadata.put("size_bytes", null);
+ wantedMetadata.put("modified", null);
+ Map receivedMetadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, wantedMetadata);
- // Make sure entries which do not exist are removed
- if (MapUtils.isEmpty(receivedMetadata)) {
- log.debug("bitstore.about is empty, so file is not present");
+ // Make sure entries which do not exist are removed
+ if (MapUtils.isEmpty(receivedMetadata)) {
+ log.debug("bitstore.about is empty, so file is not present");
+ if (deleteDbRecords) {
+ log.debug("deleting record");
+ if (verbose) {
+ System.out.println(" - Deleting bitstream information (ID: " + bid + ")");
+ }
+ checksumHistoryService.deleteByBitstream(context, bitstream);
+ if (verbose) {
+ System.out.println(" - Deleting bitstream record from database (ID: " + bid + ")");
+ }
+ bitstreamService.expunge(context, bitstream);
+ }
+ context.uncacheEntity(bitstream);
+ continue;
+ }
+
+ // This is a small chance that this is a file which is
+ // being stored -- get it next time.
+ if (isRecent(Long.valueOf(receivedMetadata.get("modified").toString()))) {
+ log.debug("file is recent");
+ context.uncacheEntity(bitstream);
+ continue;
+ }
+
if (deleteDbRecords) {
- log.debug("deleting record");
+ log.debug("deleting db record");
if (verbose) {
System.out.println(" - Deleting bitstream information (ID: " + bid + ")");
}
@@ -252,60 +290,42 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini
}
bitstreamService.expunge(context, bitstream);
}
+
+ if (isRegisteredBitstream(bitstream.getInternalId())) {
+ context.uncacheEntity(bitstream);
+ continue; // do not delete registered bitstreams
+ }
+
+
+ // Since versioning allows for multiple bitstreams, check if the internal
+ // identifier isn't used on
+ // another place
+ if (bitstreamService.findDuplicateInternalIdentifier(context, bitstream).isEmpty()) {
+ this.getStore(bitstream.getStoreNumber()).remove(bitstream);
+
+ String message = ("Deleted bitstreamID " + bid + ", internalID " + bitstream.getInternalId());
+ if (log.isDebugEnabled()) {
+ log.debug(message);
+ }
+ if (verbose) {
+ System.out.println(message);
+ }
+ }
+
context.uncacheEntity(bitstream);
- continue;
}
- // This is a small chance that this is a file which is
- // being stored -- get it next time.
- if (isRecent(Long.valueOf(receivedMetadata.get("modified").toString()))) {
- log.debug("file is recent");
- context.uncacheEntity(bitstream);
- continue;
+ // Commit actual changes to DB after dispatch events
+ System.out.print("Performing incremental commit to the database...");
+ context.commit();
+ System.out.println(" Incremental commit done!");
+
+ cleanedBitstreamCount = cleanedBitstreamCount + storage.size();
+
+ if (!deleteDbRecords) {
+ offset = offset + limit;
}
- if (deleteDbRecords) {
- log.debug("deleting db record");
- if (verbose) {
- System.out.println(" - Deleting bitstream information (ID: " + bid + ")");
- }
- checksumHistoryService.deleteByBitstream(context, bitstream);
- if (verbose) {
- System.out.println(" - Deleting bitstream record from database (ID: " + bid + ")");
- }
- bitstreamService.expunge(context, bitstream);
- }
-
- if (isRegisteredBitstream(bitstream.getInternalId())) {
- context.uncacheEntity(bitstream);
- continue; // do not delete registered bitstreams
- }
-
-
- // Since versioning allows for multiple bitstreams, check if the internal identifier isn't used on
- // another place
- if (bitstreamService.findDuplicateInternalIdentifier(context, bitstream).isEmpty()) {
- this.getStore(bitstream.getStoreNumber()).remove(bitstream);
-
- String message = ("Deleted bitstreamID " + bid + ", internalID " + bitstream.getInternalId());
- if (log.isDebugEnabled()) {
- log.debug(message);
- }
- if (verbose) {
- System.out.println(message);
- }
- }
-
- // Make sure to commit our outstanding work every 100
- // iterations. Otherwise you risk losing the entire transaction
- // if we hit an exception, which isn't useful at all for large
- // amounts of bitstreams.
- commitCounter++;
- if (commitCounter % 100 == 0) {
- context.dispatchEvents();
- }
-
- context.uncacheEntity(bitstream);
}
System.out.print("Committing changes to the database...");
diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java
index 70eabbcc80..2bad0ac012 100644
--- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java
+++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java
@@ -62,6 +62,8 @@ import org.springframework.beans.factory.annotation.Autowired;
public class S3BitStoreService extends BaseBitStoreService {
protected static final String DEFAULT_BUCKET_PREFIX = "dspace-asset-";
+ // Prefix indicating a registered bitstream
+ protected final String REGISTERED_FLAG = "-R";
/**
* log4j log
*/
@@ -246,6 +248,10 @@ public class S3BitStoreService extends BaseBitStoreService {
@Override
public InputStream get(Bitstream bitstream) throws IOException {
String key = getFullKey(bitstream.getInternalId());
+ // Strip -R from bitstream key if it's registered
+ if (isRegisteredBitstream(key)) {
+ key = key.substring(REGISTERED_FLAG.length());
+ }
try {
S3Object object = s3Service.getObject(new GetObjectRequest(bucketName, key));
return (object != null) ? object.getObjectContent() : null;
@@ -312,6 +318,10 @@ public class S3BitStoreService extends BaseBitStoreService {
@Override
public Map about(Bitstream bitstream, Map attrs) throws IOException {
String key = getFullKey(bitstream.getInternalId());
+ // If this is a registered bitstream, strip the -R prefix before retrieving
+ if (isRegisteredBitstream(key)) {
+ key = key.substring(REGISTERED_FLAG.length());
+ }
try {
ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key);
if (objectMetadata != null) {
@@ -410,13 +420,13 @@ public class S3BitStoreService extends BaseBitStoreService {
* @param sInternalId
* @return Computed Relative path
*/
- private String getRelativePath(String sInternalId) {
+ public String getRelativePath(String sInternalId) {
BitstreamStorageService bitstreamStorageService = StorageServiceFactory.getInstance()
.getBitstreamStorageService();
String sIntermediatePath = StringUtils.EMPTY;
if (bitstreamStorageService.isRegisteredBitstream(sInternalId)) {
- sInternalId = sInternalId.substring(2);
+ sInternalId = sInternalId.substring(REGISTERED_FLAG.length());
} else {
sInternalId = sanitizeIdentifier(sInternalId);
sIntermediatePath = getIntermediatePath(sInternalId);
@@ -587,4 +597,14 @@ public class S3BitStoreService extends BaseBitStoreService {
store.get(id);
*/
}
+
+ /**
+ * Is this a registered bitstream? (not stored via this service originally)
+ * @param internalId
+ * @return
+ */
+ public boolean isRegisteredBitstream(String internalId) {
+ return internalId.startsWith(REGISTERED_FLAG);
+ }
+
}
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java
index 1a690afd86..89010a7308 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java
@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import javax.sql.DataSource;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.core.Context;
@@ -37,6 +38,7 @@ import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationInfo;
+import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.internal.info.MigrationInfoDumper;
@@ -73,7 +75,6 @@ public class DatabaseUtils {
// Types of databases supported by DSpace. See getDbType()
public static final String DBMS_POSTGRES = "postgres";
- public static final String DBMS_ORACLE = "oracle";
public static final String DBMS_H2 = "h2";
// Name of the table that Flyway uses for its migration history
@@ -93,7 +94,7 @@ public class DatabaseUtils {
// Usage checks
if (argv.length < 1) {
System.out.println("\nDatabase action argument is missing.");
- System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'validate', " +
+ System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', 'validate', " +
"'update-sequences' or 'clean'");
System.out.println("\nOr, type 'database help' for more information.\n");
System.exit(1);
@@ -111,280 +112,337 @@ public class DatabaseUtils {
// *before* any other Flyway commands can be run. This is a safety check.
FlywayUpgradeUtils.upgradeFlywayTable(flyway, dataSource.getConnection());
- // "test" = Test Database Connection
- if (argv[0].equalsIgnoreCase("test")) {
- // Try to connect to the database
- System.out.println("\nAttempting to connect to database");
- try (Connection connection = dataSource.getConnection()) {
- System.out.println("Connected successfully!");
+ // Determine action param passed to "./dspace database"
+ switch (argv[0].toLowerCase(Locale.ENGLISH)) {
+ // "test" = Test Database Connection
+ case "test":
+ // Try to connect to the database
+ System.out.println("\nAttempting to connect to database");
+ try (Connection connection = dataSource.getConnection()) {
+ System.out.println("Connected successfully!");
- // Print basic database connection information
- printDBInfo(connection);
+ // Print basic database connection information
+ printDBInfo(connection);
- // Print any database warnings/errors found (if any)
- boolean issueFound = printDBIssues(connection);
+ // Print any database warnings/errors found (if any)
+ boolean issueFound = printDBIssues(connection);
- // If issues found, exit with an error status (even if connection succeeded).
- if (issueFound) {
- System.exit(1);
- } else {
- System.exit(0);
- }
- } catch (SQLException sqle) {
- System.err.println("\nError running 'test': ");
- System.err.println(" - " + sqle);
- System.err.println("\nPlease see the DSpace documentation for assistance.\n");
- sqle.printStackTrace(System.err);
- System.exit(1);
- }
- } else if (argv[0].equalsIgnoreCase("info") || argv[0].equalsIgnoreCase("status")) {
- try (Connection connection = dataSource.getConnection()) {
- // Print basic Database info
- printDBInfo(connection);
-
- // Get info table from Flyway
- System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all()));
-
- // If Flyway is NOT yet initialized, also print the determined version information
- // NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase,
- // See: http://flywaydb.org/documentation/faq.html#case-sensitive
- if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) {
- System.out
- .println("\nNOTE: This database is NOT yet initialized for auto-migrations (via Flyway).");
- // Determine which version of DSpace this looks like
- String dbVersion = determineDBVersion(connection);
- if (dbVersion != null) {
- System.out
- .println("\nYour database looks to be compatible with DSpace version " + dbVersion);
- System.out.println(
- "All upgrades *after* version " + dbVersion + " will be run during the next migration" +
- ".");
- System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database migrate'.");
- }
- }
-
- // Print any database warnings/errors found (if any)
- boolean issueFound = printDBIssues(connection);
-
- // If issues found, exit with an error status
- if (issueFound) {
- System.exit(1);
- } else {
- System.exit(0);
- }
- } catch (SQLException e) {
- System.err.println("Info exception:");
- e.printStackTrace(System.err);
- System.exit(1);
- }
- } else if (argv[0].equalsIgnoreCase("migrate")) {
- try (Connection connection = dataSource.getConnection()) {
- System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
-
- // "migrate" allows for an OPTIONAL second argument (only one may be specified):
- // - "ignored" = Also run any previously "ignored" migrations during the migration
- // - "force" = Even if no pending migrations exist, still run a migration to trigger callbacks.
- // - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING)
- if (argv.length == 2) {
- if (argv[1].equalsIgnoreCase("ignored")) {
- System.out.println(
- "Migrating database to latest version AND running previously \"Ignored\" " +
- "migrations... (Check logs for details)");
- // Update the database to latest version, but set "outOfOrder=true"
- // This will ensure any old migrations in the "ignored" state are now run
- updateDatabase(dataSource, connection, null, true);
- } else if (argv[1].equalsIgnoreCase("force")) {
- updateDatabase(dataSource, connection, null, false, true);
+ // If issues found, exit with an error status (even if connection succeeded).
+ if (issueFound) {
+ System.exit(1);
} else {
- // Otherwise, we assume "argv[1]" is a valid migration version number
- // This is only for testing! Never specify for Production!
+ System.exit(0);
+ }
+ } catch (SQLException sqle) {
+ System.err.println("\nError running 'test': ");
+ System.err.println(" - " + sqle);
+ System.err.println("\nPlease see the DSpace documentation for assistance.\n");
+ sqle.printStackTrace(System.err);
+ System.exit(1);
+ }
+ break;
+ // "info" and "status" are identical and provide database info
+ case "info":
+ case "status":
+ try (Connection connection = dataSource.getConnection()) {
+ // Print basic Database info
+ printDBInfo(connection);
+
+ // Get info table from Flyway
+ System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all()));
+
+ // If Flyway is NOT yet initialized, also print the determined version information
+ // NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase,
+ // See: http://flywaydb.org/documentation/faq.html#case-sensitive
+ if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) {
+ System.out
+ .println("\nNOTE: This database is NOT yet initialized for auto-migrations " +
+ "(via Flyway).");
+ // Determine which version of DSpace this looks like
+ String dbVersion = determineDBVersion(connection);
+ if (dbVersion != null) {
+ System.out
+ .println("\nYour database looks to be compatible with DSpace version " + dbVersion);
+ System.out.println(
+ "All upgrades *after* version " + dbVersion + " will be run during the next " +
+ "migration.");
+ System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database " +
+ "migrate'.");
+ }
+ }
+
+ // Print any database warnings/errors found (if any)
+ boolean issueFound = printDBIssues(connection);
+
+ // If issues found, exit with an error status
+ if (issueFound) {
+ System.exit(1);
+ } else {
+ System.exit(0);
+ }
+ } catch (SQLException e) {
+ System.err.println("Info exception:");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ break;
+ // "migrate" = Run all pending database migrations
+ case "migrate":
+ try (Connection connection = dataSource.getConnection()) {
+ System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
+
+ // "migrate" allows for an OPTIONAL second argument (only one may be specified):
+ // - "ignored" = Also run any previously "ignored" migrations during the migration
+ // - "force" = Even if no pending migrations exist, still run migrate to trigger callbacks.
+ // - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING)
+ if (argv.length == 2) {
+ if (argv[1].equalsIgnoreCase("ignored")) {
+ System.out.println(
+ "Migrating database to latest version AND running previously \"Ignored\" " +
+ "migrations... (Check logs for details)");
+ // Update the database to latest version, but set "outOfOrder=true"
+ // This will ensure any old migrations in the "ignored" state are now run
+ updateDatabase(dataSource, connection, null, true);
+ } else if (argv[1].equalsIgnoreCase("force")) {
+ updateDatabase(dataSource, connection, null, false, true);
+ } else {
+ // Otherwise, we assume "argv[1]" is a valid migration version number
+ // This is only for testing! Never specify for Production!
+ String migrationVersion = argv[1];
+ BufferedReader input = new BufferedReader(
+ new InputStreamReader(System.in, StandardCharsets.UTF_8));
+
+ System.out.println(
+ "You've specified to migrate your database ONLY to version " + migrationVersion +
+ " ...");
+ System.out.println(
+ "\nWARNING: In this mode, we DISABLE all callbacks, which means that you will " +
+ "need to manually update registries and manually run a reindex. This is " +
+ "because you are attempting to use an OLD version (" + migrationVersion + ") " +
+ "Database with a newer DSpace API. NEVER do this in a PRODUCTION scenario. " +
+ "The resulting database is only useful for migration testing.\n");
+
+ System.out.print(
+ "Are you SURE you only want to migrate your database to version " +
+ migrationVersion + "? [y/n]: ");
+ String choiceString = input.readLine();
+ input.close();
+
+ if (choiceString.equalsIgnoreCase("y")) {
+ System.out.println(
+ "Migrating database ONLY to version " + migrationVersion + " ... " +
+ "(Check logs for details)");
+ // Update the database, to the version specified.
+ updateDatabase(dataSource, connection, migrationVersion, false);
+ } else {
+ System.out.println("No action performed.");
+ }
+ }
+ } else {
+ System.out.println("Migrating database to latest version... " +
+ "(Check dspace logs for details)");
+ updateDatabase(dataSource, connection);
+ }
+ System.out.println("Done.");
+ System.exit(0);
+ } catch (SQLException e) {
+ System.err.println("Migration exception:");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ break;
+ // "repair" = Run Flyway repair script
+ case "repair":
+ try (Connection connection = dataSource.getConnection();) {
+ System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
+ System.out.println(
+ "Attempting to repair any previously failed migrations (or mismatched checksums) via " +
+ "FlywayDB... (Check dspace logs for details)");
+ flyway.repair();
+ System.out.println("Done.");
+ System.exit(0);
+ } catch (SQLException | FlywayException e) {
+ System.err.println("Repair exception:");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ break;
+ // "skip" = Skip a specific Flyway migration (by telling Flyway it succeeded)
+ case "skip":
+ try {
+ // "skip" requires a migration version to skip. Only that exact version will be skipped.
+ if (argv.length == 2) {
String migrationVersion = argv[1];
+
BufferedReader input = new BufferedReader(
- new InputStreamReader(System.in, StandardCharsets.UTF_8));
-
+ new InputStreamReader(System.in, StandardCharsets.UTF_8));
System.out.println(
- "You've specified to migrate your database ONLY to version " + migrationVersion + " " +
+ "You've specified to SKIP the migration with version='" + migrationVersion + "' " +
"...");
- System.out.println(
- "\nWARNING: In this mode, we DISABLE all callbacks, which means that you will need " +
- "to manually update registries and manually run a reindex. This is because you " +
- "are attempting to use an OLD version (" + migrationVersion + ") Database with " +
- "a newer DSpace API. NEVER do this in a PRODUCTION scenario. The resulting " +
- "database is only useful for migration testing.\n");
-
System.out.print(
- "Are you SURE you only want to migrate your database to version " + migrationVersion
- + "? [y/n]: ");
+ "\nWARNING: You should only skip migrations which are no longer required or have " +
+ "become obsolete. Skipping a REQUIRED migration may result in DSpace failing " +
+ "to startup or function properly. Are you sure you want to SKIP the " +
+ "migration with version '" + migrationVersion + "'? [y/n]: ");
String choiceString = input.readLine();
input.close();
if (choiceString.equalsIgnoreCase("y")) {
System.out.println(
- "Migrating database ONLY to version " + migrationVersion + " ... (Check logs for " +
- "details)");
- // Update the database, to the version specified.
- updateDatabase(dataSource, connection, migrationVersion, false);
- } else {
- System.out.println("No action performed.");
+ "Attempting to skip migration with version " + migrationVersion + " " +
+ "... (Check logs for details)");
+ skipMigration(dataSource, migrationVersion);
+ }
+ } else {
+ System.out.println("The 'skip' command REQUIRES a version to be specified. " +
+ "Only that single migration will be skipped. For the list " +
+ "of migration versions use the 'info' command.");
+ }
+ } catch (IOException e) {
+ System.err.println("Exception when attempting to skip migration:");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ break;
+ // "validate" = Run Flyway validation to check for database errors/issues
+ case "validate":
+ try (Connection connection = dataSource.getConnection();) {
+ System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
+ System.out
+ .println("Attempting to validate database status (and migration checksums) via " +
+ "FlywayDB...");
+ flyway.validate();
+ System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more " +
+ "details)");
+ System.exit(0);
+ } catch (SQLException | FlywayException e) {
+ System.err.println("Validation exception:");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ break;
+ // "clean" = Run Flyway clean script
+ case "clean":
+ // If clean is disabled, return immediately
+ if (flyway.getConfiguration().isCleanDisabled()) {
+ System.out.println(
+ "\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in " +
+ "Production scenarios!");
+ System.out.println(
+ "\nIn order to run a 'clean' you first must enable it in your DSpace config by " +
+ "specifying 'db.cleanDisabled=false'.\n");
+ System.exit(1);
+ }
+
+ try (Connection connection = dataSource.getConnection()) {
+ String dbType = getDbType(connection);
+
+ // Not all Postgres user accounts will be able to run a 'clean',
+ // as only 'superuser' accounts can remove the 'pgcrypto' extension.
+ if (dbType.equals(DBMS_POSTGRES)) {
+ // Check if database user has permissions suitable to run a clean
+ if (!PostgresUtils.checkCleanPermissions(connection)) {
+ String username = connection.getMetaData().getUserName();
+ // Exit immediately, providing a descriptive error message
+ System.out.println(
+ "\nERROR: The database user '" + username + "' does not have sufficient " +
+ "privileges to run a 'database clean' (via Flyway).");
+ System.out.println(
+ "\nIn order to run a 'clean', the database user MUST have 'superuser' privileges");
+ System.out.println(
+ "OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a " +
+ "separate schema (see documentation).");
+ System.out.println(
+ "\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO +
+ "' extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO +
+ " CASCADE;), then rerun the 'clean'");
+ System.exit(1);
}
}
- } else {
- System.out.println("Migrating database to latest version... (Check dspace logs for details)");
- updateDatabase(dataSource, connection);
- }
- System.out.println("Done.");
- System.exit(0);
- } catch (SQLException e) {
- System.err.println("Migration exception:");
- e.printStackTrace(System.err);
- System.exit(1);
- }
- } else if (argv[0].equalsIgnoreCase("repair")) {
- // "repair" = Run Flyway repair script
- try (Connection connection = dataSource.getConnection();) {
- System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
- System.out.println(
- "Attempting to repair any previously failed migrations (or mismatched checksums) via " +
- "FlywayDB... (Check dspace logs for details)");
- flyway.repair();
- System.out.println("Done.");
- System.exit(0);
- } catch (SQLException | FlywayException e) {
- System.err.println("Repair exception:");
- e.printStackTrace(System.err);
- System.exit(1);
- }
- } else if (argv[0].equalsIgnoreCase("validate")) {
- // "validate" = Run Flyway validation to check for database errors/issues
+ BufferedReader input = new BufferedReader(new InputStreamReader(System.in,
+ StandardCharsets.UTF_8));
- try (Connection connection = dataSource.getConnection();) {
- System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
- System.out
- .println("Attempting to validate database status (and migration checksums) via FlywayDB...");
- flyway.validate();
- System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more details)");
- System.exit(0);
- } catch (SQLException | FlywayException e) {
- System.err.println("Validation exception:");
- e.printStackTrace(System.err);
- System.exit(1);
- }
- } else if (argv[0].equalsIgnoreCase("clean")) {
- // "clean" = Run Flyway clean script
-
- // If clean is disabled, return immediately
- if (flyway.getConfiguration().isCleanDisabled()) {
- System.out.println(
- "\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in Production " +
- "scenarios!");
- System.out.println(
- "\nIn order to run a 'clean' you first must enable it in your DSpace config by specifying 'db" +
- ".cleanDisabled=false'.\n");
- System.exit(1);
- }
-
- try (Connection connection = dataSource.getConnection()) {
- String dbType = getDbType(connection);
-
- // Not all Postgres user accounts will be able to run a 'clean',
- // as only 'superuser' accounts can remove the 'pgcrypto' extension.
- if (dbType.equals(DBMS_POSTGRES)) {
- // Check if database user has permissions suitable to run a clean
- if (!PostgresUtils.checkCleanPermissions(connection)) {
- String username = connection.getMetaData().getUserName();
- // Exit immediately, providing a descriptive error message
+ System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
+ System.out
+ .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n");
+ System.out.println("There is NO turning back from this action. Backup your DB before " +
+ "continuing.");
+ if (dbType.equals(DBMS_POSTGRES)) {
System.out.println(
- "\nERROR: The database user '" + username + "' does not have sufficient privileges to" +
- " run a 'database clean' (via Flyway).");
- System.out.println(
- "\nIn order to run a 'clean', the database user MUST have 'superuser' privileges");
- System.out.println(
- "OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a separate " +
- "schema (see documentation).");
- System.out.println(
- "\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO + "' " +
- "extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO + " CASCADE;), then " +
- "rerun the 'clean'");
- System.exit(1);
+ "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped " +
+ "if it is in the same schema as the DSpace database.\n");
}
- }
+ System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: ");
+ String choiceString = input.readLine();
+ input.close();
- BufferedReader input = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
-
- System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
- System.out
- .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n");
- System.out.println("There is NO turning back from this action. Backup your DB before continuing.");
- if (dbType.equals(DBMS_ORACLE)) {
- System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n");
- } else if (dbType.equals(DBMS_POSTGRES)) {
- System.out.println(
- "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped if it" +
- " is in the same schema as the DSpace database.\n");
- }
- System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: ");
- String choiceString = input.readLine();
- input.close();
-
- if (choiceString.equalsIgnoreCase("y")) {
- System.out.println("Scrubbing database clean... (Check dspace logs for details)");
- cleanDatabase(flyway, dataSource);
- System.out.println("Done.");
- System.exit(0);
- } else {
- System.out.println("No action performed.");
- }
- } catch (SQLException e) {
- System.err.println("Clean exception:");
- e.printStackTrace(System.err);
- System.exit(1);
- }
- } else if (argv[0].equalsIgnoreCase("update-sequences")) {
- try (Connection connection = dataSource.getConnection()) {
- String dbType = getDbType(connection);
- String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType +
- "/update-sequences.sql";
- InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile);
- if (sqlstream != null) {
- String s = IOUtils.toString(sqlstream, "UTF-8");
- if (!s.isEmpty()) {
- System.out.println("Running " + sqlfile);
- connection.createStatement().execute(s);
- System.out.println("update-sequences complete");
+ if (choiceString.equalsIgnoreCase("y")) {
+ System.out.println("Scrubbing database clean... (Check dspace logs for details)");
+ cleanDatabase(flyway, dataSource);
+ System.out.println("Done.");
+ System.exit(0);
} else {
- System.err.println(sqlfile + " contains no SQL to execute");
+ System.out.println("No action performed.");
}
- } else {
- System.err.println(sqlfile + " not found");
+ } catch (SQLException e) {
+ System.err.println("Clean exception:");
+ e.printStackTrace(System.err);
+ System.exit(1);
}
- }
- } else {
- System.out.println("\nUsage: database [action]");
- System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', " +
- "'update-sequences' or 'clean'");
- System.out.println(
- " - test = Performs a test connection to database to " +
- "validate connection settings");
- System.out.println(
- " - info / status = Describe basic info/status about database, including validating the " +
- "compatibility of this database");
- System.out.println(
- " - migrate = Migrate the database to the latest version");
- System.out.println(
- " - repair = Attempt to repair any previously failed database " +
- "migrations or checksum mismatches (via Flyway repair)");
- System.out.println(
- " - validate = Validate current database's migration status (via Flyway validate), " +
- "validating all migration checksums.");
- System.out.println(
- " - update-sequences = Update database sequences after running AIP ingest.");
- System.out.println(
- " - clean = DESTROY all data and tables in database " +
- "(WARNING there is no going back!). " +
- "Requires 'db.cleanDisabled=false' setting in config.");
- System.out.println("");
- System.exit(0);
+ break;
+ // "update-sequences" = Run DSpace's "update-sequences.sql" script
+ case "update-sequences":
+ try (Connection connection = dataSource.getConnection()) {
+ String dbType = getDbType(connection);
+ String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType +
+ "/update-sequences.sql";
+ InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile);
+ if (sqlstream != null) {
+ String s = IOUtils.toString(sqlstream, StandardCharsets.UTF_8);
+ if (!s.isEmpty()) {
+ System.out.println("Running " + sqlfile);
+ connection.createStatement().execute(s);
+ System.out.println("update-sequences complete");
+ } else {
+ System.err.println(sqlfile + " contains no SQL to execute");
+ }
+ } else {
+ System.err.println(sqlfile + " not found");
+ }
+ }
+ break;
+ // default = show help information
+ default:
+ System.out.println("\nUsage: database [action]");
+ System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', " +
+ "'validate', 'update-sequences' or 'clean'");
+ System.out.println(
+ " - test = Performs a test connection to database to " +
+ "validate connection settings");
+ System.out.println(
+ " - info / status = Describe basic info/status about database, including validating the " +
+ "compatibility of this database");
+ System.out.println(
+ " - migrate = Migrate the database to the latest version");
+ System.out.println(
+ " - repair = Attempt to repair any previously failed database " +
+ "migrations or checksum mismatches (via Flyway repair)");
+ System.out.println(
+ " - skip [version] = Skip a single, pending or ignored migration, " +
+ "ensuring it never runs.");
+ System.out.println(
+ " - validate = Validate current database's migration status (via Flyway validate), " +
+ "validating all migration checksums.");
+ System.out.println(
+ " - update-sequences = Update database sequences after running AIP ingest.");
+ System.out.println(
+ " - clean = DESTROY all data and tables in database " +
+ "(WARNING there is no going back!). " +
+ "Requires 'db.cleanDisabled=false' setting in config.");
+ System.out.println("");
+ System.exit(0);
+ break;
}
} catch (Exception e) {
@@ -406,11 +464,10 @@ public class DatabaseUtils {
DatabaseMetaData meta = connection.getMetaData();
String dbType = getDbType(connection);
System.out.println("\nDatabase Type: " + dbType);
- if (dbType.equals(DBMS_ORACLE)) {
- System.out.println("====================================");
- System.out.println("WARNING: Oracle support is deprecated!");
- System.out.println("See https://github.com/DSpace/DSpace/issues/8214");
- System.out.println("=====================================");
+ if (!dbType.equals(DBMS_POSTGRES) && !dbType.equals(DBMS_H2)) {
+ System.err.println("====================================");
+ System.err.println("ERROR: Database type " + dbType + " is UNSUPPORTED!");
+ System.err.println("=====================================");
}
System.out.println("Database URL: " + meta.getURL());
System.out.println("Database Schema: " + getSchemaName(connection));
@@ -545,10 +602,6 @@ public class DatabaseUtils {
String dbType = getDbType(connection);
connection.close();
- if (dbType.equals(DBMS_ORACLE)) {
- log.warn("ORACLE SUPPORT IS DEPRECATED! See https://github.com/DSpace/DSpace/issues/8214");
- }
-
// Determine location(s) where Flyway will load all DB migrations
ArrayList scriptLocations = new ArrayList<>();
@@ -786,6 +839,89 @@ public class DatabaseUtils {
}
}
+ /**
+ * Skips the given migration by marking it as "successful" in the Flyway table. This ensures
+ * the given migration will never be run again.
+ *
+ * WARNING: Skipping a required migration can result in unexpected errors. Make sure the migration is
+ * not required (or obsolete) before skipping it.
+ * @param dataSource current DataSource
+ * @param skipVersion version of migration to skip
+ * @throws SQLException if error occurs
+ */
+ private static synchronized void skipMigration(DataSource dataSource,
+ String skipVersion) throws SQLException {
+ if (null == dataSource) {
+ throw new SQLException("The datasource is a null reference -- cannot continue.");
+ }
+
+ try (Connection connection = dataSource.getConnection()) {
+ // Setup Flyway API against our database
+ FluentConfiguration flywayConfiguration = setupFlyway(dataSource);
+
+ // In order to allow for skipping "Ignored" migrations, we MUST set "outOfOrder=true".
+ // (Otherwise Ignored migrations never appear in the pending list)
+ flywayConfiguration.outOfOrder(true);
+
+ // Initialized Flyway object based on this configuration
+ Flyway flyway = flywayConfiguration.load();
+
+ // Find the migration we are skipping in the list of pending migrations
+ boolean foundMigration = false;
+ for (MigrationInfo migration : flyway.info().pending()) {
+ // If this migration matches our "skipVersion"
+ if (migration.getVersion().equals(MigrationVersion.fromVersion(skipVersion))) {
+ foundMigration = true;
+ System.out.println("Found migration matching version='" + skipVersion + "'. " +
+ "Changing state to 'Success' in order to skip it.");
+
+ PreparedStatement statement = null;
+ try {
+ // Create SQL Insert which will log this migration as having already been run.
+ String INSERT_SQL = "INSERT INTO " + FLYWAY_TABLE + " " +
+ "(" +
+ "installed_rank, version, description, type, script, " +
+ "checksum, installed_by, execution_time, success" +
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ statement = connection.prepareStatement(INSERT_SQL);
+ // installed_rank
+ statement.setInt(1, getNextFlywayInstalledRank(flyway));
+ // version
+ statement.setString(2, migration.getVersion().getVersion());
+ // description
+ statement.setString(3, migration.getDescription());
+ // type
+ statement.setString(4, migration.getType().toString());
+ // script
+ statement.setString(5, migration.getScript());
+ // checksum
+ statement.setInt(6, migration.getChecksum());
+ // installed_by
+ statement.setString(7, getDBUserName(connection));
+ // execution_time is set to zero as we didn't really execute it
+ statement.setInt(8, 0);
+ // success=true tells Flyway this migration no longer needs to be run.
+ statement.setBoolean(9, true);
+
+ // Run the INSERT
+ statement.executeUpdate();
+ } finally {
+ if (statement != null && !statement.isClosed()) {
+ statement.close();
+ }
+ }
+ }
+ }
+ if (!foundMigration) {
+ System.err.println("Could not find migration to skip! " +
+ "No 'Pending' or 'Ignored' migrations match version='" + skipVersion + "'");
+ }
+ } catch (FlywayException fe) {
+ // If any FlywayException (Runtime) is thrown, change it to a SQLException
+ throw new SQLException("Flyway error occurred", fe);
+ }
+ }
+
/**
* Clean the existing database, permanently removing all data and tables
*
@@ -802,26 +938,6 @@ public class DatabaseUtils {
// First, run Flyway's clean command on database.
// For MOST database types, this takes care of everything
flyway.clean();
-
- try (Connection connection = dataSource.getConnection()) {
- // Get info about which database type we are using
- String dbType = getDbType(connection);
-
- // If this is Oracle, the only way to entirely clean the database
- // is to also purge the "Recyclebin". See:
- // http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9018.htm
- if (dbType.equals(DBMS_ORACLE)) {
- PreparedStatement statement = null;
- try {
- statement = connection.prepareStatement("PURGE RECYCLEBIN");
- statement.executeQuery();
- } finally {
- if (statement != null && !statement.isClosed()) {
- statement.close();
- }
- }
- }
- }
} catch (FlywayException fe) {
// If any FlywayException (Runtime) is thrown, change it to a SQLException
throw new SQLException("Flyway clean error occurred", fe);
@@ -1070,11 +1186,6 @@ public class DatabaseUtils {
// We need to filter by schema in PostgreSQL
schemaFilter = true;
break;
- case DBMS_ORACLE:
- // Oracle specific query for a sequence owned by our current DSpace user
- // NOTE: No need to filter by schema for Oracle, as Schema = User
- sequenceSQL = "SELECT COUNT(1) FROM user_sequences WHERE sequence_name=?";
- break;
case DBMS_H2:
// In H2, sequences are listed in the "information_schema.sequences" table
// SEE: http://www.h2database.com/html/grammar.html#information_schema
@@ -1178,11 +1289,6 @@ public class DatabaseUtils {
// For PostgreSQL, the default schema is named "public"
// See: http://www.postgresql.org/docs/9.0/static/ddl-schemas.html
schema = "public";
- } else if (dbType.equals(DBMS_ORACLE)) {
- // For Oracle, default schema is actually the user account
- // See: http://stackoverflow.com/a/13341390
- DatabaseMetaData meta = connection.getMetaData();
- schema = meta.getUserName();
} else {
// For H2 (in memory), there is no such thing as a schema
schema = null;
@@ -1192,6 +1298,34 @@ public class DatabaseUtils {
return schema;
}
+ /**
+ * Get the Database User Name in use by this Connection.
+ *
+ * @param connection Current Database Connection
+ * @return User name as a string, or "null" if cannot be determined or unspecified
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public static String getDBUserName(Connection connection)
+ throws SQLException {
+ String username = null;
+
+ // Try to get the schema from the DB connection itself.
+ // As long as the Database driver supports JDBC4.1, there should be a getSchema() method
+ // If this method is unimplemented or doesn't exist, it will throw an exception (likely an AbstractMethodError)
+ try {
+ username = connection.getMetaData().getUserName();
+ } catch (Exception | AbstractMethodError e) {
+ // ignore
+ }
+
+ // If we don't know our schema, let's try the schema in the DSpace configuration
+ if (StringUtils.isBlank(username)) {
+ username = canonicalize(connection, DSpaceServicesFactory.getInstance().getConfigurationService()
+ .getProperty("db.username"));
+ }
+ return username;
+ }
+
/**
* Return the canonical name for a database identifier based on whether this
* database defaults to storing identifiers in uppercase or lowercase.
@@ -1380,8 +1514,6 @@ public class DatabaseUtils {
String dbms_lc = prodName.toLowerCase(Locale.ROOT);
if (dbms_lc.contains("postgresql")) {
return DBMS_POSTGRES;
- } else if (dbms_lc.contains("oracle")) {
- return DBMS_ORACLE;
} else if (dbms_lc.contains("h2")) {
// Used for unit testing only
return DBMS_H2;
@@ -1443,4 +1575,22 @@ public class DatabaseUtils {
}
return null;
}
+
+ /**
+ * Determine next valid "installed_rank" value from Flyway, based on the "installed_rank" of the
+ * last applied migration.
+ * @param flyway currently loaded Flyway
+ * @return next installed rank value
+ */
+ private static int getNextFlywayInstalledRank(Flyway flyway) throws FlywayException {
+ // Load all applied migrations
+ MigrationInfo[] appliedMigrations = flyway.info().applied();
+ // If no applied migrations, throw an error.
+ // This should never happen, but this would mean Flyway is not installed or initialized
+ if (ArrayUtils.isEmpty(appliedMigrations)) {
+ throw new FlywayException("Cannot determine next 'installed_rank' as no applied migrations exist");
+ }
+ // Find the last migration in the list, and increment its "installed_rank" by one.
+ return appliedMigrations[appliedMigrations.length - 1].getInstalledRank() + 1;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java
deleted file mode 100644
index 95939f9902..0000000000
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * 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.storage.rdbms.hibernate;
-
-import org.apache.commons.lang.StringUtils;
-import org.dspace.services.ConfigurationService;
-import org.dspace.services.factory.DSpaceServicesFactory;
-import org.hibernate.type.AbstractSingleColumnStandardBasicType;
-import org.hibernate.type.descriptor.java.StringTypeDescriptor;
-import org.hibernate.type.descriptor.sql.ClobTypeDescriptor;
-import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor;
-import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
-
-/**
- * A Hibernate @Type used to properly support the CLOB in both Postgres and Oracle.
- * PostgreSQL doesn't have a CLOB type, instead it's a TEXT field.
- * Normally, you'd use org.hibernate.type.TextType to support TEXT, but that won't work for Oracle.
- * https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/type/TextType.java
- *
- * This Type checks if we are using PostgreSQL.
- * If so, it configures Hibernate to map CLOB to LongVarChar (same as org.hibernate.type.TextType)
- * If not, it uses default CLOB (which works for other databases).
- */
-public class DatabaseAwareLobType extends AbstractSingleColumnStandardBasicType {
-
- public static final DatabaseAwareLobType INSTANCE = new DatabaseAwareLobType();
-
- public DatabaseAwareLobType() {
- super( getDbDescriptor(), StringTypeDescriptor.INSTANCE );
- }
-
- public static SqlTypeDescriptor getDbDescriptor() {
- if ( isPostgres() ) {
- return LongVarcharTypeDescriptor.INSTANCE;
- } else {
- return ClobTypeDescriptor.DEFAULT;
- }
- }
-
- private static boolean isPostgres() {
- ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
- String dbDialect = configurationService.getProperty("db.dialect");
-
- return StringUtils.containsIgnoreCase(dbDialect, "PostgreSQL");
- }
-
- @Override
- public String getName() {
- return "database_aware_lob";
- }
-}
-
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java
index 842fc15e16..f0c4e4e179 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java
@@ -78,13 +78,6 @@ public class MigrationUtils {
constraintName += "_" + StringUtils.lowerCase(constraintSuffix);
cascade = true;
break;
- case "oracle":
- // In Oracle, constraints are listed in the USER_CONS_COLUMNS table
- constraintNameSQL = "SELECT CONSTRAINT_NAME " +
- "FROM USER_CONS_COLUMNS " +
- "WHERE TABLE_NAME = ? AND COLUMN_NAME = ?";
- cascade = true;
- break;
case "h2":
// In H2, column constraints are listed in the "INFORMATION_SCHEMA.KEY_COLUMN_USAGE" table
constraintNameSQL = "SELECT DISTINCT CONSTRAINT_NAME " +
@@ -160,9 +153,6 @@ public class MigrationUtils {
case "postgresql":
dropTableSQL = "DROP TABLE IF EXISTS " + tableName + " CASCADE";
break;
- case "oracle":
- dropTableSQL = "DROP TABLE " + tableName + " CASCADE CONSTRAINTS";
- break;
case "h2":
dropTableSQL = "DROP TABLE IF EXISTS " + tableName + " CASCADE";
break;
@@ -208,9 +198,6 @@ public class MigrationUtils {
case "postgresql":
dropSequenceSQL = "DROP SEQUENCE IF EXISTS " + sequenceName;
break;
- case "oracle":
- dropSequenceSQL = "DROP SEQUENCE " + sequenceName;
- break;
case "h2":
dropSequenceSQL = "DROP SEQUENCE IF EXISTS " + sequenceName;
break;
@@ -256,9 +243,6 @@ public class MigrationUtils {
case "postgresql":
dropViewSQL = "DROP VIEW IF EXISTS " + viewName + " CASCADE";
break;
- case "oracle":
- dropViewSQL = "DROP VIEW " + viewName + " CASCADE CONSTRAINTS";
- break;
case "h2":
dropViewSQL = "DROP VIEW IF EXISTS " + viewName + " CASCADE";
break;
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java
index 56c5b474d9..758e745ddc 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java
@@ -19,10 +19,9 @@ import org.flywaydb.core.api.migration.Context;
* of the "community" table. This is necessary for the upgrade from 1.3 to 1.4
*
* This class was created because the names of database constraints differs based
- * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult
+ * on the type of database (Postgres vs. H2). As such, it becomes difficult
* to write simple SQL which will work for multiple database types (especially
- * since unit tests require H2 and the syntax for H2 is different from either
- * Oracle or Postgres).
+ * since unit tests require H2 and the syntax for H2 is different from Postgres).
*
* NOTE: This migration class is very simple because it is meant to be used
* in conjuction with the corresponding SQL script:
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java
index 6d82055e53..37100a17f9 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java
@@ -19,10 +19,9 @@ import org.flywaydb.core.api.migration.Context;
* from 1.5 to 1.6
*
* This class was created because the names of database constraints differs based
- * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult
+ * on the type of database (Postgres vs. H2). As such, it becomes difficult
* to write simple SQL which will work for multiple database types (especially
- * since unit tests require H2 and the syntax for H2 is different from either
- * Oracle or Postgres).
+ * since unit tests require H2 and the syntax for H2 is different from Postgres).
*
* NOTE: This migration class is very simple because it is meant to be used
* in conjuction with the corresponding SQL script:
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java
index ea72d99b6e..8e2be91127 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java
@@ -20,10 +20,9 @@ import org.flywaydb.core.api.migration.Context;
* this column must be renamed to "resource_id".
*
* This class was created because the names of database constraints differs based
- * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult
+ * on the type of database (Postgres vs. H2). As such, it becomes difficult
* to write simple SQL which will work for multiple database types (especially
- * since unit tests require H2 and the syntax for H2 is different from either
- * Oracle or Postgres).
+ * since unit tests require H2 and the syntax for H2 is different from Postgres).
*
* NOTE: This migration class is very simple because it is meant to be used
* in conjuction with the corresponding SQL script:
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java
index b3306a9fc9..0361e68053 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java
@@ -67,8 +67,6 @@ public class V5_0_2014_11_04__Enable_XMLWorkflow_Migration
String dbFileLocation = null;
if (dbtype.toLowerCase().contains("postgres")) {
dbFileLocation = "postgres";
- } else if (dbtype.toLowerCase().contains("oracle")) {
- dbFileLocation = "oracle";
} else if (dbtype.toLowerCase().contains("h2")) {
dbFileLocation = "h2";
}
diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java
index 9aa0f4877c..4c1cf33653 100644
--- a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java
+++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java
@@ -46,8 +46,6 @@ public class V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration extends BaseJ
String dbFileLocation = null;
if (dbtype.toLowerCase().contains("postgres")) {
dbFileLocation = "postgres";
- } else if (dbtype.toLowerCase().contains("oracle")) {
- dbFileLocation = "oracle";
} else if (dbtype.toLowerCase().contains("h2")) {
dbFileLocation = "h2";
}
diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java
new file mode 100644
index 0000000000..a913f2504a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java
@@ -0,0 +1,97 @@
+/**
+ * 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.subscriptions;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.lang.StringUtils.EMPTY;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.content.Item;
+import org.dspace.content.crosswalk.StreamDisseminationCrosswalk;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.core.Email;
+import org.dspace.core.I18nUtil;
+import org.dspace.discovery.IndexableObject;
+import org.dspace.eperson.EPerson;
+import org.dspace.subscriptions.service.SubscriptionGenerator;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation class of SubscriptionGenerator
+ * which will handle the logic of sending the emails
+ * in case of 'content' subscriptionType
+ */
+@SuppressWarnings("rawtypes")
+public class ContentGenerator implements SubscriptionGenerator {
+
+ private final Logger log = LogManager.getLogger(ContentGenerator.class);
+
+ @SuppressWarnings("unchecked")
+ private Map entityType2Disseminator = new HashMap();
+
+ @Autowired
+ private ItemService itemService;
+
+ @Override
+ public void notifyForSubscriptions(Context context, EPerson ePerson,
+ List