Merge remote-tracking branch 'upstream/main' into fix-9070-b

# Conflicts:
#	dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java
This commit is contained in:
Agustina Martinez
2023-10-27 09:20:16 +01:00
32 changed files with 720 additions and 94 deletions

View File

@@ -769,7 +769,7 @@
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
<version>20231013</version>
</dependency>
<!-- Useful for testing command-line tools -->

View File

@@ -22,9 +22,21 @@ public interface AccessStatusHelper {
*
* @param context the DSpace context
* @param item the item
* @param threshold the embargo threshold date
* @return an access status value
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
throws SQLException;
/**
* Retrieve embargo information for the item
*
* @param context the DSpace context
* @param item the item to check for embargo information
* @param threshold the embargo threshold date
* @return an embargo date
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException;
}

View File

@@ -67,4 +67,9 @@ public class AccessStatusServiceImpl implements AccessStatusService {
public String getAccessStatus(Context context, Item item) throws SQLException {
return helper.getAccessStatusFromItem(context, item, forever_date);
}
@Override
public String getEmbargoFromItem(Context context, Item item) throws SQLException {
return helper.getEmbargoFromItem(context, item, forever_date);
}
}

View File

@@ -26,6 +26,7 @@ import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.Group;
import org.joda.time.LocalDate;
/**
* Default plugin implementation of the access status helper.
@@ -33,6 +34,11 @@ import org.dspace.eperson.Group;
* calculate the access status of an item based on the policies of
* the primary or the first bitstream in the original bundle.
* Users can override this method for enhanced functionality.
*
* The getEmbargoInformationFromItem method provides a simple logic to
* * retrieve embargo information of bitstreams from an item based on the policies of
* * the primary or the first bitstream in the original bundle.
* * Users can override this method for enhanced functionality.
*/
public class DefaultAccessStatusHelper implements AccessStatusHelper {
public static final String EMBARGO = "embargo";
@@ -54,12 +60,12 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
/**
* Look at the item's policies to determine an access status value.
* It is also considering a date threshold for embargos and restrictions.
* It is also considering a date threshold for embargoes and restrictions.
*
* If the item is null, simply returns the "unknown" value.
*
* @param context the DSpace context
* @param item the item to embargo
* @param item the item to check for embargoes
* @param threshold the embargo threshold date
* @return an access status value
*/
@@ -86,7 +92,7 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
.findFirst()
.orElse(null);
}
return caculateAccessStatusForDso(context, bitstream, threshold);
return calculateAccessStatusForDso(context, bitstream, threshold);
}
/**
@@ -104,7 +110,7 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
* @param threshold the embargo threshold date
* @return an access status value
*/
private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
throws SQLException {
if (dso == null) {
return METADATA_ONLY;
@@ -156,4 +162,87 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
}
return RESTRICTED;
}
/**
* Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo.
*
* If the item is null, simply returns an empty map with no embargo information.
*
* @param context the DSpace context
* @param item the item to embargo
* @return an access status value
*/
@Override
public String getEmbargoFromItem(Context context, Item item, Date threshold)
throws SQLException {
Date embargoDate;
// If Item status is not "embargo" then return a null embargo date.
String accessStatus = getAccessStatusFromItem(context, item, threshold);
if (item == null || !accessStatus.equals(EMBARGO)) {
return null;
}
// Consider only the original bundles.
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
// Check for primary bitstreams first.
Bitstream bitstream = bundles.stream()
.map(bundle -> bundle.getPrimaryBitstream())
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (bitstream == null) {
// If there is no primary bitstream,
// take the first bitstream in the bundles.
bitstream = bundles.stream()
.map(bundle -> bundle.getBitstreams())
.flatMap(List::stream)
.findFirst()
.orElse(null);
}
if (bitstream == null) {
return null;
}
embargoDate = this.retrieveShortestEmbargo(context, bitstream);
return embargoDate != null ? embargoDate.toString() : null;
}
/**
*
*/
private Date retrieveShortestEmbargo(Context context, Bitstream bitstream) throws SQLException {
Date embargoDate = null;
// Only consider read policies.
List<ResourcePolicy> policies = authorizeService
.getPoliciesActionFilter(context, bitstream, Constants.READ);
// Looks at all read policies.
for (ResourcePolicy policy : policies) {
boolean isValid = resourcePolicyService.isDateValid(policy);
Group group = policy.getGroup();
if (group != null && StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
// Only calculate the status for the anonymous group.
if (!isValid) {
// If the policy is not valid there is an active embargo
Date startDate = policy.getStartDate();
if (startDate != null && !startDate.before(LocalDate.now().toDate())) {
// There is an active embargo: aim to take the shortest embargo (account for rare cases where
// more than one resource policy exists)
if (embargoDate == null) {
embargoDate = startDate;
} else {
embargoDate = startDate.before(embargoDate) ? startDate : embargoDate;
}
}
}
}
}
return embargoDate;
}
}

