list = new ArrayList<>();
- list.add(ISODateTimeFormat.dateTime());
- list.add(ISODateTimeFormat.dateTimeNoMillis());
+ /**
+ * Build a list of ISO date formatters to parse various forms.
+ *
+ * Note: any formatter which does not parse a zone or
+ * offset must have a default zone set. See {@link stringToDate}.
+ *
+ * @return the formatters.
+ */
+ static private List getDateFormatters() {
+ List list = new ArrayList<>();
+ list.add(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]X"));
+ list.add(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME
+ .withZone(ZoneId.systemDefault().normalized()));
return list;
}
- public Date stringToDate(String date) {
+ /**
+ * Convert a date string to internal form, trying several parsers.
+ *
+ * @param date serialized date to be converted.
+ * @return converted date, or null if no parser accepted the input.
+ */
+ static public Date stringToDate(String date) {
Date result = null;
if (StringUtils.isNotBlank(date)) {
- List dateFormatters = getDateFormatters();
- boolean converted = false;
- int formatter = 0;
- while (!converted) {
+ for (DateTimeFormatter formatter : getDateFormatters()) {
try {
- DateTimeFormatter dateTimeFormatter = dateFormatters.get(formatter);
- DateTime dateTime = dateTimeFormatter.parseDateTime(date);
- result = dateTime.toDate();
- converted = true;
- } catch (IllegalArgumentException e) {
- formatter++;
- if (formatter > dateFormatters.size()) {
- converted = true;
- }
- log.error("Could not find a valid date format for: \"" + date + "\"", e);
+ ZonedDateTime dateTime = ZonedDateTime.parse(date, formatter);
+ result = Date.from(dateTime.toInstant());
+ break;
+ } catch (DateTimeException e) {
+ log.debug("Input '{}' did not match {}", date, formatter);
}
}
}
+ if (null == result) {
+ log.error("Could not find a valid date format for: \"{}\"", date);
+ }
return result;
}
/**
* log4j logger
*/
- private static Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorityValue.class);
+ private static Logger log = LogManager.getLogger();
@Override
public String toString() {
@@ -272,6 +283,10 @@ public class AuthorityValue {
return new AuthorityValue();
}
+ /**
+ * Get the type of authority which created this value.
+ * @return type name.
+ */
public String getAuthorityType() {
return "internal";
}
diff --git a/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java b/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java
index 20baef8f9c..b42bd130b7 100644
--- a/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java
+++ b/dspace-api/src/main/java/org/dspace/authority/indexer/DSpaceAuthorityIndexer.java
@@ -80,7 +80,15 @@ public class DSpaceAuthorityIndexer implements AuthorityIndexerInterface, Initia
throws SQLException, AuthorizeException {
List values = new ArrayList<>();
for (String metadataField : metadataFields) {
- List metadataValues = itemService.getMetadataByMetadataString(item, metadataField);
+
+ String[] fieldParts = metadataField.split("\\.");
+ String schema = (fieldParts.length > 0 ? fieldParts[0] : null);
+ String element = (fieldParts.length > 1 ? fieldParts[1] : null);
+ String qualifier = (fieldParts.length > 2 ? fieldParts[2] : null);
+
+ // Get metadata values without virtual metadata
+ List metadataValues = itemService.getMetadata(item, schema, element, qualifier, Item.ANY,
+ false);
for (MetadataValue metadataValue : metadataValues) {
String content = metadataValue.getValue();
String authorityKey = metadataValue.getAuthority();
diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
index fc438c234c..beff6fdc48 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
@@ -451,7 +451,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
if (e == null) {
return false; // anonymous users can't be admins....
} else {
- return groupService.isMember(c, e, Group.ADMIN);
+ return groupService.isMember(c, e, c.getAdminGroup());
}
}
@@ -895,7 +895,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
return true;
}
} catch (SearchServiceException e) {
- log.error("Failed getting getting community/collection admin status for "
+ log.error("Failed getting community/collection admin status for "
+ context.getCurrentUser().getEmail() + " The search error is: " + e.getMessage()
+ " The search resourceType filter was: " + query);
}
diff --git a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java
index 1ce2e55886..ec4cb199ea 100644
--- a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java
+++ b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java
@@ -108,7 +108,7 @@ public class CrossLinks {
} else {
// Exact match, if the key field has no .* wildcard
if (links.containsKey(metadata)) {
- return links.get(key);
+ return links.get(metadata);
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.java b/dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.java
new file mode 100644
index 0000000000..afd74a588d
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.java
@@ -0,0 +1,77 @@
+/**
+ * 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.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/**
+ * Extended version of the DefaultParser. This parser skip/ignore unknown arguments.
+ */
+public class DSpaceSkipUnknownArgumentsParser extends DefaultParser {
+
+
+ @Override
+ public CommandLine parse(Options options, String[] arguments) throws ParseException {
+ return super.parse(options, getOnlyKnownArguments(options, arguments));
+ }
+
+ @Override
+ public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException {
+ return super.parse(options, getOnlyKnownArguments(options, arguments), properties);
+ }
+
+ /**
+ * Parse the arguments according to the specified options and properties.
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param stopAtNonOption can be ignored - an unrecognized argument is ignored, an unrecognized argument doesn't
+ * stop the parsing and doesn't trigger a ParseException
+ *
+ * @return the list of atomic option and value tokens
+ * @throws ParseException if there are any problems encountered while parsing the command line tokens.
+ */
+ @Override
+ public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException {
+ return super.parse(options, getOnlyKnownArguments(options, arguments), stopAtNonOption);
+ }
+
+ /**
+ * Parse the arguments according to the specified options and properties.
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @param stopAtNonOption can be ignored - an unrecognized argument is ignored, an unrecognized argument doesn't
+ * stop the parsing and doesn't trigger a ParseException
+ *
+ * @return the list of atomic option and value tokens
+ * @throws ParseException if there are any problems encountered while parsing the command line tokens.
+ */
+ @Override
+ public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
+ throws ParseException {
+ return super.parse(options, getOnlyKnownArguments(options, arguments), properties, stopAtNonOption);
+ }
+
+
+ private String[] getOnlyKnownArguments(Options options, String[] arguments) {
+ List knownArguments = new ArrayList<>();
+ for (String arg : arguments) {
+ if (options.hasOption(arg)) {
+ knownArguments.add(arg);
+ }
+ }
+ return knownArguments.toArray(new String[0]);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java
index 451a3b7578..5485735a28 100644
--- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java
+++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java
@@ -307,10 +307,18 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport
return collection;
}
+ public void setCollection(Collection collection) {
+ this.collection = collection;
+ }
+
public Community getCommunity() {
return community;
}
+ public void setCommunity(Community community) {
+ this.community = community;
+ }
+
/**
* Get the asset store number where this bitstream is stored
*
diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
index cc89cea33a..e23e5ce2c8 100644
--- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
@@ -276,6 +276,11 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp
//Remove our bitstream from all our bundles
final List bundles = bitstream.getBundles();
for (Bundle bundle : bundles) {
+ authorizeService.authorizeAction(context, bundle, Constants.REMOVE);
+ //We also need to remove the bitstream id when it's set as bundle's primary bitstream
+ if (bitstream.equals(bundle.getPrimaryBitstream())) {
+ bundle.unsetPrimaryBitstreamID();
+ }
bundle.removeBitstream(bitstream);
}
@@ -403,7 +408,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp
@Override
public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLException {
- Pattern pattern = Pattern.compile("^" + bitstream.getName() + ".([^.]+)$");
+ Pattern pattern = getBitstreamNamePattern(bitstream);
for (Bundle bundle : bitstream.getBundles()) {
for (Item item : bundle.getItems()) {
@@ -420,6 +425,13 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp
return null;
}
+ protected Pattern getBitstreamNamePattern(Bitstream bitstream) {
+ if (bitstream.getName() != null) {
+ return Pattern.compile("^" + Pattern.quote(bitstream.getName()) + ".([^.]+)$");
+ }
+ return Pattern.compile("^" + bitstream.getName() + ".([^.]+)$");
+ }
+
@Override
public BitstreamFormat getFormat(Context context, Bitstream bitstream) throws SQLException {
if (bitstream.getBitstreamFormat() == null) {
@@ -446,10 +458,15 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp
@Override
public Bitstream findByIdOrLegacyId(Context context, String id) throws SQLException {
- if (StringUtils.isNumeric(id)) {
- return findByLegacyId(context, Integer.parseInt(id));
- } else {
- return find(context, UUID.fromString(id));
+ try {
+ if (StringUtils.isNumeric(id)) {
+ return findByLegacyId(context, Integer.parseInt(id));
+ } else {
+ return find(context, UUID.fromString(id));
+ }
+ } catch (IllegalArgumentException e) {
+ // Not a valid legacy ID or valid UUID
+ return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/content/Bundle.java b/dspace-api/src/main/java/org/dspace/content/Bundle.java
index 6c62c3dc91..e5cbdb6ff2 100644
--- a/dspace-api/src/main/java/org/dspace/content/Bundle.java
+++ b/dspace-api/src/main/java/org/dspace/content/Bundle.java
@@ -126,7 +126,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport {
* Unset the primary bitstream ID of the bundle
*/
public void unsetPrimaryBitstreamID() {
- primaryBitstream = null;
+ setPrimaryBitstreamID(null);
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java
index 20c43e4bfc..3ba90c8cc2 100644
--- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java
@@ -194,7 +194,6 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement
List defaultBitstreamReadGroups =
authorizeService.getAuthorizedGroups(context, owningCollection,
Constants.DEFAULT_BITSTREAM_READ);
- log.info(defaultBitstreamReadGroups.size());
// If this collection is configured with a DEFAULT_BITSTREAM_READ group, overwrite the READ policy
// inherited from the bundle with this policy.
if (!defaultBitstreamReadGroups.isEmpty()) {
@@ -563,10 +562,15 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement
@Override
public Bundle findByIdOrLegacyId(Context context, String id) throws SQLException {
- if (StringUtils.isNumeric(id)) {
- return findByLegacyId(context, Integer.parseInt(id));
- } else {
- return find(context, UUID.fromString(id));
+ try {
+ if (StringUtils.isNumeric(id)) {
+ return findByLegacyId(context, Integer.parseInt(id));
+ } else {
+ return find(context, UUID.fromString(id));
+ }
+ } catch (IllegalArgumentException e) {
+ // Not a valid legacy ID or valid UUID
+ return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java
index 53b63dbef1..a13c19d46c 100644
--- a/dspace-api/src/main/java/org/dspace/content/Collection.java
+++ b/dspace-api/src/main/java/org/dspace/content/Collection.java
@@ -135,6 +135,9 @@ public class Collection extends DSpaceObject implements DSpaceObjectLegacySuppor
protected void setLogo(Bitstream logo) {
this.logo = logo;
+ if (logo != null) {
+ logo.setCollection(this);
+ }
setModified();
}
diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
index 84ca1692cc..652d2a5f38 100644
--- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
@@ -895,10 +895,15 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i
@Override
public Collection findByIdOrLegacyId(Context context, String id) throws SQLException {
- if (StringUtils.isNumeric(id)) {
- return findByLegacyId(context, Integer.parseInt(id));
- } else {
- return find(context, UUID.fromString(id));
+ try {
+ if (StringUtils.isNumeric(id)) {
+ return findByLegacyId(context, Integer.parseInt(id));
+ } else {
+ return find(context, UUID.fromString(id));
+ }
+ } catch (IllegalArgumentException e) {
+ // Not a valid legacy ID or valid UUID
+ return null;
}
}
@@ -1021,6 +1026,61 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i
return resp;
}
+ @Override
+ public Collection retrieveCollectionWithSubmitByEntityType(Context context, Item item,
+ String entityType) throws SQLException {
+ Collection ownCollection = item.getOwningCollection();
+ return retrieveWithSubmitCollectionByEntityType(context, ownCollection.getCommunities(), entityType);
+ }
+
+ private Collection retrieveWithSubmitCollectionByEntityType(Context context, List communities,
+ String entityType) {
+
+ for (Community community : communities) {
+ Collection collection = retrieveCollectionWithSubmitByCommunityAndEntityType(context, community,
+ entityType);
+ if (collection != null) {
+ return collection;
+ }
+ }
+
+ for (Community community : communities) {
+ List parentCommunities = community.getParentCommunities();
+ Collection collection = retrieveWithSubmitCollectionByEntityType(context, parentCommunities, entityType);
+ if (collection != null) {
+ return collection;
+ }
+ }
+
+ return retrieveCollectionWithSubmitByCommunityAndEntityType(context, null, entityType);
+ }
+
+ @Override
+ public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context context, Community community,
+ String entityType) {
+ context.turnOffAuthorisationSystem();
+ List collections;
+ try {
+ collections = findCollectionsWithSubmit(null, context, community, entityType, 0, 1);
+ } catch (SQLException | SearchServiceException e) {
+ throw new RuntimeException(e);
+ }
+ context.restoreAuthSystemState();
+ if (collections != null && collections.size() > 0) {
+ return collections.get(0);
+ }
+ if (community != null) {
+ for (Community subCommunity : community.getSubcommunities()) {
+ Collection collection = retrieveCollectionWithSubmitByCommunityAndEntityType(context,
+ subCommunity, entityType);
+ if (collection != null) {
+ return collection;
+ }
+ }
+ }
+ return null;
+ }
+
@Override
public List findCollectionsWithSubmit(String q, Context context, Community community, String entityType,
int offset, int limit) throws SQLException, SearchServiceException {
diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java
index dd6d978936..d82e08bab7 100644
--- a/dspace-api/src/main/java/org/dspace/content/Community.java
+++ b/dspace-api/src/main/java/org/dspace/content/Community.java
@@ -123,6 +123,9 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport
void setLogo(Bitstream logo) {
this.logo = logo;
+ if (logo != null) {
+ logo.setCommunity(this);
+ }
setModified();
}
diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java
index 15ac1c58a6..045adc229e 100644
--- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java
@@ -694,10 +694,15 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp
@Override
public Community findByIdOrLegacyId(Context context, String id) throws SQLException {
- if (StringUtils.isNumeric(id)) {
- return findByLegacyId(context, Integer.parseInt(id));
- } else {
- return find(context, UUID.fromString(id));
+ try {
+ if (StringUtils.isNumeric(id)) {
+ return findByLegacyId(context, Integer.parseInt(id));
+ } else {
+ return find(context, UUID.fromString(id));
+ }
+ } catch (IllegalArgumentException e) {
+ // Not a valid legacy ID or valid UUID
+ return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java
index 32c5b92c60..89e42c182b 100644
--- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java
@@ -93,7 +93,7 @@ public class InstallItemServiceImpl implements InstallItemService {
// As this is a BRAND NEW item, as a final step we need to remove the
// submitter item policies created during deposit and replace them with
// the default policies from the collection.
- itemService.inheritCollectionDefaultPolicies(c, item, collection);
+ itemService.inheritCollectionDefaultPolicies(c, item, collection, false);
return item;
}
@@ -150,7 +150,6 @@ public class InstallItemServiceImpl implements InstallItemService {
return finishItem(c, item, is);
}
-
protected void populateMetadata(Context c, Item item)
throws SQLException, AuthorizeException {
// create accession date
@@ -158,15 +157,6 @@ public class InstallItemServiceImpl implements InstallItemService {
itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
"date", "accessioned", null, now.toString());
- // add date available if not under embargo, otherwise it will
- // be set when the embargo is lifted.
- // this will flush out fatal embargo metadata
- // problems before we set inArchive.
- if (embargoService.getEmbargoTermsAsDate(c, item) == null) {
- itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
- "date", "available", null, now.toString());
- }
-
// If issue date is set as "today" (literal string), then set it to current date
// In the below loop, we temporarily clear all issued dates and re-add, one-by-one,
// replacing "today" with today's date.
@@ -271,4 +261,28 @@ public class InstallItemServiceImpl implements InstallItemService {
return myMessage.toString();
}
+
+ @Override
+ public String getSubmittedByProvenanceMessage(Context context, Item item) throws SQLException {
+ // get date
+ DCDate now = DCDate.getCurrent();
+
+ // Create provenance description
+ StringBuffer provmessage = new StringBuffer();
+
+ if (item.getSubmitter() != null) {
+ provmessage.append("Submitted by ").append(item.getSubmitter().getFullName())
+ .append(" (").append(item.getSubmitter().getEmail()).append(") on ")
+ .append(now.toString());
+ } else {
+ // else, null submitter
+ provmessage.append("Submitted by unknown (probably automated) on")
+ .append(now.toString());
+ }
+ provmessage.append("\n");
+
+ // add sizes and checksums of bitstreams
+ provmessage.append(getBitstreamProvenanceMessage(context, item));
+ return provmessage.toString();
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
index 8d1ba14b2c..9791f69abb 100644
--- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
@@ -77,6 +77,7 @@ import org.dspace.orcid.service.OrcidQueueService;
import org.dspace.orcid.service.OrcidSynchronizationService;
import org.dspace.orcid.service.OrcidTokenService;
import org.dspace.profile.service.ResearcherProfileService;
+import org.dspace.qaevent.dao.QAEventsDAO;
import org.dspace.services.ConfigurationService;
import org.dspace.versioning.service.VersioningService;
import org.dspace.workflow.WorkflowItemService;
@@ -170,6 +171,9 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl- implements It
@Autowired(required = true)
protected SubscribeService subscribeService;
+ @Autowired
+ private QAEventsDAO qaEventsDao;
+
protected ItemServiceImpl() {
super();
}
@@ -819,6 +823,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
orcidToken.setProfileItem(null);
}
+ List qaEvents = qaEventsDao.findByItem(context, item);
+ for (QAEventProcessed qaEvent : qaEvents) {
+ qaEventsDao.delete(context, qaEvent);
+ }
+
//Only clear collections after we have removed everything else from the item
item.clearCollections();
item.setOwningCollection(null);
@@ -920,8 +929,16 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
@Override
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection)
throws SQLException, AuthorizeException {
- adjustItemPolicies(context, item, collection);
- adjustBundleBitstreamPolicies(context, item, collection);
+ inheritCollectionDefaultPolicies(context, item, collection, true);
+ }
+
+ @Override
+ public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection,
+ boolean replaceReadRPWithCollectionRP)
+ throws SQLException, AuthorizeException {
+
+ adjustItemPolicies(context, item, collection, replaceReadRPWithCollectionRP);
+ adjustBundleBitstreamPolicies(context, item, collection, replaceReadRPWithCollectionRP);
log.debug(LogHelper.getHeader(context, "item_inheritCollectionDefaultPolicies",
"item_id=" + item.getID()));
@@ -930,6 +947,13 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
@Override
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection)
throws SQLException, AuthorizeException {
+ adjustBundleBitstreamPolicies(context, item, collection, true);
+ }
+
+ @Override
+ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection,
+ boolean replaceReadRPWithCollectionRP)
+ throws SQLException, AuthorizeException {
// Bundles should inherit from DEFAULT_ITEM_READ so that if the item is readable, the files
// can be listed (even if they are themselves not readable as per DEFAULT_BITSTREAM_READ or other
// policies or embargos applied
@@ -948,10 +972,19 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
}
// TODO: should we also throw an exception if no DEFAULT_ITEM_READ?
+ boolean removeCurrentReadRPBitstream =
+ replaceReadRPWithCollectionRP && defaultCollectionBitstreamPolicies.size() > 0;
+ boolean removeCurrentReadRPBundle =
+ replaceReadRPWithCollectionRP && defaultCollectionBundlePolicies.size() > 0;
+
// remove all policies from bundles, add new ones
// Remove bundles
List bunds = item.getBundles();
for (Bundle mybundle : bunds) {
+ // If collection has default READ policies, remove the bundle's READ policies.
+ if (removeCurrentReadRPBundle) {
+ authorizeService.removePoliciesActionFilter(context, mybundle, Constants.READ);
+ }
// if come from InstallItem: remove all submission/workflow policies
authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_SUBMISSION);
@@ -960,6 +993,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionBundlePolicies);
for (Bitstream bitstream : mybundle.getBitstreams()) {
+ // If collection has default READ policies, remove the bundle's READ policies.
+ if (removeCurrentReadRPBitstream) {
+ authorizeService.removePoliciesActionFilter(context, bitstream, Constants.READ);
+ }
+
// if come from InstallItem: remove all submission/workflow policies
removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies,
defaultCollectionBitstreamPolicies);
@@ -968,7 +1006,14 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
}
@Override
- public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream)
+ public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream)
+ throws SQLException, AuthorizeException {
+ adjustBitstreamPolicies(context, item, collection, bitstream, true);
+ }
+
+ @Override
+ public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream,
+ boolean replaceReadRPWithCollectionRP)
throws SQLException, AuthorizeException {
List defaultCollectionPolicies = authorizeService
.getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ);
@@ -998,10 +1043,22 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
@Override
public void adjustItemPolicies(Context context, Item item, Collection collection)
throws SQLException, AuthorizeException {
+ adjustItemPolicies(context, item, collection, true);
+ }
+
+ @Override
+ public void adjustItemPolicies(Context context, Item item, Collection collection,
+ boolean replaceReadRPWithCollectionRP)
+ throws SQLException, AuthorizeException {
// read collection's default READ policies
List defaultCollectionPolicies = authorizeService
.getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ);
+ // If collection has defaultREAD policies, remove the item's READ policies.
+ if (replaceReadRPWithCollectionRP && defaultCollectionPolicies.size() > 0) {
+ authorizeService.removePoliciesActionFilter(context, item, Constants.READ);
+ }
+
// MUST have default policies
if (defaultCollectionPolicies.size() < 1) {
throw new SQLException("Collection " + collection.getID()
@@ -1378,16 +1435,6 @@ prevent the generation of resource policy entry values with null dspace_object a
}
}
- @Override
- public Iterator
- findByMetadataQuery(Context context, List
> listFieldList,
- List query_op, List query_val, List collectionUuids,
- String regexClause, int offset, int limit)
- throws SQLException, AuthorizeException, IOException {
- return itemDAO
- .findByMetadataQuery(context, listFieldList, query_op, query_val, collectionUuids, regexClause, offset,
- limit);
- }
-
@Override
public DSpaceObject getAdminObject(Context context, Item item, int action) throws SQLException {
DSpaceObject adminObject = null;
@@ -1561,10 +1608,15 @@ prevent the generation of resource policy entry values with null dspace_object a
@Override
public Item findByIdOrLegacyId(Context context, String id) throws SQLException {
- if (StringUtils.isNumeric(id)) {
- return findByLegacyId(context, Integer.parseInt(id));
- } else {
- return find(context, UUID.fromString(id));
+ try {
+ if (StringUtils.isNumeric(id)) {
+ return findByLegacyId(context, Integer.parseInt(id));
+ } else {
+ return find(context, UUID.fromString(id));
+ }
+ } catch (IllegalArgumentException e) {
+ // Not a valid legacy ID or valid UUID
+ return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java
new file mode 100644
index 0000000000..9e90f81be3
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java
@@ -0,0 +1,213 @@
+/**
+ * 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;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
+import org.dspace.qaevent.service.dto.QAMessageDTO;
+import org.dspace.util.RawJsonDeserializer;
+
+/**
+ * This class represent the Quality Assurance broker data as loaded in our solr
+ * qaevent core
+ *
+ */
+public class QAEvent {
+ public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
+ 'f' };
+ public static final String ACCEPTED = "accepted";
+ public static final String REJECTED = "rejected";
+ public static final String DISCARDED = "discarded";
+
+ public static final String OPENAIRE_SOURCE = "openaire";
+
+ private String source;
+
+ private String eventId;
+ /**
+ * contains the targeted dspace object,
+ * ie: oai:www.openstarts.units.it:123456789/1120 contains the handle
+ * of the DSpace pbject in its final part 123456789/1120
+ * */
+ private String originalId;
+
+ /**
+ * evaluated with the targeted dspace object id
+ *
+ * */
+ private String target;
+
+ private String related;
+
+ private String title;
+
+ private String topic;
+
+ private double trust;
+
+ @JsonDeserialize(using = RawJsonDeserializer.class)
+ private String message;
+
+ private Date lastUpdate;
+
+ private String status = "PENDING";
+
+ public QAEvent() {
+ }
+
+ public QAEvent(String source, String originalId, String target, String title,
+ String topic, double trust, String message, Date lastUpdate) {
+ super();
+ this.source = source;
+ this.originalId = originalId;
+ this.target = target;
+ this.title = title;
+ this.topic = topic;
+ this.trust = trust;
+ this.message = message;
+ this.lastUpdate = lastUpdate;
+ try {
+ computedEventId();
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
+
+ }
+
+ public String getOriginalId() {
+ return originalId;
+ }
+
+ public void setOriginalId(String originalId) {
+ this.originalId = originalId;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public void setTopic(String topic) {
+ this.topic = topic;
+ }
+
+ public double getTrust() {
+ return trust;
+ }
+
+ public void setTrust(double trust) {
+ this.trust = trust;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getEventId() {
+ if (eventId == null) {
+ try {
+ computedEventId();
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return eventId;
+ }
+
+ public void setEventId(String eventId) {
+ this.eventId = eventId;
+ }
+
+ public String getTarget() {
+ return target;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ public Date getLastUpdate() {
+ return lastUpdate;
+ }
+
+ public void setLastUpdate(Date lastUpdate) {
+ this.lastUpdate = lastUpdate;
+ }
+
+ public void setRelated(String related) {
+ this.related = related;
+ }
+
+ public String getRelated() {
+ return related;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getSource() {
+ return source != null ? source : OPENAIRE_SOURCE;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ /*
+ * DTO constructed via Jackson use empty constructor. In this case, the eventId
+ * must be compute on the get method. This method create a signature based on
+ * the event fields and store it in the eventid attribute.
+ */
+ private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncodingException {
+ MessageDigest digester = MessageDigest.getInstance("MD5");
+ String dataToString = "source=" + source + ",originalId=" + originalId + ", title=" + title + ", topic="
+ + topic + ", trust=" + trust + ", message=" + message;
+ digester.update(dataToString.getBytes("UTF-8"));
+ byte[] signature = digester.digest();
+ char[] arr = new char[signature.length << 1];
+ for (int i = 0; i < signature.length; i++) {
+ int b = signature[i];
+ int idx = i << 1;
+ arr[idx] = HEX_DIGITS[(b >> 4) & 0xf];
+ arr[idx + 1] = HEX_DIGITS[b & 0xf];
+ }
+ eventId = new String(arr);
+
+ }
+
+ public Class extends QAMessageDTO> getMessageDtoClass() {
+ switch (getSource()) {
+ case OPENAIRE_SOURCE:
+ return OpenaireMessageDTO.class;
+ default:
+ throw new IllegalArgumentException("Unknown event's source: " + getSource());
+ }
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java
new file mode 100644
index 0000000000..3631a2ff68
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java
@@ -0,0 +1,82 @@
+/**
+ * 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;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.dspace.eperson.EPerson;
+
+/**
+ * This class represent the stored information about processed notification
+ * broker events
+ *
+ */
+@Entity
+@Table(name = "qaevent_processed")
+public class QAEventProcessed implements Serializable {
+
+ private static final long serialVersionUID = 3427340199132007814L;
+
+ @Id
+ @Column(name = "qaevent_id")
+ private String eventId;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "qaevent_timestamp")
+ private Date eventTimestamp;
+
+ @JoinColumn(name = "eperson_uuid")
+ @ManyToOne
+ private EPerson eperson;
+
+ @JoinColumn(name = "item_uuid")
+ @ManyToOne
+ private Item item;
+
+ public String getEventId() {
+ return eventId;
+ }
+
+ public void setEventId(String eventId) {
+ this.eventId = eventId;
+ }
+
+ public Date getEventTimestamp() {
+ return eventTimestamp;
+ }
+
+ public void setEventTimestamp(Date eventTimestamp) {
+ this.eventTimestamp = eventTimestamp;
+ }
+
+ public EPerson getEperson() {
+ return eperson;
+ }
+
+ public void setEperson(EPerson eperson) {
+ this.eperson = eperson;
+ }
+
+ public Item getItem() {
+ return item;
+ }
+
+ public void setItem(Item item) {
+ this.item = item;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
index ec8f8769be..f4d1f02710 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
@@ -17,6 +17,7 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.DCInput;
@@ -24,7 +25,6 @@ import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.app.util.SubmissionConfig;
-import org.dspace.app.util.SubmissionConfigReader;
import org.dspace.app.util.SubmissionConfigReaderException;
import org.dspace.content.Collection;
import org.dspace.content.MetadataValue;
@@ -34,6 +34,8 @@ import org.dspace.core.service.PluginService;
import org.dspace.discovery.configuration.DiscoveryConfigurationService;
import org.dspace.discovery.configuration.DiscoverySearchFilterFacet;
import org.dspace.services.ConfigurationService;
+import org.dspace.submit.factory.SubmissionServiceFactory;
+import org.dspace.submit.service.SubmissionConfigService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -87,7 +89,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
protected Map vocabularyIndexMap = new HashMap<>();
// the item submission reader
- private SubmissionConfigReader itemSubmissionConfigReader;
+ private SubmissionConfigService submissionConfigService;
@Autowired(required = true)
protected ConfigurationService configurationService;
@@ -134,7 +136,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
private synchronized void init() {
if (!initialized) {
try {
- itemSubmissionConfigReader = new SubmissionConfigReader();
+ submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService();
} catch (SubmissionConfigReaderException e) {
// the system is in an illegal state as the submission definition is not valid
throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
@@ -239,8 +241,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
// there is an authority configured for the metadata valid for some collections,
// check if it is the requested collection
Map controllerFormDef = controllerFormDefinitions.get(fieldKey);
- SubmissionConfig submissionConfig = itemSubmissionConfigReader
- .getSubmissionConfigByCollection(collection.getHandle());
+ SubmissionConfig submissionConfig = submissionConfigService
+ .getSubmissionConfigByCollection(collection);
String submissionName = submissionConfig.getSubmissionName();
// check if the requested collection has a submission definition that use an authority for the metadata
if (controllerFormDef.containsKey(submissionName)) {
@@ -261,14 +263,14 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
}
@Override
- public void clearCache() {
+ public void clearCache() throws SubmissionConfigReaderException {
controller.clear();
authorities.clear();
presentation.clear();
closed.clear();
controllerFormDefinitions.clear();
authoritiesFormDefinitions.clear();
- itemSubmissionConfigReader = null;
+ submissionConfigService.reload();
initialized = false;
}
@@ -318,7 +320,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
*/
private void autoRegisterChoiceAuthorityFromInputReader() {
try {
- List submissionConfigs = itemSubmissionConfigReader
+ List submissionConfigs = submissionConfigService
.getAllSubmissionConfigs(Integer.MAX_VALUE, 0);
DCInputsReader dcInputsReader = new DCInputsReader();
@@ -489,10 +491,11 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
init();
ChoiceAuthority ma = controller.get(fieldKey);
if (ma == null && collection != null) {
- SubmissionConfigReader configReader;
+ SubmissionConfigService configReaderService;
try {
- configReader = new SubmissionConfigReader();
- SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle());
+ configReaderService = SubmissionServiceFactory.getInstance().getSubmissionConfigService();
+ SubmissionConfig submissionName = configReaderService
+ .getSubmissionConfigByCollection(collection);
ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName());
} catch (SubmissionConfigReaderException e) {
// the system is in an illegal state as the submission definition is not valid
@@ -557,6 +560,15 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
init();
ChoiceAuthority source = this.getChoiceAuthorityByAuthorityName(nameVocab);
if (source != null && source instanceof DSpaceControlledVocabulary) {
+
+ // First, check if this vocabulary index is disabled
+ String[] vocabulariesDisabled = configurationService
+ .getArrayProperty("webui.browse.vocabularies.disabled");
+ if (vocabulariesDisabled != null && ArrayUtils.contains(vocabulariesDisabled, nameVocab)) {
+ // Discard this vocabulary browse index
+ return null;
+ }
+
Set metadataFields = new HashSet<>();
Map> formsToFields = this.authoritiesFormDefinitions.get(nameVocab);
for (Map.Entry> formToField : formsToFields.entrySet()) {
@@ -585,6 +597,12 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
break;
}
}
+
+ // If there is no matching facet, return null to ignore this vocabulary index
+ if (matchingFacet == null) {
+ return null;
+ }
+
DSpaceControlledVocabularyIndex vocabularyIndex =
new DSpaceControlledVocabularyIndex((DSpaceControlledVocabulary) source, metadataFields,
matchingFacet);
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
index b1d8cf36a5..902bded33e 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
@@ -156,7 +156,8 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
int found = 0;
List v = new ArrayList();
for (int i = 0; i < valuesLocale.length; ++i) {
- if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) {
+ // In a DCInputAuthority context, a user will want to query the labels, not the values
+ if (query == null || StringUtils.containsIgnoreCase(labelsLocale[i], query)) {
if (found >= start && v.size() < limit) {
v.add(new Choice(null, valuesLocale[i], labelsLocale[i]));
if (valuesLocale[i].equalsIgnoreCase(query)) {
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java
index da5c743c5b..418000e51e 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOJournalTitle.java
@@ -59,7 +59,37 @@ public class SHERPARoMEOJournalTitle implements ChoiceAuthority {
@Override
public Choices getBestMatch(String text, String locale) {
- return getMatches(text, 0, 1, locale);
+ // punt if there is no query text
+ if (text == null || text.trim().length() == 0) {
+ return new Choices(true);
+ }
+ int limit = 10;
+ SHERPAService sherpaService = new DSpace().getSingletonService(SHERPAService.class);
+ SHERPAResponse sherpaResponse = sherpaService.performRequest("publication", "title",
+ "equals", text, 0, limit);
+ Choices result;
+ if (CollectionUtils.isNotEmpty(sherpaResponse.getJournals())) {
+ List list = sherpaResponse
+ .getJournals().stream()
+ .map(sherpaJournal -> new Choice(sherpaJournal.getIssns().get(0),
+ sherpaJournal.getTitles().get(0), sherpaJournal.getTitles().get(0)))
+ .collect(Collectors.toList());
+ int total = sherpaResponse.getJournals().size();
+
+ int confidence;
+ if (list.isEmpty()) {
+ confidence = Choices.CF_NOTFOUND;
+ } else if (list.size() == 1) {
+ confidence = Choices.CF_UNCERTAIN;
+ } else {
+ confidence = Choices.CF_AMBIGUOUS;
+ }
+ result = new Choices(list.toArray(new Choice[list.size()]), 0, total, confidence,
+ total > limit);
+ } else {
+ result = new Choices(false);
+ }
+ return result;
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java
index 0f93dff498..d1001ce5db 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/SHERPARoMEOPublisher.java
@@ -60,7 +60,38 @@ public class SHERPARoMEOPublisher implements ChoiceAuthority {
@Override
public Choices getBestMatch(String text, String locale) {
- return getMatches(text, 0, 1, locale);
+ // punt if there is no query text
+ if (text == null || text.trim().length() == 0) {
+ return new Choices(true);
+ }
+ int limit = 10;
+ SHERPAService sherpaService = new DSpace().getSingletonService(SHERPAService.class);
+ SHERPAPublisherResponse sherpaResponse = sherpaService.performPublisherRequest("publisher", "name",
+ "equals", text, 0, limit);
+ Choices result;
+ if (CollectionUtils.isNotEmpty(sherpaResponse.getPublishers())) {
+ List list = sherpaResponse
+ .getPublishers().stream()
+ .map(sherpaPublisher ->
+ new Choice(sherpaPublisher.getIdentifier(),
+ sherpaPublisher.getName(), sherpaPublisher.getName()))
+ .collect(Collectors.toList());
+ int total = sherpaResponse.getPublishers().size();
+
+ int confidence;
+ if (list.isEmpty()) {
+ confidence = Choices.CF_NOTFOUND;
+ } else if (list.size() == 1) {
+ confidence = Choices.CF_UNCERTAIN;
+ } else {
+ confidence = Choices.CF_AMBIGUOUS;
+ }
+ result = new Choices(list.toArray(new Choice[list.size()]), 0, total, confidence,
+ total > limit);
+ } else {
+ result = new Choices(false);
+ }
+ return result;
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java
index a9fd24e947..94e5ca57a0 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java
@@ -10,6 +10,7 @@ package org.dspace.content.authority.service;
import java.util.List;
import java.util.Set;
+import org.dspace.app.util.SubmissionConfigReaderException;
import org.dspace.content.Collection;
import org.dspace.content.MetadataValue;
import org.dspace.content.authority.Choice;
@@ -174,7 +175,7 @@ public interface ChoiceAuthorityService {
/**
* This method has been created to have a way of clearing the cache kept inside the service
*/
- public void clearCache();
+ public void clearCache() throws SubmissionConfigReaderException;
/**
* Should we store the authority key (if any) for such field key and collection?
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
index 86da51e6cc..49d3527a35 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
@@ -11,7 +11,6 @@ import java.sql.SQLException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
-import java.util.UUID;
import org.dspace.content.Collection;
import org.dspace.content.Community;
@@ -80,10 +79,6 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO- {
public Iterator
- findByMetadataField(Context context, MetadataField metadataField, String value,
boolean inArchive) throws SQLException;
- public Iterator
- findByMetadataQuery(Context context, List
> listFieldList,
- List query_op, List query_val, List collectionUuids,
- String regexClause, int offset, int limit) throws SQLException;
-
public Iterator- findByAuthorityValue(Context context, MetadataField metadataField, String authority,
boolean inArchive) throws SQLException;
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
index d6d77fe7f0..0e051625aa 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
@@ -68,9 +68,9 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO impleme
@Override
public List findBitstreamsWithNoRecentChecksum(Context context) throws SQLException {
- Query query = createQuery(context,
- "select b from Bitstream b where b not in (select c.bitstream from " +
- "MostRecentChecksum c)");
+ Query query = createQuery(context, "SELECT b FROM MostRecentChecksum c RIGHT JOIN Bitstream b " +
+ "ON c.bitstream = b WHERE c IS NULL" );
+
return query.getResultList();
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
index aad8cf3c50..5c840f68e9 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
@@ -12,7 +12,6 @@ import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
-import java.util.UUID;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.criteria.CriteriaBuilder;
@@ -24,20 +23,10 @@ import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.Item_;
import org.dspace.content.MetadataField;
-import org.dspace.content.MetadataValue;
import org.dspace.content.dao.ItemDAO;
import org.dspace.core.AbstractHibernateDSODAO;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
-import org.hibernate.Criteria;
-import org.hibernate.criterion.Criterion;
-import org.hibernate.criterion.DetachedCriteria;
-import org.hibernate.criterion.Order;
-import org.hibernate.criterion.Projections;
-import org.hibernate.criterion.Property;
-import org.hibernate.criterion.Restrictions;
-import org.hibernate.criterion.Subqueries;
-import org.hibernate.type.StandardBasicTypes;
/**
* Hibernate implementation of the Database Access Object interface class for the Item object.
@@ -174,120 +163,6 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO
- implements ItemDA
return iterate(query);
}
- enum OP {
- equals {
- public Criterion buildPredicate(String val, String regexClause) {
- return Property.forName("mv.value").eq(val);
- }
- },
- not_equals {
- public Criterion buildPredicate(String val, String regexClause) {
- return OP.equals.buildPredicate(val, regexClause);
- }
- },
- like {
- public Criterion buildPredicate(String val, String regexClause) {
- return Property.forName("mv.value").like(val);
- }
- },
- not_like {
- public Criterion buildPredicate(String val, String regexClause) {
- return OP.like.buildPredicate(val, regexClause);
- }
- },
- contains {
- public Criterion buildPredicate(String val, String regexClause) {
- return Property.forName("mv.value").like("%" + val + "%");
- }
- },
- doesnt_contain {
- public Criterion buildPredicate(String val, String regexClause) {
- return OP.contains.buildPredicate(val, regexClause);
- }
- },
- exists {
- public Criterion buildPredicate(String val, String regexClause) {
- return Property.forName("mv.value").isNotNull();
- }
- },
- doesnt_exist {
- public Criterion buildPredicate(String val, String regexClause) {
- return OP.exists.buildPredicate(val, regexClause);
- }
- },
- matches {
- public Criterion buildPredicate(String val, String regexClause) {
- return Restrictions.sqlRestriction(regexClause, val, StandardBasicTypes.STRING);
- }
- },
- doesnt_match {
- public Criterion buildPredicate(String val, String regexClause) {
- return OP.matches.buildPredicate(val, regexClause);
- }
-
- };
- public abstract Criterion buildPredicate(String val, String regexClause);
- }
-
- @Override
- @Deprecated
- public Iterator
- findByMetadataQuery(Context context, List
> listFieldList,
- List query_op, List query_val, List collectionUuids,
- String regexClause, int offset, int limit) throws SQLException {
-
- Criteria criteria = getHibernateSession(context).createCriteria(Item.class, "item");
- criteria.setFirstResult(offset);
- criteria.setMaxResults(limit);
-
- if (!collectionUuids.isEmpty()) {
- DetachedCriteria dcollCriteria = DetachedCriteria.forClass(Collection.class, "coll");
- dcollCriteria.setProjection(Projections.property("coll.id"));
- dcollCriteria.add(Restrictions.eqProperty("coll.id", "item.owningCollection"));
- dcollCriteria.add(Restrictions.in("coll.id", collectionUuids));
- criteria.add(Subqueries.exists(dcollCriteria));
- }
-
- int index = Math.min(listFieldList.size(), Math.min(query_op.size(), query_val.size()));
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < index; i++) {
- OP op = OP.valueOf(query_op.get(i));
- if (op == null) {
- log.warn("Skipping Invalid Operator: " + query_op.get(i));
- continue;
- }
-
- if (op == OP.matches || op == OP.doesnt_match) {
- if (regexClause.isEmpty()) {
- log.warn("Skipping Unsupported Regex Operator: " + query_op.get(i));
- continue;
- }
- }
-
- DetachedCriteria subcriteria = DetachedCriteria.forClass(MetadataValue.class, "mv");
- subcriteria.add(Property.forName("mv.dSpaceObject").eqProperty("item.id"));
- subcriteria.setProjection(Projections.property("mv.dSpaceObject"));
-
- if (!listFieldList.get(i).isEmpty()) {
- subcriteria.add(Restrictions.in("metadataField", listFieldList.get(i)));
- }
-
- subcriteria.add(op.buildPredicate(query_val.get(i), regexClause));
-
- if (op == OP.exists || op == OP.equals || op == OP.like || op == OP.contains || op == OP.matches) {
- criteria.add(Subqueries.exists(subcriteria));
- } else {
- criteria.add(Subqueries.notExists(subcriteria));
- }
- }
- criteria.addOrder(Order.asc("item.id"));
-
- log.debug(String.format("Running custom query with %d filters", index));
-
- return ((List- ) criteria.list()).iterator();
-
- }
-
@Override
public Iterator
- findByAuthorityValue(Context context, MetadataField metadataField, String authority,
boolean inArchive) 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 90db5c7314..0a56105ead 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
@@ -417,6 +417,34 @@ public interface CollectionService
public List findCollectionsWithSubmit(String q, Context context, Community community,
int offset, int limit) throws SQLException, SearchServiceException;
+ /**
+ * Retrieve the first collection in the community or its descending that support
+ * the provided entityType
+ *
+ * @param context the DSpace context
+ * @param community the root from where the search start
+ * @param entityType the requested entity type
+ * @return the first collection in the community or its descending
+ * that support the provided entityType
+ */
+ public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context context, Community community,
+ String entityType);
+
+ /**
+ * Retrieve the close collection to the item for which the current user has
+ * 'submit' privileges that support the provided entityType. Close mean the
+ * collection that can be reach with the minimum steps starting from the item
+ * (owningCollection, brothers collections, etc)
+ *
+ * @param context the DSpace context
+ * @param item the item from where the search start
+ * @param entityType the requested entity type
+ * @return the first collection in the community or its descending
+ * that support the provided entityType
+ */
+ public Collection retrieveCollectionWithSubmitByEntityType(Context context, Item item, String entityType)
+ throws SQLException;
+
/**
* Counts the number of Collection for which the current user has 'submit' privileges.
* NOTE: for better performance, this method retrieves its results from an index (cache)
diff --git a/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java b/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java
index 67ac2e2049..d00c62cc91 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java
@@ -83,4 +83,15 @@ public interface InstallItemService {
public String getBitstreamProvenanceMessage(Context context, Item myitem)
throws SQLException;
+ /**
+ * Generate provenance description of direct item submission (not through workflow).
+ *
+ * @param context context
+ * @param item the item to generate description for
+ * @return provenance description
+ * @throws SQLException if database error
+ */
+ public String getSubmittedByProvenanceMessage(Context context, Item item)
+ throws SQLException;;
+
}
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 b6bf7aa5cf..43a804cde2 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
@@ -23,7 +23,6 @@ import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.EntityType;
import org.dspace.content.Item;
-import org.dspace.content.MetadataField;
import org.dspace.content.MetadataValue;
import org.dspace.content.Thumbnail;
import org.dspace.content.WorkspaceItem;
@@ -473,7 +472,7 @@ public interface ItemService
public void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException;
/**
- * remove all policies on an item and its contents, and replace them with
+ * Remove all policies on an item and its contents, and replace them with
* the DEFAULT_ITEM_READ and DEFAULT_BITSTREAM_READ policies belonging to
* the collection.
*
@@ -488,6 +487,26 @@ public interface ItemService
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection)
throws java.sql.SQLException, AuthorizeException;
+ /**
+ * Remove all submission and workflow policies on an item and its contents, and add
+ * default collection policies which are not yet already in place.
+ * If overrideItemReadPolicies is true, then all read policies on the item are replaced (but only if the
+ * collection has a default read policy).
+ *
+ * @param context DSpace context object
+ * @param item item to reset policies on
+ * @param collection Collection
+ * @param overrideItemReadPolicies if true, all read policies on the item are replaced (but only if the
+ * collection has a default read policy)
+ * @throws SQLException if database error
+ * if an SQL error or if no default policies found. It's a bit
+ * draconian, but default policies must be enforced.
+ * @throws AuthorizeException if authorization error
+ */
+ public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection,
+ boolean overrideItemReadPolicies)
+ throws java.sql.SQLException, AuthorizeException;
+
/**
* Adjust the Bundle and Bitstream policies to reflect what have been defined
* during the submission/workflow. The temporary SUBMISSION and WORKFLOW
@@ -507,6 +526,28 @@ public interface ItemService
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection)
throws SQLException, AuthorizeException;
+ /**
+ * Adjust the Bundle and Bitstream policies to reflect what have been defined
+ * during the submission/workflow. The temporary SUBMISSION and WORKFLOW
+ * policies are removed and the policies defined at the item and collection
+ * level are copied and inherited as appropriate. Custom selected Item policies
+ * are copied to the bundle/bitstream only if no explicit custom policies were
+ * already applied to the bundle/bitstream. Collection's policies are inherited
+ * if there are no other policies defined or if the append mode is defined by
+ * the configuration via the core.authorization.installitem.inheritance-read.append-mode property
+ *
+ * @param context DSpace context object
+ * @param item Item to adjust policies on
+ * @param collection Collection
+ * @param replaceReadRPWithCollectionRP if true, all read policies on the item are replaced (but only if the
+ * collection has a default read policy)
+ * @throws SQLException If database error
+ * @throws AuthorizeException If authorization error
+ */
+ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection,
+ boolean replaceReadRPWithCollectionRP)
+ throws SQLException, AuthorizeException;
+
/**
* Adjust the Bitstream policies to reflect what have been defined
* during the submission/workflow. The temporary SUBMISSION and WORKFLOW
@@ -527,6 +568,29 @@ public interface ItemService
public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream)
throws SQLException, AuthorizeException;
+ /**
+ * Adjust the Bitstream policies to reflect what have been defined
+ * during the submission/workflow. The temporary SUBMISSION and WORKFLOW
+ * policies are removed and the policies defined at the item and collection
+ * level are copied and inherited as appropriate. Custom selected Item policies
+ * are copied to the bitstream only if no explicit custom policies were
+ * already applied to the bitstream. Collection's policies are inherited
+ * if there are no other policies defined or if the append mode is defined by
+ * the configuration via the core.authorization.installitem.inheritance-read.append-mode property
+ *
+ * @param context DSpace context object
+ * @param item Item to adjust policies on
+ * @param collection Collection
+ * @param bitstream Bitstream to adjust policies on
+ * @param replaceReadRPWithCollectionRP If true, all read policies on the bitstream are replaced (but only if the
+ * collection has a default read policy)
+ * @throws SQLException If database error
+ * @throws AuthorizeException If authorization error
+ */
+ public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream,
+ boolean replaceReadRPWithCollectionRP)
+ throws SQLException, AuthorizeException;
+
/**
* Adjust the Item's policies to reflect what have been defined during the
@@ -545,6 +609,26 @@ public interface ItemService
public void adjustItemPolicies(Context context, Item item, Collection collection)
throws SQLException, AuthorizeException;
+ /**
+ * Adjust the Item's policies to reflect what have been defined during the
+ * submission/workflow. The temporary SUBMISSION and WORKFLOW policies are
+ * removed and the default policies defined at the collection level are
+ * inherited as appropriate. Collection's policies are inherited if there are no
+ * other policies defined or if the append mode is defined by the configuration
+ * via the core.authorization.installitem.inheritance-read.append-mode property
+ *
+ * @param context DSpace context object
+ * @param item Item to adjust policies on
+ * @param collection Collection
+ * @param replaceReadRPWithCollectionRP If true, all read policies on the item are replaced (but only if the
+ * collection has a default read policy)
+ * @throws SQLException If database error
+ * @throws AuthorizeException If authorization error
+ */
+ public void adjustItemPolicies(Context context, Item item, Collection collection,
+ boolean replaceReadRPWithCollectionRP)
+ throws SQLException, AuthorizeException;
+
/**
* Moves the item from one collection to another one
*
@@ -664,11 +748,6 @@ public interface ItemService
String schema, String element, String qualifier, String value)
throws SQLException, AuthorizeException, IOException;
- public Iterator
- findByMetadataQuery(Context context, List
> listFieldList,
- List query_op, List query_val, List collectionUuids,
- String regexClause, int offset, int limit)
- throws SQLException, AuthorizeException, IOException;
-
/**
* Find all the items in the archive with a given authority key value
* in the indicated metadata field.
@@ -790,24 +869,24 @@ 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
- */
+ * 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
- */
+ * 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;
/**
diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java
index e6535f0941..e9c6b95b7f 100644
--- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java
+++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java
@@ -83,13 +83,14 @@ public abstract class AbstractHibernateDSODAO extends Ab
if (CollectionUtils.isNotEmpty(metadataFields) || StringUtils.isNotBlank(additionalWhere)) {
//Add the where query on metadata
query.append(" WHERE ");
+ // Group the 'OR' clauses below in outer parentheses, e.g. "WHERE (clause1 OR clause2 OR clause3)".
+ // Grouping these 'OR' clauses allows for later code to append 'AND' clauses without unexpected behaviors
+ query.append("(");
for (int i = 0; i < metadataFields.size(); i++) {
MetadataField metadataField = metadataFields.get(i);
if (StringUtils.isNotBlank(operator)) {
- query.append(" (");
query.append("lower(STR(" + metadataField.toString()).append(".value)) ").append(operator)
.append(" lower(:queryParam)");
- query.append(")");
if (i < metadataFields.size() - 1) {
query.append(" OR ");
}
@@ -102,6 +103,7 @@ public abstract class AbstractHibernateDSODAO extends Ab
}
query.append(additionalWhere);
}
+ query.append(")");
}
}
diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java
index 82b39dd2df..02a3fee09f 100644
--- a/dspace-api/src/main/java/org/dspace/core/Context.java
+++ b/dspace-api/src/main/java/org/dspace/core/Context.java
@@ -128,6 +128,11 @@ public class Context implements AutoCloseable {
private DBConnection dbConnection;
+ /**
+ * The default administrator group
+ */
+ private Group adminGroup;
+
public enum Mode {
READ_ONLY,
READ_WRITE,
@@ -810,6 +815,15 @@ public class Context implements AutoCloseable {
readOnlyCache.clear();
}
+ // When going to READ_ONLY, flush database changes to ensure that the current data is retrieved
+ if (newMode == Mode.READ_ONLY && mode != Mode.READ_ONLY) {
+ try {
+ dbConnection.flushSession();
+ } catch (SQLException ex) {
+ log.warn("Unable to flush database changes after switching to READ_ONLY mode", ex);
+ }
+ }
+
//save the new mode
mode = newMode;
}
@@ -951,4 +965,15 @@ public class Context implements AutoCloseable {
public boolean isContextUserSwitched() {
return currentUserPreviousState != null;
}
+
+ /**
+ * Returns the default "Administrator" group for DSpace administrators.
+ * The result is cached in the 'adminGroup' field, so it is only looked up once.
+ * This is done to improve performance, as this method is called quite often.
+ */
+ public Group getAdminGroup() throws SQLException {
+ return (adminGroup == null) ? EPersonServiceFactory.getInstance()
+ .getGroupService()
+ .findByName(this, Group.ADMIN) : adminGroup;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/core/DBConnection.java b/dspace-api/src/main/java/org/dspace/core/DBConnection.java
index cb5825eec1..66e4a65dbf 100644
--- a/dspace-api/src/main/java/org/dspace/core/DBConnection.java
+++ b/dspace-api/src/main/java/org/dspace/core/DBConnection.java
@@ -148,4 +148,12 @@ public interface DBConnection {
* @throws java.sql.SQLException passed through.
*/
public void uncacheEntity(E entity) throws SQLException;
+
+ /**
+ * Do a manual flush. This synchronizes the in-memory state of the Session
+ * with the database (write changes to the database)
+ *
+ * @throws SQLException passed through.
+ */
+ public void flushSession() throws SQLException;
}
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 998d934c95..f6df740a53 100644
--- a/dspace-api/src/main/java/org/dspace/core/Email.java
+++ b/dspace-api/src/main/java/org/dspace/core/Email.java
@@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
-import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.activation.DataHandler;
@@ -41,7 +40,6 @@ import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.ParseException;
-import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.velocity.Template;
@@ -57,26 +55,40 @@ import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
- * Class representing an e-mail message, also used to send e-mails.
+ * Class representing an e-mail message. The {@link send} method causes the
+ * assembled message to be formatted and sent.
*
* Typical use:
- *
+ *
+ * Email email = Email.getEmail(path);
+ * email.addRecipient("foo@bar.com");
+ * email.addArgument("John");
+ * email.addArgument("On the Testing of DSpace");
+ * email.send();
+ *
+ * {@code path} is the filesystem path of an email template, typically in
+ * {@code ${dspace.dir}/config/emails/} and can include the subject -- see
+ * below. Templates are processed by
+ * Apache Velocity. They may contain VTL directives and property
+ * placeholders.
*
- * Email email = new Email();
- * email.addRecipient("foo@bar.com");
- * email.addArgument("John");
- * email.addArgument("On the Testing of DSpace");
- * email.send();
- *
+ * {@link addArgument(string)} adds a property to the {@code params} array
+ * in the Velocity context, which can be used to replace placeholder tokens
+ * in the message. These arguments are indexed by number in the order they were
+ * added to the message.
*
- * name
is the name of an email template in
- * dspace-dir/config/emails/
(which also includes the subject.)
- * arg0
and arg1
are arguments to fill out the
- * message with.
- *
- * Emails are formatted using Apache Velocity. Headers such as Subject may be
- * supplied by the template, by defining them using #set(). Example:
- *
+ * The DSpace configuration properties are also available to templates as the
+ * array {@code config}, indexed by name. Example: {@code ${config.get('dspace.name')}}
+ *
+ * Recipients and attachments may be added as needed. See {@link addRecipient},
+ * {@link addAttachment(File, String)}, and
+ * {@link addAttachment(InputStream, String, String)}.
+ *
+ * Headers such as Subject may be supplied by the template, by defining them
+ * using the VTL directive {@code #set()}. Only headers named in the DSpace
+ * configuration array property {@code mail.message.headers} will be added.
+ *
+ * Example:
*
*
*
@@ -91,12 +103,14 @@ import org.dspace.services.factory.DSpaceServicesFactory;
*
* Thank you for sending us your submission "${params[1]}".
*
+ * --
+ * The ${config.get('dspace.name')} Team
+ *
*
*
*
* If the example code above was used to send this mail, the resulting mail
* would have the subject Example e-mail
and the body would be:
- *
*
*
*
@@ -105,7 +119,16 @@ import org.dspace.services.factory.DSpaceServicesFactory;
*
* Thank you for sending us your submission "On the Testing of DSpace".
*
+ * --
+ * The DSpace Team
+ *
*
+ *
+ * There are two ways to load a message body. One can create an instance of
+ * {@link Email} and call {@link setContent} on it, passing the body as a String. Or
+ * one can use the static factory method {@link getEmail} to load a file by its
+ * complete filesystem path. In either case the text will be loaded into a
+ * Velocity template.
*
* @author Robert Tansley
* @author Jim Downing - added attachment handling code
@@ -115,7 +138,6 @@ public class Email {
/**
* The content of the message
*/
- private String content;
private String contentName;
/**
@@ -176,13 +198,12 @@ public class Email {
moreAttachments = new ArrayList<>(10);
subject = "";
template = null;
- content = "";
replyTo = null;
charset = null;
}
/**
- * Add a recipient
+ * Add a recipient.
*
* @param email the recipient's email address
*/
@@ -196,16 +217,24 @@ public class Email {
* "Subject:" line must be stripped.
*
* @param name a name for this message body
- * @param cnt the content of the message
+ * @param content the content of the message
*/
- public void setContent(String name, String cnt) {
- content = cnt;
+ public void setContent(String name, String content) {
contentName = name;
arguments.clear();
+
+ VelocityEngine templateEngine = new VelocityEngine();
+ templateEngine.init(VELOCITY_PROPERTIES);
+
+ StringResourceRepository repo = (StringResourceRepository)
+ templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME);
+ repo.putStringResource(contentName, content);
+ // Turn content into a template.
+ template = templateEngine.getTemplate(contentName);
}
/**
- * Set the subject of the message
+ * Set the subject of the message.
*
* @param s the subject of the message
*/
@@ -214,7 +243,7 @@ public class Email {
}
/**
- * Set the reply-to email address
+ * Set the reply-to email address.
*
* @param email the reply-to email address
*/
@@ -223,7 +252,7 @@ public class Email {
}
/**
- * Fill out the next argument in the template
+ * Fill out the next argument in the template.
*
* @param arg the value for the next argument
*/
@@ -231,6 +260,13 @@ public class Email {
arguments.add(arg);
}
+ /**
+ * Add an attachment bodypart to the message from an external file.
+ *
+ * @param f reference to a file to be attached.
+ * @param name a name for the resulting bodypart in the message's MIME
+ * structure.
+ */
public void addAttachment(File f, String name) {
attachments.add(new FileAttachment(f, name));
}
@@ -238,6 +274,17 @@ public class Email {
/** When given a bad MIME type for an attachment, use this instead. */
private static final String DEFAULT_ATTACHMENT_TYPE = "application/octet-stream";
+ /**
+ * Add an attachment bodypart to the message from a byte stream.
+ *
+ * @param is the content of this stream will become the content of the
+ * bodypart.
+ * @param name a name for the resulting bodypart in the message's MIME
+ * structure.
+ * @param mimetype the MIME type of the resulting bodypart, such as
+ * "text/pdf". If {@code null} it will default to
+ * "application/octet-stream", which is MIME for "unknown format".
+ */
public void addAttachment(InputStream is, String name, String mimetype) {
if (null == mimetype) {
LOG.error("Null MIME type replaced with '" + DEFAULT_ATTACHMENT_TYPE
@@ -257,6 +304,11 @@ public class Email {
moreAttachments.add(new InputStreamAttachment(is, name, mimetype));
}
+ /**
+ * Set the character set of the message.
+ *
+ * @param cs the name of a character set, such as "UTF-8" or "EUC-JP".
+ */
public void setCharset(String cs) {
charset = cs;
}
@@ -280,15 +332,20 @@ public class Email {
* {@code mail.message.headers} then that name and its value will be added
* to the message's headers.
*
- *
"subject" is treated specially: if {@link setSubject()} has not been called,
- * the value of any "subject" property will be used as if setSubject had
- * been called with that value. Thus a template may define its subject, but
- * the caller may override it.
+ *
"subject" is treated specially: if {@link setSubject()} has not been
+ * called, the value of any "subject" property will be used as if setSubject
+ * had been called with that value. Thus a template may define its subject,
+ * but the caller may override it.
*
* @throws MessagingException if there was a problem sending the mail.
* @throws IOException if IO error
*/
public void send() throws MessagingException, IOException {
+ if (null == template) {
+ // No template -- no content -- PANIC!!!
+ throw new MessagingException("Email has no body");
+ }
+
ConfigurationService config
= DSpaceServicesFactory.getInstance().getConfigurationService();
@@ -308,37 +365,18 @@ public class Email {
MimeMessage message = new MimeMessage(session);
// Set the recipients of the message
- Iterator i = recipients.iterator();
-
- while (i.hasNext()) {
- message.addRecipient(Message.RecipientType.TO, new InternetAddress(
- i.next()));
+ for (String recipient : recipients) {
+ message.addRecipient(Message.RecipientType.TO,
+ new InternetAddress(recipient));
}
// Get headers defined by the template.
String[] templateHeaders = config.getArrayProperty("mail.message.headers");
// Format the mail message body
- VelocityEngine templateEngine = new VelocityEngine();
- templateEngine.init(VELOCITY_PROPERTIES);
-
VelocityContext vctx = new VelocityContext();
vctx.put("config", new UnmodifiableConfigurationService(config));
vctx.put("params", Collections.unmodifiableList(arguments));
- if (null == template) {
- if (StringUtils.isBlank(content)) {
- // No template and no content -- PANIC!!!
- throw new MessagingException("Email has no body");
- }
- // No template, so use a String of content.
- StringResourceRepository repo = (StringResourceRepository)
- templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME);
- repo.putStringResource(contentName, content);
- // Turn content into a template.
- template = templateEngine.getTemplate(contentName);
- templateHeaders = new String[] {};
- }
-
StringWriter writer = new StringWriter();
try {
template.merge(vctx, writer);
@@ -405,7 +443,8 @@ public class Email {
// add the stream
messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(new DataHandler(
- new InputStreamDataSource(attachment.name,attachment.mimetype,attachment.is)));
+ new InputStreamDataSource(attachment.name,
+ attachment.mimetype, attachment.is)));
messageBodyPart.setFileName(attachment.name);
multipart.addBodyPart(messageBodyPart);
}
@@ -447,6 +486,9 @@ public class Email {
/**
* Get the VTL template for an email message. The message is suitable
* for inserting values using Apache Velocity.
+ *
+ * Note that everything is stored here, so that only send() throws a
+ * MessagingException.
*
* @param emailFile
* full name for the email template, for example "/dspace/config/emails/register".
@@ -484,15 +526,6 @@ public class Email {
}
return email;
}
- /*
- * Implementation note: It might be necessary to add a quick utility method
- * like "send(to, subject, message)". We'll see how far we get without it -
- * having all emails as templates in the config allows customisation and
- * internationalisation.
- *
- * Note that everything is stored and the run in send() so that only send()
- * throws a MessagingException.
- */
/**
* Test method to send an email to check email server settings
@@ -547,7 +580,7 @@ public class Email {
}
/**
- * Utility struct class for handling file attachments.
+ * Utility record class for handling file attachments.
*
* @author ojd20
*/
@@ -563,7 +596,7 @@ public class Email {
}
/**
- * Utility struct class for handling file attachments.
+ * Utility record class for handling file attachments.
*
* @author Adán Román Ruiz at arvo.es
*/
@@ -580,6 +613,8 @@ public class Email {
}
/**
+ * Wrap an {@link InputStream} in a {@link DataSource}.
+ *
* @author arnaldo
*/
public static class InputStreamDataSource implements DataSource {
@@ -587,6 +622,14 @@ public class Email {
private final String contentType;
private final ByteArrayOutputStream baos;
+ /**
+ * Consume the content of an InputStream and store it in a local buffer.
+ *
+ * @param name give the DataSource a name.
+ * @param contentType the DataSource contains this type of data.
+ * @param inputStream content to be buffered in the DataSource.
+ * @throws IOException if the stream cannot be read.
+ */
InputStreamDataSource(String name, String contentType, InputStream inputStream) throws IOException {
this.name = name;
this.contentType = contentType;
diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java
index 3321e4d837..b371af80ee 100644
--- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java
+++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java
@@ -337,4 +337,17 @@ public class HibernateDBConnection implements DBConnection {
}
}
}
+
+ /**
+ * Do a manual flush. This synchronizes the in-memory state of the Session
+ * with the database (write changes to the database)
+ *
+ * @throws SQLException passed through.
+ */
+ @Override
+ public void flushSession() throws SQLException {
+ if (getSession().isDirty()) {
+ getSession().flush();
+ }
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java
index 8324105a30..d895f9a764 100644
--- a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java
@@ -17,9 +17,12 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
+import javax.servlet.http.HttpServletRequest;
import org.dspace.core.service.LicenseService;
import org.dspace.services.factory.DSpaceServicesFactory;
+import org.dspace.services.model.Request;
+import org.dspace.web.ContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -101,13 +104,14 @@ public class LicenseServiceImpl implements LicenseService {
/**
* Get the site-wide default license that submitters need to grant
*
+ * Localized license requires: default_{{locale}}.license file.
+ * Locale also must be listed in webui.supported.locales setting.
+ *
* @return the default license
*/
@Override
public String getDefaultSubmissionLicense() {
- if (null == license) {
- init();
- }
+ init();
return license;
}
@@ -115,9 +119,8 @@ public class LicenseServiceImpl implements LicenseService {
* Load in the default license.
*/
protected void init() {
- File licenseFile = new File(
- DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir")
- + File.separator + "config" + File.separator + "default.license");
+ Context context = obtainContext();
+ File licenseFile = new File(I18nUtil.getDefaultLicense(context));
FileInputStream fir = null;
InputStreamReader ir = null;
@@ -169,4 +172,24 @@ public class LicenseServiceImpl implements LicenseService {
}
}
}
+
+ /**
+ * Obtaining current request context.
+ * Return new context if getting one from current request failed.
+ *
+ * @return DSpace context object
+ */
+ private Context obtainContext() {
+ try {
+ Request currentRequest = DSpaceServicesFactory.getInstance().getRequestService().getCurrentRequest();
+ if (currentRequest != null) {
+ HttpServletRequest request = currentRequest.getHttpServletRequest();
+ return ContextUtil.obtainContext(request);
+ }
+ } catch (Exception e) {
+ log.error("Can't load current request context.");
+ }
+
+ return new Context();
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java b/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java
index 07bfed5fe5..2899e3f6bd 100644
--- a/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java
+++ b/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java
@@ -17,6 +17,7 @@ import org.dspace.app.util.DCInput;
import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
+import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
@@ -69,7 +70,7 @@ public class RequiredMetadata extends AbstractCurationTask {
handle = "in workflow";
}
sb.append("Item: ").append(handle);
- for (String req : getReqList(item.getOwningCollection().getHandle())) {
+ for (String req : getReqList(item.getOwningCollection())) {
List vals = itemService.getMetadataByMetadataString(item, req);
if (vals.size() == 0) {
sb.append(" missing required field: ").append(req);
@@ -91,14 +92,14 @@ public class RequiredMetadata extends AbstractCurationTask {
}
}
- protected List getReqList(String handle) throws DCInputsReaderException {
- List reqList = reqMap.get(handle);
+ protected List getReqList(Collection collection) throws DCInputsReaderException {
+ List reqList = reqMap.get(collection.getHandle());
if (reqList == null) {
reqList = reqMap.get("default");
}
if (reqList == null) {
reqList = new ArrayList();
- List inputSet = reader.getInputsByCollectionHandle(handle);
+ List inputSet = reader.getInputsByCollection(collection);
for (DCInputSet inputs : inputSet) {
for (DCInput[] row : inputs.getFields()) {
for (DCInput input : row) {
diff --git a/dspace-api/src/main/java/org/dspace/curate/Curation.java b/dspace-api/src/main/java/org/dspace/curate/Curation.java
index b3af072a32..4d70286e79 100644
--- a/dspace-api/src/main/java/org/dspace/curate/Curation.java
+++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java
@@ -152,17 +152,10 @@ public class Curation extends DSpaceRunnable {
super.handler.logInfo("Curating id: " + entry.getObjectId());
}
curator.clear();
- // does entry relate to a DSO or workflow object?
- if (entry.getObjectId().indexOf('/') > 0) {
- for (String taskName : entry.getTaskNames()) {
- curator.addTask(taskName);
- }
- curator.curate(context, entry.getObjectId());
- } else {
- // TODO: Remove this exception once curation tasks are supported by configurable workflow
- // e.g. see https://github.com/DSpace/DSpace/pull/3157
- throw new IllegalArgumentException("curation for workflow items is no longer supported");
+ for (String taskName : entry.getTaskNames()) {
+ curator.addTask(taskName);
}
+ curator.curate(context, entry.getObjectId());
}
queue.release(this.queue, ticket, true);
return ticket;
diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java
index 05c7a8d999..00e91ee1fb 100644
--- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java
@@ -13,6 +13,8 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+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.content.Collection;
@@ -30,6 +32,7 @@ import org.dspace.workflow.CurationTaskConfig;
import org.dspace.workflow.FlowStep;
import org.dspace.workflow.Task;
import org.dspace.workflow.TaskSet;
+import org.dspace.xmlworkflow.Role;
import org.dspace.xmlworkflow.RoleMembers;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
@@ -47,14 +50,17 @@ import org.springframework.stereotype.Service;
* Manage interactions between curation and workflow. A curation task can be
* attached to a workflow step, to be executed during the step.
*
+ *
+ * NOTE: when run in workflow, curation tasks run with
+ * authorization disabled.
+ *
* @see CurationTaskConfig
* @author mwood
*/
@Service
public class XmlWorkflowCuratorServiceImpl
implements XmlWorkflowCuratorService {
- private static final Logger LOG
- = org.apache.logging.log4j.LogManager.getLogger();
+ private static final Logger LOG = LogManager.getLogger();
@Autowired(required = true)
protected XmlWorkflowFactory workflowFactory;
@@ -97,7 +103,18 @@ public class XmlWorkflowCuratorServiceImpl
throws AuthorizeException, IOException, SQLException {
Curator curator = new Curator();
curator.setReporter(reporter);
- return curate(curator, c, wfi);
+ c.turnOffAuthorisationSystem();
+ boolean wasAnonymous = false;
+ if (null == c.getCurrentUser()) { // We need someone to email
+ wasAnonymous = true;
+ c.setCurrentUser(ePersonService.getSystemEPerson(c));
+ }
+ boolean failedP = curate(curator, c, wfi);
+ if (wasAnonymous) {
+ c.setCurrentUser(null);
+ }
+ c.restoreAuthSystemState();
+ return failedP;
}
@Override
@@ -123,40 +140,47 @@ public class XmlWorkflowCuratorServiceImpl
item.setOwningCollection(wfi.getCollection());
for (Task task : step.tasks) {
curator.addTask(task.name);
- curator.curate(item);
- int status = curator.getStatus(task.name);
- String result = curator.getResult(task.name);
- String action = "none";
- switch (status) {
- case Curator.CURATE_FAIL:
- // task failed - notify any contacts the task has assigned
- if (task.powers.contains("reject")) {
- action = "reject";
- }
- notifyContacts(c, wfi, task, "fail", action, result);
- // if task so empowered, reject submission and terminate
- if ("reject".equals(action)) {
- workflowService.sendWorkflowItemBackSubmission(c, wfi,
- c.getCurrentUser(), null,
- task.name + ": " + result);
- return false;
- }
- break;
- case Curator.CURATE_SUCCESS:
- if (task.powers.contains("approve")) {
- action = "approve";
- }
- notifyContacts(c, wfi, task, "success", action, result);
- if ("approve".equals(action)) {
- // cease further task processing and advance submission
- return true;
- }
- break;
- case Curator.CURATE_ERROR:
- notifyContacts(c, wfi, task, "error", action, result);
- break;
- default:
- break;
+ // Check whether the task is configured to be queued rather than automatically run
+ if (StringUtils.isNotEmpty(step.queue)) {
+ // queue attribute has been set in the FlowStep configuration: add task to configured queue
+ curator.queue(c, item.getID().toString(), step.queue);
+ } else {
+ // Task is configured to be run automatically
+ curator.curate(c, item);
+ int status = curator.getStatus(task.name);
+ String result = curator.getResult(task.name);
+ String action = "none";
+ switch (status) {
+ case Curator.CURATE_FAIL:
+ // task failed - notify any contacts the task has assigned
+ if (task.powers.contains("reject")) {
+ action = "reject";
+ }
+ notifyContacts(c, wfi, task, "fail", action, result);
+ // if task so empowered, reject submission and terminate
+ if ("reject".equals(action)) {
+ workflowService.sendWorkflowItemBackSubmission(c, wfi,
+ c.getCurrentUser(), null,
+ task.name + ": " + result);
+ return false;
+ }
+ break;
+ case Curator.CURATE_SUCCESS:
+ if (task.powers.contains("approve")) {
+ action = "approve";
+ }
+ notifyContacts(c, wfi, task, "success", action, result);
+ if ("approve".equals(action)) {
+ // cease further task processing and advance submission
+ return true;
+ }
+ break;
+ case Curator.CURATE_ERROR:
+ notifyContacts(c, wfi, task, "error", action, result);
+ break;
+ default:
+ break;
+ }
}
curator.clear();
}
@@ -223,8 +247,12 @@ public class XmlWorkflowCuratorServiceImpl
String status, String action, String message)
throws AuthorizeException, IOException, SQLException {
List epa = resolveContacts(c, task.getContacts(status), wfi);
- if (epa.size() > 0) {
+ if (!epa.isEmpty()) {
workflowService.notifyOfCuration(c, wfi, epa, task.name, action, message);
+ } else {
+ LOG.warn("No contacts were found for workflow item {}: "
+ + "task {} returned action {} with message {}",
+ wfi.getID(), task.name, action, message);
}
}
@@ -247,8 +275,7 @@ public class XmlWorkflowCuratorServiceImpl
// decode contacts
if ("$flowgroup".equals(contact)) {
// special literal for current flowgoup
- ClaimedTask claimedTask = claimedTaskService.findByWorkflowIdAndEPerson(c, wfi, c.getCurrentUser());
- String stepID = claimedTask.getStepID();
+ String stepID = getFlowStep(c, wfi).step;
Step step;
try {
Workflow workflow = workflowFactory.getWorkflow(wfi.getCollection());
@@ -258,19 +285,26 @@ public class XmlWorkflowCuratorServiceImpl
String.valueOf(wfi.getID()), e);
return epList;
}
- RoleMembers roleMembers = step.getRole().getMembers(c, wfi);
- for (EPerson ep : roleMembers.getEPersons()) {
- epList.add(ep);
- }
- for (Group group : roleMembers.getGroups()) {
- epList.addAll(group.getMembers());
+ Role role = step.getRole();
+ if (null != role) {
+ RoleMembers roleMembers = role.getMembers(c, wfi);
+ for (EPerson ep : roleMembers.getEPersons()) {
+ epList.add(ep);
+ }
+ for (Group group : roleMembers.getGroups()) {
+ epList.addAll(group.getMembers());
+ }
+ } else {
+ epList.add(ePersonService.getSystemEPerson(c));
}
} else if ("$colladmin".equals(contact)) {
+ // special literal for collection administrators
Group adGroup = wfi.getCollection().getAdministrators();
if (adGroup != null) {
epList.addAll(groupService.allMembers(c, adGroup));
}
} else if ("$siteadmin".equals(contact)) {
+ // special literal for site administrator
EPerson siteEp = ePersonService.findByEmail(c,
configurationService.getProperty("mail.admin"));
if (siteEp != null) {
diff --git a/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java b/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java
index 2ad1eac129..778b779cfe 100644
--- a/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java
+++ b/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java
@@ -42,9 +42,9 @@ public interface XmlWorkflowCuratorService {
*
* @param c the context
* @param wfi the workflow item
- * @return true if curation was completed or not required,
+ * @return true if curation was completed or not required;
* false if tasks were queued for later completion,
- * or item was rejected
+ * or item was rejected.
* @throws AuthorizeException if authorization error
* @throws IOException if IO error
* @throws SQLException if database error
@@ -58,7 +58,9 @@ public interface XmlWorkflowCuratorService {
* @param curator the curation context
* @param c the user context
* @param wfId the workflow item's ID
- * @return true if curation failed.
+ * @return true if curation curation was completed or not required;
+ * false if tasks were queued for later completion,
+ * or item was rejected.
* @throws AuthorizeException if authorization error
* @throws IOException if IO error
* @throws SQLException if database error
@@ -72,7 +74,9 @@ public interface XmlWorkflowCuratorService {
* @param curator the curation context
* @param c the user context
* @param wfi the workflow item
- * @return true if curation failed.
+ * @return true if workflow curation was completed or not required;
+ * false if tasks were queued for later completion,
+ * or item was rejected.
* @throws AuthorizeException if authorization error
* @throws IOException if IO error
* @throws SQLException if database error
diff --git a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java
index ee220e5a4f..21468def68 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java
@@ -76,14 +76,19 @@ public class FullTextContentStreams extends ContentStreamBase {
if (StringUtils.equals(FULLTEXT_BUNDLE, myBundle.getName())) {
// a-ha! grab the text out of the bitstreams
List bitstreams = myBundle.getBitstreams();
+ log.debug("Processing full-text bitstreams. Item handle: " + sourceInfo);
for (Bitstream fulltextBitstream : emptyIfNull(bitstreams)) {
fullTextStreams.add(new FullTextBitstream(sourceInfo, fulltextBitstream));
- log.debug("Added BitStream: "
- + fulltextBitstream.getStoreNumber() + " "
- + fulltextBitstream.getSequenceID() + " "
- + fulltextBitstream.getName());
+ if (fulltextBitstream != null) {
+ log.debug("Added BitStream: "
+ + fulltextBitstream.getStoreNumber() + " "
+ + fulltextBitstream.getSequenceID() + " "
+ + fulltextBitstream.getName());
+ } else {
+ log.error("Found a NULL bitstream when processing full-text files: item handle:" + sourceInfo);
+ }
}
}
}
@@ -158,16 +163,16 @@ public class FullTextContentStreams extends ContentStreamBase {
}
public String getContentType(final Context context) throws SQLException {
- BitstreamFormat format = bitstream.getFormat(context);
+ BitstreamFormat format = bitstream != null ? bitstream.getFormat(context) : null;
return format == null ? null : StringUtils.trimToEmpty(format.getMIMEType());
}
public String getFileName() {
- return StringUtils.trimToEmpty(bitstream.getName());
+ return bitstream != null ? StringUtils.trimToEmpty(bitstream.getName()) : null;
}
public long getSize() {
- return bitstream.getSizeBytes();
+ return bitstream != null ? bitstream.getSizeBytes() : -1;
}
public InputStream getInputStream() throws SQLException, IOException, AuthorizeException {
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 661c48d91c..b70e9162f7 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
@@ -7,14 +7,20 @@
*/
package org.dspace.discovery;
+import static org.dspace.discovery.IndexClientOptions.TYPE_OPTION;
+
import java.io.IOException;
import java.sql.SQLException;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.List;
import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Collectors;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.ParseException;
+import org.apache.commons.lang3.StringUtils;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
@@ -51,6 +57,17 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream()
+ .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList());
+ type = commandLine.getOptionValue(TYPE_OPTION);
+ if (!indexableObjectTypes.contains(type)) {
+ handler.handleException(String.format("%s is not a valid indexable object type, options: %s",
+ type, Arrays.toString(indexableObjectTypes.toArray())));
+ }
+ }
+
/** Acquire from dspace-services in future */
/**
* new DSpace.getServiceManager().getServiceByName("org.dspace.discovery.SolrIndexer");
@@ -113,6 +130,10 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream()
+ .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList());
options
.addOption("r", "remove", true, "remove an Item, Collection or Community from index based on its handle");
options.addOption("i", "index", true,
"add or update an Item, Collection or Community based on its handle or uuid");
+ options.addOption(TYPE_OPTION, "type", true, "reindex only specific type of " +
+ "(re)indexable objects; options: " + Arrays.toString(indexableObjectTypes.toArray()));
options.addOption("c", "clean", false,
"clean existing index removing any documents that no longer exist in the db");
options.addOption("d", "delete", false,
diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java
index 4ff1f31344..80602ac804 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java
@@ -154,7 +154,11 @@ public class IndexEventConsumer implements Consumer {
case Event.REMOVE:
case Event.ADD:
- if (object == null) {
+ // At this time, ADD and REMOVE actions are ignored on SITE object. They are only triggered for
+ // top-level communities. No action is necessary as Community itself is indexed (or deleted) separately.
+ if (event.getSubjectType() == Constants.SITE) {
+ log.debug(event.getEventTypeAsString() + " event triggered for Site object. Skipping it.");
+ } else if (object == null) {
log.warn(event.getEventTypeAsString() + " event, could not get object for "
+ event.getObjectTypeAsString() + " id="
+ event.getObjectID()
@@ -201,6 +205,10 @@ public class IndexEventConsumer implements Consumer {
@Override
public void end(Context ctx) throws Exception {
+ // Change the mode to readonly to improve performance
+ Context.Mode originalMode = ctx.getCurrentMode();
+ ctx.setMode(Context.Mode.READ_ONLY);
+
try {
for (String uid : uniqueIdsToDelete) {
try {
@@ -230,6 +238,8 @@ public class IndexEventConsumer implements Consumer {
uniqueIdsToDelete.clear();
createdItemsToUpdate.clear();
}
+
+ ctx.setMode(originalMode);
}
}
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 0cf2aa50af..cd3797e3e3 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java
@@ -1031,9 +1031,8 @@ public class SolrServiceImpl implements SearchService, IndexingService {
// Add information about our search fields
for (String field : searchFields) {
List valuesAsString = new ArrayList<>();
- for (Object o : doc.getFieldValues(field)) {
- valuesAsString.add(String.valueOf(o));
- }
+ Optional.ofNullable(doc.getFieldValues(field))
+ .ifPresent(l -> l.forEach(o -> valuesAsString.add(String.valueOf(o))));
resultDoc.addSearchField(field, valuesAsString.toArray(new String[valuesAsString.size()]));
}
result.addSearchDocument(indexableObject, resultDoc);
diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java
index 55c99b168e..f1ae137b91 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java
@@ -64,7 +64,14 @@ public abstract class IndexFactoryImpl