View File

@@ -40,7 +40,18 @@ public interface AccessStatusService {
*
* @param context the DSpace context
* @param item the item
* @return an access status value
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatus(Context context, Item item) throws SQLException;
/**
* Retrieve embargo information for the item
*
* @param context the DSpace context
* @param item the item to check for embargo information
* @return an embargo date
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getEmbargoFromItem(Context context, Item item) throws SQLException;
}

View File

@@ -464,7 +464,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
.forEach(accessCondition -> createResourcePolicy(item, accessCondition,
itemAccessConditions.get(accessCondition.getName())));
itemService.adjustItemPolicies(context, item, item.getOwningCollection());
itemService.adjustItemPolicies(context, item, item.getOwningCollection(), false);
}
/**

View File

@@ -578,6 +578,10 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
wfItem = workflowService.startWithoutNotify(c, wsItem);
}
} else {
// Add provenance info
String provenance = installItemService.getSubmittedByProvenanceMessage(c, wsItem.getItem());
itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provenance);
// Install the item
installItemService.installItem(c, wsItem);
}
@@ -1363,7 +1367,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
* is the field is defined as authority controlled
*/
private static boolean isAuthorityControlledField(String md) {
String mdf = StringUtils.substringAfter(md, ":");
String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md;
mdf = StringUtils.substringBefore(mdf, "[");
return authorityControlled.contains(mdf);
}

View File

@@ -774,6 +774,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
// put item in system
if (!isTest) {
try {
// Add provenance info
String provenance = installItemService.getSubmittedByProvenanceMessage(c, wi.getItem());
itemService.addMetadata(c, wi.getItem(), MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provenance);
installItemService.installItem(c, wi, myhandle);
} catch (Exception e) {
workspaceItemService.deleteAll(c, wi);

View File

@@ -403,7 +403,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl<Bitstream> 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 +420,13 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl<Bitstream> 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) {

View File

@@ -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;
}
@@ -271,4 +271,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();
}
}

View File

@@ -920,8 +920,16 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> 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 +938,13 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> 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 +963,19 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> 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<Bundle> 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 +984,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> 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 +997,14 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> 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<ResourcePolicy> defaultCollectionPolicies = authorizeService
.getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ);
@@ -998,10 +1034,22 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> 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<ResourcePolicy> 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()

View File

@@ -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;;
}

View File

@@ -473,7 +473,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 +488,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 +527,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 +569,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 +610,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
*
@@ -790,24 +875,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<Item> 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;
/**

View File

@@ -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();
}
}

View File

@@ -14,6 +14,7 @@ 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;
@@ -31,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;
@@ -48,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.
*
* <p>
* <strong>NOTE:</strong> when run in workflow, curation tasks <em>run with
* authorization disabled</em>.
*
* @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;
@@ -98,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
@@ -130,7 +146,7 @@ public class XmlWorkflowCuratorServiceImpl
curator.queue(c, item.getID().toString(), step.queue);
} else {
// Task is configured to be run automatically
curator.curate(item);
curator.curate(c, item);
int status = curator.getStatus(task.name);
String result = curator.getResult(task.name);
String action = "none";
@@ -231,8 +247,12 @@ public class XmlWorkflowCuratorServiceImpl
String status, String action, String message)
throws AuthorizeException, IOException, SQLException {
List<EPerson> 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);
}
}
@@ -255,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());
@@ -266,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) {

View File

@@ -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

View File

@@ -47,6 +47,7 @@ import org.dspace.eperson.service.GroupService;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.event.Event;
import org.dspace.orcid.service.OrcidTokenService;
import org.dspace.services.ConfigurationService;
import org.dspace.util.UUIDUtils;
import org.dspace.versioning.Version;
import org.dspace.versioning.VersionHistory;
@@ -101,6 +102,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
protected VersionDAO versionDAO;
@Autowired(required = true)
protected ClaimedTaskService claimedTaskService;
@Autowired(required = true)
protected ConfigurationService configurationService;
@Autowired
protected OrcidTokenService orcidTokenService;
@@ -113,6 +116,30 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
return ePersonDAO.findByID(context, EPerson.class, id);
}
/**
* Create a fake EPerson which can receive email. Its address will be the
* value of "mail.admin", or "postmaster" if all else fails.
* @param c
* @return
* @throws SQLException
*/
@Override
public EPerson getSystemEPerson(Context c)
throws SQLException {
String adminEmail = configurationService.getProperty("mail.admin");
if (null == adminEmail) {
adminEmail = "postmaster"; // Last-ditch attempt to send *somewhere*
}
EPerson systemEPerson = findByEmail(c, adminEmail);
if (null == systemEPerson) {
systemEPerson = new EPerson();
systemEPerson.setEmail(adminEmail);
}
return systemEPerson;
}
@Override
public EPerson findByIdOrLegacyId(Context context, String id) throws SQLException {
if (StringUtils.isNumeric(id)) {

View File

@@ -13,6 +13,7 @@ import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.validation.constraints.NotNull;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Item;
@@ -157,6 +158,19 @@ public interface EPersonService extends DSpaceObjectService<EPerson>, DSpaceObje
public List<EPerson> findAll(Context context, int sortField, int pageSize, int offset)
throws SQLException;
/**
* The "System EPerson" is a fake account that exists only to receive email.
* It has an email address that should be presumed usable. It does not
* exist in the database and is not complete.
*
* @param context current DSpace session.
* @return an EPerson that can presumably receive email.
* @throws SQLException
*/
@NotNull
public EPerson getSystemEPerson(Context context)
throws SQLException;
/**
* Create a new eperson
*

View File

@@ -90,13 +90,11 @@ public class HandleDAOImpl extends AbstractHibernateDAO<Handle> implements Handl
@Override
public long countHandlesByPrefix(Context context, String prefix) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Handle.class);
Root<Handle> handleRoot = criteriaQuery.from(Handle.class);
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(Handle.class)));
criteriaQuery.select(handleRoot);
criteriaQuery.where(criteriaBuilder.like(handleRoot.get(Handle_.handle), prefix + "%"));
return countLong(context, criteriaQuery, criteriaBuilder, handleRoot);
}

View File

@@ -68,10 +68,9 @@ public class HandleIdentifierProvider extends IdentifierProvider {
try {
String id = mint(context, dso);
// move canonical to point the latest version
// Populate metadata
if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) {
Item item = (Item) dso;
populateHandleMetadata(context, item, id);
populateHandleMetadata(context, dso, id);
}
return id;

View File

@@ -95,11 +95,11 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
String id = mint(context, dso);
// move canonical to point the latest version
if (dso != null && dso.getType() == Constants.ITEM) {
if (dso.getType() == Constants.ITEM && dso instanceof Item) {
Item item = (Item) dso;
VersionHistory history = null;
VersionHistory history;
try {
history = versionHistoryService.findByItem(context, (Item) dso);
history = versionHistoryService.findByItem(context, item);
} catch (SQLException ex) {
throw new RuntimeException("A problem with the database connection occured.", ex);
}

View File

@@ -221,6 +221,8 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
//Get our next step, if none is found, archive our item
firstStep = wf.getNextStep(context, wfi, firstStep, ActionResult.OUTCOME_COMPLETE);
if (firstStep == null) {
// record the submitted provenance message
recordStart(context, wfi.getItem(),null);
archive(context, wfi);
} else {
activateFirstStep(context, wf, firstStep, wfi);
@@ -1187,25 +1189,30 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
DCDate now = DCDate.getCurrent();
// Create provenance description
String provmessage = "";
StringBuffer provmessage = new StringBuffer();
if (myitem.getSubmitter() != null) {
provmessage = "Submitted by " + myitem.getSubmitter().getFullName()
+ " (" + myitem.getSubmitter().getEmail() + ") on "
+ now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n";
provmessage.append("Submitted by ").append(myitem.getSubmitter().getFullName())
.append(" (").append(myitem.getSubmitter().getEmail()).append(") on ")
.append(now.toString());
} else {
// else, null submitter
provmessage = "Submitted by unknown (probably automated) on"
+ now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n";
provmessage.append("Submitted by unknown (probably automated) on")
.append(now.toString());
}
if (action != null) {
provmessage.append(" workflow start=").append(action.getProvenanceStartId()).append("\n");
} else {
provmessage.append("\n");
}
// add sizes and checksums of bitstreams
provmessage += installItemService.getBitstreamProvenanceMessage(context, myitem);
provmessage.append(installItemService.getBitstreamProvenanceMessage(context, myitem));
// Add message to the DC
itemService
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provmessage);
"description", "provenance", "en", provmessage.toString());
itemService.update(context, myitem);
}

View File

@@ -1,9 +0,0 @@
--
-- The contents of this file are subject to the license and copyright
-- detailed in the LICENSE and NOTICE files at the root of the source
-- tree and available online at
--
-- http://www.dspace.org/license/
--
ALTER TABLE process MODIFY (parameters CLOB);

View File

@@ -274,6 +274,8 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold);
assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO));
String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold);
assertThat("testWithEmbargo 1", embargoDate, equalTo(policy.getStartDate().toString()));
}
/**
@@ -391,6 +393,8 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold);
assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO));
String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold);
assertThat("testWithPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(policy.getStartDate().toString()));
}
/**
@@ -420,6 +424,8 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold);
assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS));
String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold);
assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null));
}
/**

View File

@@ -26,6 +26,8 @@ import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.app.requestitem.RequestItem;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.builder.BitstreamBuilder;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
@@ -37,6 +39,7 @@ import org.dspace.builder.RelationshipTypeBuilder;
import org.dspace.builder.RequestItemBuilder;
import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.EntityType;
@@ -48,6 +51,8 @@ import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.dspace.versioning.Version;
import org.dspace.versioning.factory.VersionServiceFactory;
import org.dspace.versioning.service.VersioningService;
@@ -68,6 +73,8 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
protected VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService();
protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
Community community;
Collection collection1;
@@ -752,6 +759,154 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
assertNull(itemService.find(context, item.getID()));
}
@Test
public void testMoveItemToCollectionWithMoreRestrictiveItemReadPolicy() throws Exception {
/* Verify that, if we move an item from a collection with a permissive default item READ policy
* to a collection with a restrictive default item READ policy,
* that the item and its bundles do not retain the original permissive item READ policy.
* However, its bitstreams do.
*/
context.turnOffAuthorisationSystem();
Group anonymous = groupService.findByName(context, Group.ANONYMOUS);
Group admin = groupService.findByName(context, Group.ADMIN);
// Set up the two different collections: one permissive and one restrictive in its default READ policy.
Collection permissive = CollectionBuilder
.createCollection(context, community)
.build();
Collection restrictive = CollectionBuilder
.createCollection(context, community)
.build();
authorizeService.removePoliciesActionFilter(context, restrictive, Constants.DEFAULT_ITEM_READ);
authorizeService.addPolicy(context, restrictive, Constants.DEFAULT_ITEM_READ, admin);
// Add an item to the permissive collection.
Item item = ItemBuilder
.createItem(context, permissive)
.build();
Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, InputStream.nullInputStream())
.build();
Bundle bundle = item.getBundles("ORIGINAL").get(0);
// Verify that the item, bundle and bitstream each have exactly one READ policy, for the anonymous group.
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, item, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
// Move the item to the restrictive collection, making sure to inherit default policies.
itemService.move(context, item, permissive, restrictive, true);
// Verify that the item's read policy now only allows administrators.
assertEquals(
List.of(admin),
authorizeService.getPoliciesActionFilter(context, item, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(admin),
authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
context.restoreAuthSystemState();
}
@Test
public void testMoveItemToCollectionWithMoreRestrictiveBitstreamReadPolicy() throws Exception {
/* Verify that, if we move an item from a collection with a permissive default bitstream READ policy
* to a collection with a restrictive default bitstream READ policy,
* that the item's bitstreams do not retain the original permissive READ policy.
* However, the item itself and its bundles do retain the original policy.
*/
context.turnOffAuthorisationSystem();
Group anonymous = groupService.findByName(context, Group.ANONYMOUS);
Group admin = groupService.findByName(context, Group.ADMIN);
// Set up the two different collections: one permissive and one restrictive in its default READ policy.
Collection permissive = CollectionBuilder
.createCollection(context, community)
.build();
Collection restrictive = CollectionBuilder
.createCollection(context, community)
.build();
authorizeService.removePoliciesActionFilter(context, restrictive, Constants.DEFAULT_BITSTREAM_READ);
authorizeService.addPolicy(context, restrictive, Constants.DEFAULT_BITSTREAM_READ, admin);
// Add an item to the permissive collection.
Item item = ItemBuilder
.createItem(context, permissive)
.build();
Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, InputStream.nullInputStream())
.build();
Bundle bundle = item.getBundles("ORIGINAL").get(0);
// Verify that the item, bundle and bitstream each have exactly one READ policy, for the anonymous group.
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, item, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
// Move the item to the restrictive collection, making sure to inherit default policies.
itemService.move(context, item, permissive, restrictive, true);
// Verify that the bundle and bitstream's read policies now only allows administrators.
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, item, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(anonymous),
authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
assertEquals(
List.of(admin),
authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ)
.stream().map(ResourcePolicy::getGroup).collect(Collectors.toList())
);
context.restoreAuthSystemState();
}
private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value,
String authority, int place, MetadataValue metadataValue) {
assertThat(metadataValue.getValue(), equalTo(value));

View File

@@ -85,7 +85,6 @@ public class XOAI {
// needed because the solr query only returns 10 rows by default
private final Context context;
private boolean optimize;
private final boolean verbose;
private boolean clean;
@@ -122,9 +121,8 @@ public class XOAI {
return formats;
}
public XOAI(Context context, boolean optimize, boolean clean, boolean verbose) {
public XOAI(Context context, boolean clean, boolean verbose) {
this.context = context;
this.optimize = optimize;
this.clean = clean;
this.verbose = verbose;
@@ -173,12 +171,6 @@ public class XOAI {
}
solrServerResolver.getServer().commit();
if (optimize) {
println("Optimizing Index");
solrServerResolver.getServer().optimize();
println("Index optimized");
}
// Set last compilation date
xoaiLastCompilationCacheService.put(new Date());
return result;
@@ -586,7 +578,6 @@ public class XOAI {
CommandLineParser parser = new DefaultParser();
Options options = new Options();
options.addOption("c", "clear", false, "Clear index before indexing");
options.addOption("o", "optimize", false, "Optimize index at the end");
options.addOption("v", "verbose", false, "Verbose output");
options.addOption("h", "help", false, "Shows some help");
options.addOption("n", "number", true, "FOR DEVELOPMENT MUST DELETE");
@@ -620,7 +611,7 @@ public class XOAI {
if (COMMAND_IMPORT.equals(command)) {
ctx = new Context(Context.Mode.READ_ONLY);
XOAI indexer = new XOAI(ctx, line.hasOption('o'), line.hasOption('c'), line.hasOption('v'));
XOAI indexer = new XOAI(ctx, line.hasOption('c'), line.hasOption('v'));
applicationContext.getAutowireCapableBeanFactory().autowireBean(indexer);
@@ -706,7 +697,6 @@ public class XOAI {
System.out.println(" " + COMMAND_IMPORT + " - To import DSpace items into OAI index and cache system");
System.out.println(" " + COMMAND_CLEAN_CACHE + " - Cleans the OAI cached responses");
System.out.println("> Parameters:");
System.out.println(" -o Optimize index after indexing (" + COMMAND_IMPORT + " only)");
System.out.println(" -c Clear index (" + COMMAND_IMPORT + " only)");
System.out.println(" -v Verbose output");
System.out.println(" -h Shows this text");

View File

@@ -12,6 +12,7 @@ import java.util.List;
import com.lyncode.xoai.dataprovider.xml.xoai.Element;
import com.lyncode.xoai.dataprovider.xml.xoai.Metadata;
import org.apache.commons.lang3.StringUtils;
import org.dspace.access.status.factory.AccessStatusServiceFactory;
import org.dspace.access.status.service.AccessStatusService;
import org.dspace.content.Item;
@@ -31,6 +32,13 @@ import org.dspace.xoai.util.ItemUtils;
* <field name="value">open.access</field>
* </element>
* </element>
* OR
* <element name="others">
* <element name="access-status">
* <field name="value">embargo</field>
* <field name="embargo">2024-10-10</field>
* </element>
* </element>
* }
* </pre>
* Returning Values are based on:
@@ -46,9 +54,15 @@ public class AccessStatusElementItemCompilePlugin implements XOAIExtensionItemCo
String accessStatusType;
accessStatusType = accessStatusService.getAccessStatus(context, item);
String embargoFromItem = accessStatusService.getEmbargoFromItem(context, item);
Element accessStatus = ItemUtils.create("access-status");
accessStatus.getField().add(ItemUtils.createValue("value", accessStatusType));
if (StringUtils.isNotEmpty(embargoFromItem)) {
accessStatus.getField().add(ItemUtils.createValue("embargo", embargoFromItem));
}
Element others;
List<Element> elements = metadata.getElement();
if (ItemUtils.getElement(elements, "others") != null) {

View File

@@ -21,7 +21,6 @@ import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.validator.routines.EmailValidator;
import org.apache.http.client.utils.URIBuilder;
import org.apache.logging.log4j.LogManager;
@@ -48,7 +47,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.web.util.HtmlUtils;
/**
* Component to expose item requests.
*
@@ -173,11 +172,11 @@ public class RequestItemRepository
username = user.getFullName();
} else { // An anonymous session may provide a name.
// Escape username to evade nasty XSS attempts
username = StringEscapeUtils.escapeHtml4(rir.getRequestName());
username = HtmlUtils.htmlEscape(rir.getRequestName(),"UTF-8");
}
// Requester's message text, escaped to evade nasty XSS attempts
String message = StringEscapeUtils.escapeHtml4(rir.getRequestMessage());
String message = HtmlUtils.htmlEscape(rir.getRequestMessage(),"UTF-8");
// Create the request.
String token;

View File

@@ -1695,6 +1695,53 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
.andExpect(jsonPath("$.type", is("bitstream")));
}
@Test
public void thumbnailEndpointTestWithSpecialCharactersInFileName() throws Exception {
// Given an Item
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1").build();
Item item = ItemBuilder.createItem(context, col1)
.withTitle("Test item -- thumbnail")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.build();
Bundle originalBundle = BundleBuilder.createBundle(context, item)
.withName(Constants.DEFAULT_BUNDLE_NAME)
.build();
Bundle thumbnailBundle = BundleBuilder.createBundle(context, item)
.withName("THUMBNAIL")
.build();
InputStream is = IOUtils.toInputStream("dummy", "utf-8");
// With an ORIGINAL Bitstream & matching THUMBNAIL Bitstream containing special characters in filenames
Bitstream bitstream = BitstreamBuilder.createBitstream(context, originalBundle, is)
.withName("test (2023) file.pdf")
.withMimeType("application/pdf")
.build();
Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, is)
.withName("test (2023) file.pdf.jpg")
.withMimeType("image/jpeg")
.build();
context.restoreAuthSystemState();
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/thumbnail"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.uuid", Matchers.is(thumbnail.getID().toString())))
.andExpect(jsonPath("$.type", is("bitstream")));
}
@Test
public void thumbnailEndpointMultipleThumbnailsWithPrimaryBitstreamTest() throws Exception {
// Given an Item

View File

@@ -1432,6 +1432,18 @@
<xsl:when test="$lc_dc_type = 'book review'">
<xsl:text>literature</xsl:text>
</xsl:when>
<xsl:when test="$lc_dc_type = 'bachelor thesis'">
<xsl:text>literature</xsl:text>
</xsl:when>
<xsl:when test="$lc_dc_type = 'doctoral thesis'">
<xsl:text>literature</xsl:text>
</xsl:when>
<xsl:when test="$lc_dc_type = 'master thesis'">
<xsl:text>literature</xsl:text>
</xsl:when>
<xsl:when test="$lc_dc_type = 'thesis'">
<xsl:text>literature</xsl:text>
</xsl:when>
<xsl:when test="$lc_dc_type = 'dataset'">
<xsl:text>dataset</xsl:text>
</xsl:when>

View File

@@ -115,6 +115,20 @@
<!-- <dc:identifier xsi:type="dcterms:URI"><xsl:value-of select="." /></dc:identifier> -->
</xsl:for-each>
<!-- ******* Check access-status information to determine the item's access status ******* -->
<xsl:for-each select="doc:metadata/doc:element[@name='others']/doc:element[@name='access-status']/doc:field[@name='value']/text()">
<!-- Add access status information for embargo or restricted status only in uketdterms:embargotype -->
<xsl:if test=".='restricted' or .='embargo'">
<uketdterms:embargotype><xsl:value-of select="." /></uketdterms:embargotype>
</xsl:if>
</xsl:for-each>
<!-- ******* Check access-status embargo information for embargoed content associated with this item ******* -->
<xsl:for-each select="doc:metadata/doc:element[@name='others']/doc:element[@name='access-status']/doc:field[@name='embargo']/text()">
<!-- Add embargo information -->
<uketdterms:embargodate><xsl:value-of select="." /></uketdterms:embargodate>
</xsl:for-each>
<!-- ******* URLs for digital object(s) (obtained from file 'bundles') ******* -->
<xsl:for-each select="doc:metadata/doc:element[@name='bundles']/doc:element[@name='bundle']">

View File

@@ -37,7 +37,7 @@
<jaxb-runtime.version>2.3.8</jaxb-runtime.version>
<jcache-version>1.1.1</jcache-version>
<!-- NOTE: Jetty needed for Solr, Handle Server & tests -->
<jetty.version>9.4.52.v20230823</jetty.version>
<jetty.version>9.4.53.v20231009</jetty.version>
<log4j.version>2.20.0</log4j.version>
<pdfbox-version>2.0.29</pdfbox-version>
<rome.version>1.19.0</rome.version>