Merge remote-tracking branch '4science-bitbucket/main' into CST-5288

This commit is contained in:
Luca Giamminonni
2022-05-11 10:55:42 +02:00
31 changed files with 1529 additions and 63 deletions

View File

@@ -0,0 +1,30 @@
/**
* 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.access.status;
import java.sql.SQLException;
import java.util.Date;
import org.dspace.content.Item;
import org.dspace.core.Context;
/**
* Plugin interface for the access status calculation.
*/
public interface AccessStatusHelper {
/**
* Calculate the access status for the item.
*
* @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 getAccessStatusFromItem(Context context, Item item, Date threshold)
throws SQLException;
}

View File

@@ -0,0 +1,66 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.access.status;
import java.sql.SQLException;
import java.util.Date;
import org.dspace.access.status.service.AccessStatusService;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.core.service.PluginService;
import org.dspace.services.ConfigurationService;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation for the access status calculation service.
*/
public class AccessStatusServiceImpl implements AccessStatusService {
// Plugin implementation, set from the DSpace configuration by init().
protected AccessStatusHelper helper = null;
protected Date forever_date = null;
@Autowired(required = true)
protected ConfigurationService configurationService;
@Autowired(required = true)
protected PluginService pluginService;
/**
* Initialize the bean (after dependency injection has already taken place).
* Ensures the configurationService is injected, so that we can get the plugin
* and the forever embargo date threshold from the configuration.
* Called by "init-method" in Spring configuration.
*
* @throws Exception on generic exception
*/
public void init() throws Exception {
if (helper == null) {
helper = (AccessStatusHelper) pluginService.getSinglePlugin(AccessStatusHelper.class);
if (helper == null) {
throw new IllegalStateException("The AccessStatusHelper plugin was not defined in "
+ "DSpace configuration.");
}
// Defines the embargo forever date threshold for the access status.
// Look at EmbargoService.FOREVER for some improvements?
int year = configurationService.getIntProperty("access.status.embargo.forever.year");
int month = configurationService.getIntProperty("access.status.embargo.forever.month");
int day = configurationService.getIntProperty("access.status.embargo.forever.day");
forever_date = new LocalDate(year, month, day).toDate();
}
}
@Override
public String getAccessStatus(Context context, Item item) throws SQLException {
return helper.getAccessStatusFromItem(context, item, forever_date);
}
}

View File

@@ -0,0 +1,159 @@
/**
* 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.access.status;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.Group;
/**
* Default plugin implementation of the access status helper.
* The getAccessStatusFromItem method provides a simple logic to
* 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.
*/
public class DefaultAccessStatusHelper implements AccessStatusHelper {
public static final String EMBARGO = "embargo";
public static final String METADATA_ONLY = "metadata.only";
public static final String OPEN_ACCESS = "open.access";
public static final String RESTRICTED = "restricted";
public static final String UNKNOWN = "unknown";
protected ItemService itemService =
ContentServiceFactory.getInstance().getItemService();
protected ResourcePolicyService resourcePolicyService =
AuthorizeServiceFactory.getInstance().getResourcePolicyService();
protected AuthorizeService authorizeService =
AuthorizeServiceFactory.getInstance().getAuthorizeService();
public DefaultAccessStatusHelper() {
super();
}
/**
* Look at the item's policies to determine an access status value.
* It is also considering a date threshold for embargos and restrictions.
*
* If the item is null, simply returns the "unknown" value.
*
* @param context the DSpace context
* @param item the item to embargo
* @param threshold the embargo threshold date
* @return an access status value
*/
@Override
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
throws SQLException {
if (item == null) {
return UNKNOWN;
}
// 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);
}
return caculateAccessStatusForDso(context, bitstream, threshold);
}
/**
* Look at the DSpace object's policies to determine an access status value.
*
* If the object is null, returns the "metadata.only" value.
* If any policy attached to the object is valid for the anonymous group,
* returns the "open.access" value.
* Otherwise, if the policy start date is before the embargo threshold date,
* returns the "embargo" value.
* Every other cases return the "restricted" value.
*
* @param context the DSpace context
* @param dso the DSpace object
* @param threshold the embargo threshold date
* @return an access status value
*/
private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
throws SQLException {
if (dso == null) {
return METADATA_ONLY;
}
// Only consider read policies.
List<ResourcePolicy> policies = authorizeService
.getPoliciesActionFilter(context, dso, Constants.READ);
int openAccessCount = 0;
int embargoCount = 0;
int restrictedCount = 0;
int unknownCount = 0;
// Looks at all read policies.
for (ResourcePolicy policy : policies) {
boolean isValid = resourcePolicyService.isDateValid(policy);
Group group = policy.getGroup();
// The group must not be null here. However,
// if it is, consider this as an unexpected case.
if (group == null) {
unknownCount++;
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
// Only calculate the status for the anonymous group.
if (isValid) {
// If the policy is valid, the anonymous group have access
// to the bitstream.
openAccessCount++;
} else {
Date startDate = policy.getStartDate();
if (startDate != null && !startDate.before(threshold)) {
// If the policy start date have a value and if this value
// is equal or superior to the configured forever date, the
// access status is also restricted.
restrictedCount++;
} else {
// If the current date is not between the policy start date
// and end date, the access status is embargo.
embargoCount++;
}
}
}
}
if (openAccessCount > 0) {
return OPEN_ACCESS;
}
if (embargoCount > 0 && restrictedCount == 0) {
return EMBARGO;
}
if (unknownCount > 0) {
return UNKNOWN;
}
return RESTRICTED;
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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.access.status.factory;
import org.dspace.access.status.service.AccessStatusService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* Abstract factory to get services for the access status package,
* use AccessStatusServiceFactory.getInstance() to retrieve an implementation.
*/
public abstract class AccessStatusServiceFactory {
public abstract AccessStatusService getAccessStatusService();
public static AccessStatusServiceFactory getInstance() {
return DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("accessStatusServiceFactory", AccessStatusServiceFactory.class);
}
}

View File

@@ -0,0 +1,26 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.access.status.factory;
import org.dspace.access.status.service.AccessStatusService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Factory implementation to get services for the access status package,
* use AccessStatusServiceFactory.getInstance() to retrieve an implementation.
*/
public class AccessStatusServiceFactoryImpl extends AccessStatusServiceFactory {
@Autowired(required = true)
private AccessStatusService accessStatusService;
@Override
public AccessStatusService getAccessStatusService() {
return accessStatusService;
}
}

View File

@@ -0,0 +1,30 @@
/**
* 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/
*/
/**
* <p>
* Access status allows the users to view the bitstreams availability before
* browsing into the item itself.
* </p>
* <p>
* The access status is calculated through a pluggable class:
* {@link org.dspace.access.status.AccessStatusHelper}.
* The {@link org.dspace.access.status.AccessStatusServiceImpl}
* must be configured to specify this class, as well as a forever embargo date
* threshold year, month and day.
* </p>
* <p>
* See {@link org.dspace.access.status.DefaultAccessStatusHelper} for a simple calculation
* based on the primary or the first bitstream of the original bundle. You can
* supply your own class to implement more complex access statuses.
* </p>
* <p>
* For now, the access status is calculated when the item is shown in a list.
* </p>
*/
package org.dspace.access.status;

View File

@@ -0,0 +1,46 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.access.status.service;
import java.sql.SQLException;
import org.dspace.content.Item;
import org.dspace.core.Context;
/**
* Public interface to the access status subsystem.
* <p>
* Configuration properties: (with examples)
* {@code
* # values for the forever embargo date threshold
* # This threshold date is used in the default access status helper to dermine if an item is
* # restricted or embargoed based on the start date of the primary (or first) file policies.
* # In this case, if the policy start date is inferior to the threshold date, the status will
* # be embargo, else it will be restricted.
* # You might want to change this threshold based on your needs. For example: some databases
* # doesn't accept a date superior to 31 december 9999.
* access.status.embargo.forever.year = 10000
* access.status.embargo.forever.month = 1
* access.status.embargo.forever.day = 1
* # implementation of access status helper plugin - replace with local implementation if applicable
* # This default access status helper provides an item status based on the policies of the primary
* # bitstream (or first bitstream in the original bundles if no primary file is specified).
* plugin.single.org.dspace.access.status.AccessStatusHelper = org.dspace.access.status.DefaultAccessStatusHelper
* }
*/
public interface AccessStatusService {
/**
* Calculate the access status for an Item while considering the forever embargo date threshold.
*
* @param context the DSpace context
* @param item the item
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatus(Context context, Item item) throws SQLException;
}

View File

@@ -561,6 +561,15 @@ public class DCInput {
return true; return true;
} }
/**
* Get the type bind list for use in determining whether
* to display this field in angular dynamic form building
* @return list of bound types
*/
public List<String> getTypeBindList() {
return typeBind;
}
/** /**
* Verify whether the current field contains an entity relationship * Verify whether the current field contains an entity relationship
* This also implies a relationship type is defined for this field * This also implies a relationship type is defined for this field

View File

@@ -140,6 +140,7 @@
<dc-qualifier>ispartofseries</dc-qualifier> <dc-qualifier>ispartofseries</dc-qualifier>
<repeatable>true</repeatable> <repeatable>true</repeatable>
<label>Series/Report No.</label> <label>Series/Report No.</label>
<type-bind>Technical Report</type-bind>
<input-type>series</input-type> <input-type>series</input-type>
<hint>Enter the series and number assigned to this item by your community.</hint> <hint>Enter the series and number assigned to this item by your community.</hint>
<required></required> <required></required>

View File

@@ -0,0 +1,126 @@
/**
* 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.access.status;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import java.sql.SQLException;
import org.apache.logging.log4j.Logger;
import org.dspace.AbstractUnitTest;
import org.dspace.access.status.factory.AccessStatusServiceFactory;
import org.dspace.access.status.service.AccessStatusService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Unit Tests for access status service
*/
public class AccessStatusServiceTest extends AbstractUnitTest {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AccessStatusServiceTest.class);
private Collection collection;
private Community owningCommunity;
private Item item;
protected CommunityService communityService =
ContentServiceFactory.getInstance().getCommunityService();
protected CollectionService collectionService =
ContentServiceFactory.getInstance().getCollectionService();
protected ItemService itemService =
ContentServiceFactory.getInstance().getItemService();
protected WorkspaceItemService workspaceItemService =
ContentServiceFactory.getInstance().getWorkspaceItemService();
protected InstallItemService installItemService =
ContentServiceFactory.getInstance().getInstallItemService();
protected AccessStatusService accessStatusService =
AccessStatusServiceFactory.getInstance().getAccessStatusService();
/**
* This method will be run before every test as per @Before. It will
* initialize resources required for the tests.
*
* Other methods can be annotated with @Before here or in subclasses
* but no execution order is guaranteed
*/
@Before
@Override
public void init() {
super.init();
try {
context.turnOffAuthorisationSystem();
owningCommunity = communityService.create(null, context);
collection = collectionService.create(context, owningCommunity);
item = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
context.restoreAuthSystemState();
} catch (AuthorizeException ex) {
log.error("Authorization Error in init", ex);
fail("Authorization Error in init: " + ex.getMessage());
} catch (SQLException ex) {
log.error("SQL Error in init", ex);
fail("SQL Error in init: " + ex.getMessage());
}
}
/**
* This method will be run after every test as per @After. It will
* clean resources initialized by the @Before methods.
*
* Other methods can be annotated with @After here or in subclasses
* but no execution order is guaranteed
*/
@After
@Override
public void destroy() {
context.turnOffAuthorisationSystem();
try {
itemService.delete(context, item);
} catch (Exception e) {
// ignore
}
try {
collectionService.delete(context, collection);
} catch (Exception e) {
// ignore
}
try {
communityService.delete(context, owningCommunity);
} catch (Exception e) {
// ignore
}
context.restoreAuthSystemState();
item = null;
collection = null;
owningCommunity = null;
try {
super.destroy();
} catch (Exception e) {
// ignore
}
}
@Test
public void testGetAccessStatus() throws Exception {
String status = accessStatusService.getAccessStatus(context, item);
assertNotEquals("testGetAccessStatus 0", status, DefaultAccessStatusHelper.UNKNOWN);
}
}

View File

@@ -0,0 +1,423 @@
/**
* 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.access.status;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.AbstractUnitTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.joda.time.LocalDate;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
private static final Logger log = LogManager.getLogger(DefaultAccessStatusHelperTest.class);
private Collection collection;
private Community owningCommunity;
private Item itemWithoutBundle;
private Item itemWithoutBitstream;
private Item itemWithBitstream;
private Item itemWithEmbargo;
private Item itemWithDateRestriction;
private Item itemWithGroupRestriction;
private Item itemWithoutPolicy;
private Item itemWithoutPrimaryBitstream;
private Item itemWithPrimaryAndMultipleBitstreams;
private Item itemWithoutPrimaryAndMultipleBitstreams;
private DefaultAccessStatusHelper helper;
private Date threshold;
protected CommunityService communityService =
ContentServiceFactory.getInstance().getCommunityService();
protected CollectionService collectionService =
ContentServiceFactory.getInstance().getCollectionService();
protected ItemService itemService =
ContentServiceFactory.getInstance().getItemService();
protected WorkspaceItemService workspaceItemService =
ContentServiceFactory.getInstance().getWorkspaceItemService();
protected InstallItemService installItemService =
ContentServiceFactory.getInstance().getInstallItemService();
protected BundleService bundleService =
ContentServiceFactory.getInstance().getBundleService();
protected BitstreamService bitstreamService =
ContentServiceFactory.getInstance().getBitstreamService();
protected ResourcePolicyService resourcePolicyService =
AuthorizeServiceFactory.getInstance().getResourcePolicyService();
protected GroupService groupService =
EPersonServiceFactory.getInstance().getGroupService();
/**
* This method will be run before every test as per @Before. It will
* initialize resources required for the tests.
*
* Other methods can be annotated with @Before here or in subclasses
* but no execution order is guaranteed
*/
@Before
@Override
public void init() {
super.init();
try {
context.turnOffAuthorisationSystem();
owningCommunity = communityService.create(null, context);
collection = collectionService.create(context, owningCommunity);
itemWithoutBundle = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithoutBitstream = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithBitstream = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithEmbargo = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithDateRestriction = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithGroupRestriction = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithoutPolicy = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithoutPrimaryBitstream = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithPrimaryAndMultipleBitstreams = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
itemWithoutPrimaryAndMultipleBitstreams = installItemService.installItem(context,
workspaceItemService.create(context, collection, true));
context.restoreAuthSystemState();
} catch (AuthorizeException ex) {
log.error("Authorization Error in init", ex);
fail("Authorization Error in init: " + ex.getMessage());
} catch (SQLException ex) {
log.error("SQL Error in init", ex);
fail("SQL Error in init: " + ex.getMessage());
}
helper = new DefaultAccessStatusHelper();
threshold = new LocalDate(10000, 1, 1).toDate();
}
/**
* This method will be run after every test as per @After. It will
* clean resources initialized by the @Before methods.
*
* Other methods can be annotated with @After here or in subclasses
* but no execution order is guaranteed
*/
@After
@Override
public void destroy() {
context.turnOffAuthorisationSystem();
try {
itemService.delete(context, itemWithoutBundle);
itemService.delete(context, itemWithoutBitstream);
itemService.delete(context, itemWithBitstream);
itemService.delete(context, itemWithEmbargo);
itemService.delete(context, itemWithDateRestriction);
itemService.delete(context, itemWithGroupRestriction);
itemService.delete(context, itemWithoutPolicy);
itemService.delete(context, itemWithoutPrimaryBitstream);
itemService.delete(context, itemWithPrimaryAndMultipleBitstreams);
itemService.delete(context, itemWithoutPrimaryAndMultipleBitstreams);
} catch (Exception e) {
// ignore
}
try {
collectionService.delete(context, collection);
} catch (Exception e) {
// ignore
}
try {
communityService.delete(context, owningCommunity);
} catch (Exception e) {
// ignore
}
context.restoreAuthSystemState();
itemWithoutBundle = null;
itemWithoutBitstream = null;
itemWithBitstream = null;
itemWithEmbargo = null;
itemWithDateRestriction = null;
itemWithGroupRestriction = null;
itemWithoutPolicy = null;
itemWithoutPrimaryBitstream = null;
itemWithPrimaryAndMultipleBitstreams = null;
itemWithoutPrimaryAndMultipleBitstreams = null;
collection = null;
owningCommunity = null;
helper = null;
threshold = null;
communityService = null;
collectionService = null;
itemService = null;
workspaceItemService = null;
installItemService = null;
bundleService = null;
bitstreamService = null;
resourcePolicyService = null;
groupService = null;
try {
super.destroy();
} catch (Exception e) {
// ignore
}
}
/**
* Test for a null item
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithNullItem() throws Exception {
String status = helper.getAccessStatusFromItem(context, null, threshold);
assertThat("testWithNullItem 0", status, equalTo(DefaultAccessStatusHelper.UNKNOWN));
}
/**
* Test for an item with no bundle
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithoutBundle() throws Exception {
String status = helper.getAccessStatusFromItem(context, itemWithoutBundle, threshold);
assertThat("testWithoutBundle 0", status, equalTo(DefaultAccessStatusHelper.METADATA_ONLY));
}
/**
* Test for an item with no bitstream
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithoutBitstream() throws Exception {
context.turnOffAuthorisationSystem();
bundleService.create(context, itemWithoutBitstream, Constants.CONTENT_BUNDLE_NAME);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithoutBitstream, threshold);
assertThat("testWithoutBitstream 0", status, equalTo(DefaultAccessStatusHelper.METADATA_ONLY));
}
/**
* Test for an item with a basic bitstream (open access)
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithBitstream() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithBitstream, Constants.CONTENT_BUNDLE_NAME);
Bitstream bitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bitstream.setName(context, "primary");
bundle.setPrimaryBitstreamID(bitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithBitstream, threshold);
assertThat("testWithBitstream 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS));
}
/**
* Test for an item with an embargo
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithEmbargo() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithEmbargo, Constants.CONTENT_BUNDLE_NAME);
Bitstream bitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bitstream.setName(context, "primary");
bundle.setPrimaryBitstreamID(bitstream);
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Embargo");
Group group = groupService.findByName(context, Group.ANONYMOUS);
policy.setGroup(group);
policy.setAction(Constants.READ);
policy.setStartDate(new LocalDate(9999, 12, 31).toDate());
policies.add(policy);
authorizeService.removeAllPolicies(context, bitstream);
authorizeService.addPolicies(context, policies, bitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold);
assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO));
}
/**
* Test for an item with an anonymous date restriction
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithDateRestriction() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithDateRestriction, Constants.CONTENT_BUNDLE_NAME);
Bitstream bitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bitstream.setName(context, "primary");
bundle.setPrimaryBitstreamID(bitstream);
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Restriction");
Group group = groupService.findByName(context, Group.ANONYMOUS);
policy.setGroup(group);
policy.setAction(Constants.READ);
policy.setStartDate(new LocalDate(10000, 1, 1).toDate());
policies.add(policy);
authorizeService.removeAllPolicies(context, bitstream);
authorizeService.addPolicies(context, policies, bitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithDateRestriction, threshold);
assertThat("testWithDateRestriction 0", status, equalTo(DefaultAccessStatusHelper.RESTRICTED));
}
/**
* Test for an item with a group restriction
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithGroupRestriction() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithGroupRestriction, Constants.CONTENT_BUNDLE_NAME);
Bitstream bitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bitstream.setName(context, "primary");
bundle.setPrimaryBitstreamID(bitstream);
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Restriction");
Group group = groupService.findByName(context, Group.ADMIN);
policy.setGroup(group);
policy.setAction(Constants.READ);
policies.add(policy);
authorizeService.removeAllPolicies(context, bitstream);
authorizeService.addPolicies(context, policies, bitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithGroupRestriction, threshold);
assertThat("testWithGroupRestriction 0", status, equalTo(DefaultAccessStatusHelper.RESTRICTED));
}
/**
* Test for an item with no policy
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithoutPolicy() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithoutPolicy, Constants.CONTENT_BUNDLE_NAME);
Bitstream bitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bitstream.setName(context, "primary");
bundle.setPrimaryBitstreamID(bitstream);
authorizeService.removeAllPolicies(context, bitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithoutPolicy, threshold);
assertThat("testWithoutPolicy 0", status, equalTo(DefaultAccessStatusHelper.RESTRICTED));
}
/**
* Test for an item with no primary bitstream
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithoutPrimaryBitstream() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithoutPrimaryBitstream, Constants.CONTENT_BUNDLE_NAME);
Bitstream bitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bitstream.setName(context, "first");
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryBitstream, threshold);
assertThat("testWithoutPrimaryBitstream 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS));
}
/**
* Test for an item with an open access bitstream
* and another primary bitstream on embargo
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithPrimaryAndMultipleBitstreams() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithPrimaryAndMultipleBitstreams,
Constants.CONTENT_BUNDLE_NAME);
bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
Bitstream primaryBitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
bundle.setPrimaryBitstreamID(primaryBitstream);
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Embargo");
Group group = groupService.findByName(context, Group.ANONYMOUS);
policy.setGroup(group);
policy.setAction(Constants.READ);
policy.setStartDate(new LocalDate(9999, 12, 31).toDate());
policies.add(policy);
authorizeService.removeAllPolicies(context, primaryBitstream);
authorizeService.addPolicies(context, policies, primaryBitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold);
assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO));
}
/**
* Test for an item with an open access bitstream
* and another bitstream on embargo
* @throws java.lang.Exception passed through.
*/
@Test
public void testWithNoPrimaryAndMultipleBitstreams() throws Exception {
context.turnOffAuthorisationSystem();
Bundle bundle = bundleService.create(context, itemWithoutPrimaryAndMultipleBitstreams,
Constants.CONTENT_BUNDLE_NAME);
bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
Bitstream anotherBitstream = bitstreamService.create(context, bundle,
new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8)));
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Embargo");
Group group = groupService.findByName(context, Group.ANONYMOUS);
policy.setGroup(group);
policy.setAction(Constants.READ);
policy.setStartDate(new LocalDate(9999, 12, 31).toDate());
policies.add(policy);
authorizeService.removeAllPolicies(context, anotherBitstream);
authorizeService.addPolicies(context, policies, anotherBitstream);
context.restoreAuthSystemState();
String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold);
assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS));
}
}

View File

@@ -155,6 +155,7 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
inputField.setInput(inputRest); inputField.setInput(inputRest);
if (dcinput.isMetadataField()) { if (dcinput.isMetadataField()) {
inputField.setSelectableMetadata(selectableMetadata); inputField.setSelectableMetadata(selectableMetadata);
inputField.setTypeBind(dcinput.getTypeBindList());
} }
if (dcinput.isRelationshipField()) { if (dcinput.isRelationshipField()) {
selectableRelationship = getSelectableRelationships(dcinput); selectableRelationship = getSelectableRelationships(dcinput);

View File

@@ -0,0 +1,49 @@
/**
* 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.app.rest.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
/**
* The Access Status REST Resource.
*/
public class AccessStatusRest implements RestModel {
public static final String NAME = "accessStatus";
String status;
@Override
@JsonProperty(access = Access.READ_ONLY)
public String getType() {
return NAME;
}
@Override
@JsonIgnore
public String getTypePlural() {
return getType();
}
public AccessStatusRest() {
setStatus(null);
}
public AccessStatusRest(String status) {
setStatus(status);
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -17,6 +17,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @author Andrea Bollini (andrea.bollini at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it)
*/ */
@LinksRest(links = { @LinksRest(links = {
@LinkRest(
name = ItemRest.ACCESS_STATUS,
method = "getAccessStatus"
),
@LinkRest( @LinkRest(
name = ItemRest.BUNDLES, name = ItemRest.BUNDLES,
method = "getBundles" method = "getBundles"
@@ -51,6 +55,7 @@ public class ItemRest extends DSpaceObjectRest {
public static final String PLURAL_NAME = "items"; public static final String PLURAL_NAME = "items";
public static final String CATEGORY = RestAddressableModel.CORE; public static final String CATEGORY = RestAddressableModel.CORE;
public static final String ACCESS_STATUS = "accessStatus";
public static final String BUNDLES = "bundles"; public static final String BUNDLES = "bundles";
public static final String MAPPED_COLLECTIONS = "mappedCollections"; public static final String MAPPED_COLLECTIONS = "mappedCollections";
public static final String OWNING_COLLECTION = "owningCollection"; public static final String OWNING_COLLECTION = "owningCollection";

View File

@@ -83,6 +83,11 @@ public class SubmissionFormFieldRest {
*/ */
private List<LanguageFormField> languageCodes; private List<LanguageFormField> languageCodes;
/**
* The list of type bind value
*/
private List<String> typeBind;
/** /**
* Getter for {@link #selectableMetadata} * Getter for {@link #selectableMetadata}
* *
@@ -266,6 +271,14 @@ public class SubmissionFormFieldRest {
} }
} }
public List<String> getTypeBind() {
return typeBind;
}
public void setTypeBind(List<String> typeBind) {
this.typeBind = typeBind;
}
public SelectableRelationship getSelectableRelationship() { public SelectableRelationship getSelectableRelationship() {
return selectableRelationship; return selectableRelationship;
} }

View File

@@ -0,0 +1,31 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.dspace.app.rest.model.AccessStatusRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
/**
* Access Status Rest HAL Resource. The HAL Resource wraps the REST Resource
* adding support for the links and embedded resources
*/
@RelNameDSpaceResource(AccessStatusRest.NAME)
public class AccessStatusResource extends HALResource<AccessStatusRest> {
@JsonUnwrapped
private AccessStatusRest data;
public AccessStatusResource(AccessStatusRest entry) {
super(entry);
}
public AccessStatusRest getData() {
return data;
}
}

View File

@@ -0,0 +1,61 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.dspace.access.status.service.AccessStatusService;
import org.dspace.app.rest.model.AccessStatusRest;
import org.dspace.app.rest.model.ItemRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for calculating the access status of an Item
*/
@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.ACCESS_STATUS)
public class ItemAccessStatusLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
ItemService itemService;
@Autowired
AccessStatusService accessStatusService;
@PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')")
public AccessStatusRest getAccessStatus(@Nullable HttpServletRequest request,
UUID itemId,
@Nullable Pageable optionalPageable,
Projection projection) {
try {
Context context = obtainContext();
Item item = itemService.find(context, itemId);
if (item == null) {
throw new ResourceNotFoundException("No such item: " + itemId);
}
AccessStatusRest accessStatusRest = new AccessStatusRest();
String accessStatus = accessStatusService.getAccessStatus(context, item);
accessStatusRest.setStatus(accessStatus);
return accessStatusRest;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -31,6 +31,8 @@ import org.dspace.content.InProgressSubmission;
import org.dspace.content.MetadataValue; import org.dspace.content.MetadataValue;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.Utils; import org.dspace.core.Utils;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/** /**
* Describe step for DSpace Spring Rest. Expose and allow patching of the in progress submission metadata. It is * Describe step for DSpace Spring Rest. Expose and allow patching of the in progress submission metadata. It is
@@ -43,7 +45,11 @@ public class DescribeStep extends AbstractProcessingStep {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DescribeStep.class); private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DescribeStep.class);
// Input reader for form configuration
private DCInputsReader inputReader; private DCInputsReader inputReader;
// Configuration service
private final ConfigurationService configurationService =
DSpaceServicesFactory.getInstance().getConfigurationService();
public DescribeStep() throws DCInputsReaderException { public DescribeStep() throws DCInputsReaderException {
inputReader = new DCInputsReader(); inputReader = new DCInputsReader();
@@ -64,8 +70,17 @@ public class DescribeStep extends AbstractProcessingStep {
private void readField(InProgressSubmission obj, SubmissionStepConfig config, DataDescribe data, private void readField(InProgressSubmission obj, SubmissionStepConfig config, DataDescribe data,
DCInputSet inputConfig) throws DCInputsReaderException { DCInputSet inputConfig) throws DCInputsReaderException {
String documentTypeValue = "";
List<MetadataValue> documentType = itemService.getMetadataByMetadataString(obj.getItem(),
configurationService.getProperty("submit.type-bind.field", "dc.type"));
if (documentType.size() > 0) {
documentTypeValue = documentType.get(0).getValue();
}
for (DCInput[] row : inputConfig.getFields()) { for (DCInput[] row : inputConfig.getFields()) {
for (DCInput input : row) { for (DCInput input : row) {
// Is this input allowed for the document type, as per type bind config? If there is no type
// bind set, this is always true
boolean allowed = input.isAllowedFor(documentTypeValue);
List<String> fieldsName = new ArrayList<String>(); List<String> fieldsName = new ArrayList<String>();
if (input.isQualdropValue()) { if (input.isQualdropValue()) {
@@ -91,20 +106,30 @@ public class DescribeStep extends AbstractProcessingStep {
String[] metadataToCheck = Utils.tokenize(md.getMetadataField().toString()); String[] metadataToCheck = Utils.tokenize(md.getMetadataField().toString());
if (data.getMetadata().containsKey( if (data.getMetadata().containsKey(
Utils.standardize(metadataToCheck[0], metadataToCheck[1], metadataToCheck[2], "."))) { Utils.standardize(metadataToCheck[0], metadataToCheck[1], metadataToCheck[2], "."))) {
data.getMetadata() // If field is allowed by type bind, add value to existing field set, otherwise remove
.get(Utils.standardize(md.getMetadataField().getMetadataSchema().getName(), // all values for this field
md.getMetadataField().getElement(), if (allowed) {
md.getMetadataField().getQualifier(), data.getMetadata()
".")) .get(Utils.standardize(md.getMetadataField().getMetadataSchema().getName(),
.add(dto); md.getMetadataField().getElement(),
md.getMetadataField().getQualifier(),
"."))
.add(dto);
} else {
data.getMetadata().remove(Utils.standardize(metadataToCheck[0], metadataToCheck[1],
metadataToCheck[2], "."));
}
} else { } else {
List<MetadataValueRest> listDto = new ArrayList<>(); // Add values only if allowed by type bind
listDto.add(dto); if (allowed) {
data.getMetadata() List<MetadataValueRest> listDto = new ArrayList<>();
.put(Utils.standardize(md.getMetadataField().getMetadataSchema().getName(), listDto.add(dto);
md.getMetadataField().getElement(), data.getMetadata()
md.getMetadataField().getQualifier(), .put(Utils.standardize(md.getMetadataField().getMetadataSchema().getName(),
"."), listDto); md.getMetadataField().getElement(),
md.getMetadataField().getQualifier(),
"."), listDto);
}
} }
} }
} }

View File

@@ -16,6 +16,7 @@ import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.ErrorRest;
import org.dspace.app.rest.repository.WorkspaceItemRestRepository; import org.dspace.app.rest.repository.WorkspaceItemRestRepository;
import org.dspace.app.rest.submit.SubmissionService; import org.dspace.app.rest.submit.SubmissionService;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.app.util.DCInput; import org.dspace.app.util.DCInput;
import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReader;
@@ -25,6 +26,7 @@ import org.dspace.content.InProgressSubmission;
import org.dspace.content.MetadataValue; import org.dspace.content.MetadataValue;
import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.authority.service.MetadataAuthorityService;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.services.ConfigurationService;
/** /**
* Execute three validation check on fields validation: * Execute three validation check on fields validation:
@@ -50,12 +52,20 @@ public class MetadataValidation extends AbstractValidation {
private MetadataAuthorityService metadataAuthorityService; private MetadataAuthorityService metadataAuthorityService;
private ConfigurationService configurationService;
@Override @Override
public List<ErrorRest> validate(SubmissionService submissionService, InProgressSubmission obj, public List<ErrorRest> validate(SubmissionService submissionService, InProgressSubmission obj,
SubmissionStepConfig config) throws DCInputsReaderException, SQLException { SubmissionStepConfig config) throws DCInputsReaderException, SQLException {
List<ErrorRest> errors = new ArrayList<>(); List<ErrorRest> errors = new ArrayList<>();
String documentTypeValue = "";
DCInputSet inputConfig = getInputReader().getInputsByFormName(config.getId()); DCInputSet inputConfig = getInputReader().getInputsByFormName(config.getId());
List<MetadataValue> documentType = itemService.getMetadataByMetadataString(obj.getItem(),
configurationService.getProperty("submit.type-bind.field", "dc.type"));
if (documentType.size() > 0) {
documentTypeValue = documentType.get(0).getValue();
}
for (DCInput[] row : inputConfig.getFields()) { for (DCInput[] row : inputConfig.getFields()) {
for (DCInput input : row) { for (DCInput input : row) {
String fieldKey = String fieldKey =
@@ -71,12 +81,21 @@ public class MetadataValidation extends AbstractValidation {
for (int i = 1; i < inputPairs.size(); i += 2) { for (int i = 1; i < inputPairs.size(); i += 2) {
String fullFieldname = input.getFieldName() + "." + (String) inputPairs.get(i); String fullFieldname = input.getFieldName() + "." + (String) inputPairs.get(i);
List<MetadataValue> mdv = itemService.getMetadataByMetadataString(obj.getItem(), fullFieldname); List<MetadataValue> mdv = itemService.getMetadataByMetadataString(obj.getItem(), fullFieldname);
validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors); // If the input is not allowed for this type, strip it from item metadata.
if (mdv.size() > 0 && input.isVisible(DCInput.SUBMISSION_SCOPE)) { if (!input.isAllowedFor(documentTypeValue)) {
foundResult = true; itemService.removeMetadataValues(ContextUtil.obtainCurrentRequestContext(),
obj.getItem(), mdv);
} else {
validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors);
if (mdv.size() > 0 && input.isVisible(DCInput.SUBMISSION_SCOPE)) {
foundResult = true;
}
} }
} }
if (input.isRequired() && ! foundResult) { // If the input is required but not allowed for this type, and we removed, don't throw
// an error - this way, a field can be required for "Book" to which it is bound, but not
// other types. A user may have switched between types before a final deposit
if (input.isRequired() && !foundResult && input.isAllowedFor(documentTypeValue)) {
// for this required qualdrop no value was found, add to the list of error fields // for this required qualdrop no value was found, add to the list of error fields
addError(errors, ERROR_VALIDATION_REQUIRED, addError(errors, ERROR_VALIDATION_REQUIRED,
"/" + WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" + "/" + WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" +
@@ -89,6 +108,12 @@ public class MetadataValidation extends AbstractValidation {
for (String fieldName : fieldsName) { for (String fieldName : fieldsName) {
List<MetadataValue> mdv = itemService.getMetadataByMetadataString(obj.getItem(), fieldName); List<MetadataValue> mdv = itemService.getMetadataByMetadataString(obj.getItem(), fieldName);
if (!input.isAllowedFor(documentTypeValue)) {
itemService.removeMetadataValues(ContextUtil.obtainCurrentRequestContext(), obj.getItem(), mdv);
// Continue here, this skips the required check since we've just removed values that previously
// appeared, and the configuration already indicates this field shouldn't be included
continue;
}
validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors); validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors);
if ((input.isRequired() && mdv.size() == 0) && input.isVisible(DCInput.SUBMISSION_SCOPE)) { if ((input.isRequired() && mdv.size() == 0) && input.isVisible(DCInput.SUBMISSION_SCOPE)) {
// since this field is missing add to list of error // since this field is missing add to list of error
@@ -124,6 +149,10 @@ public class MetadataValidation extends AbstractValidation {
} }
} }
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
public void setItemService(ItemService itemService) { public void setItemService(ItemService itemService) {
this.itemService = itemService; this.itemService = itemService;
} }

View File

@@ -16,6 +16,7 @@
<bean name="metadataValidation" class="org.dspace.app.rest.submit.step.validation.MetadataValidation" <bean name="metadataValidation" class="org.dspace.app.rest.submit.step.validation.MetadataValidation"
scope="prototype"> scope="prototype">
<property name="name" value="submission-form"/> <property name="name" value="submission-form"/>
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
<property name="itemService" ref="org.dspace.content.ItemServiceImpl"/> <property name="itemService" ref="org.dspace.content.ItemServiceImpl"/>
<property name="metadataAuthorityService" ref="org.dspace.content.authority.MetadataAuthorityServiceImpl"/> <property name="metadataAuthorityService" ref="org.dspace.content.authority.MetadataAuthorityServiceImpl"/>
</bean> </bean>

View File

@@ -12,6 +12,7 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist;
import static org.dspace.core.Constants.WRITE; import static org.dspace.core.Constants.WRITE;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
@@ -3861,6 +3862,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$.inArchive", Matchers.is(false))) .andExpect(jsonPath("$.inArchive", Matchers.is(false)))
.andExpect(jsonPath("$._links.self.href", .andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items/" + item.getID().toString()))) Matchers.containsString("/api/core/items/" + item.getID().toString())))
.andExpect(jsonPath("$._links.accessStatus.href",
Matchers.containsString("/api/core/items/" + item.getID().toString() + "/accessStatus")))
.andExpect(jsonPath("$._links.bundles.href", .andExpect(jsonPath("$._links.bundles.href",
Matchers.containsString("/api/core/items/" + item.getID().toString() + "/bundles"))) Matchers.containsString("/api/core/items/" + item.getID().toString() + "/bundles")))
.andExpect(jsonPath("$._links.mappedCollections.href", .andExpect(jsonPath("$._links.mappedCollections.href",
@@ -3893,6 +3896,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$.inArchive", Matchers.is(false))) .andExpect(jsonPath("$.inArchive", Matchers.is(false)))
.andExpect(jsonPath("$._links.self.href", .andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items/" + item.getID().toString()))) Matchers.containsString("/api/core/items/" + item.getID().toString())))
.andExpect(jsonPath("$._links.accessStatus.href",
Matchers.containsString("/api/core/items/" + item.getID().toString() + "/accessStatus")))
.andExpect(jsonPath("$._links.bundles.href", .andExpect(jsonPath("$._links.bundles.href",
Matchers.containsString("/api/core/items/" + item.getID().toString() + "/bundles"))) Matchers.containsString("/api/core/items/" + item.getID().toString() + "/bundles")))
.andExpect(jsonPath("$._links.mappedCollections.href", .andExpect(jsonPath("$._links.mappedCollections.href",
@@ -3926,6 +3931,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
Matchers.containsString("/api/core/items/" + item.getID().toString()))) Matchers.containsString("/api/core/items/" + item.getID().toString())))
.andExpect(jsonPath("$._links.self.href", .andExpect(jsonPath("$._links.self.href",
Matchers.containsString("/api/core/items/" + item.getID().toString()))) Matchers.containsString("/api/core/items/" + item.getID().toString())))
.andExpect(jsonPath("$._links.accessStatus.href",
Matchers.containsString("/api/core/items/" + item.getID().toString() + "/accessStatus")))
.andExpect(jsonPath("$._links.bundles.href", .andExpect(jsonPath("$._links.bundles.href",
Matchers.containsString("/api/core/items/" + item.getID().toString() + "/bundles"))) Matchers.containsString("/api/core/items/" + item.getID().toString() + "/bundles")))
.andExpect(jsonPath("$._links.mappedCollections.href", .andExpect(jsonPath("$._links.mappedCollections.href",
@@ -4376,4 +4383,35 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test
public void findAccessStatusForItemBadRequestTest() throws Exception {
getClient().perform(get("/api/core/items/{uuid}/accessStatus", "1"))
.andExpect(status().isBadRequest());
}
@Test
public void findAccessStatusForItemNotFoundTest() throws Exception {
UUID fakeUUID = UUID.randomUUID();
getClient().perform(get("/api/core/items/{uuid}/accessStatus", fakeUUID))
.andExpect(status().isNotFound());
}
@Test
public void findAccessStatusForItemTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection owningCollection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Owning Collection")
.build();
Item item = ItemBuilder.createItem(context, owningCollection)
.withTitle("Test item")
.build();
context.restoreAuthSystemState();
getClient().perform(get("/api/core/items/{uuid}/accessStatus", item.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", notNullValue()));
}
} }

View File

@@ -11,6 +11,7 @@ import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -113,20 +114,20 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone"))) .startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone")))
// check the first two rows // check the first two rows
.andExpect(jsonPath("$.rows[0].fields", contains( .andExpect(jsonPath("$.rows[0].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("name", "Author", SubmissionFormFieldMatcher.matchFormFieldDefinition("name", "Author", null,
null, true,"Add an author", "dc.contributor.author")))) null, true,"Add an author", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains( .andExpect(jsonPath("$.rows[1].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Title", SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Title", null,
"You must enter a main title for this item.", false, "You must enter a main title for this item.", false,
"Enter the main title of the item.", "dc.title")))) "Enter the main title of the item.", "dc.title"))))
// check a row with multiple fields // check a row with multiple fields
.andExpect(jsonPath("$.rows[3].fields", .andExpect(jsonPath("$.rows[3].fields",
contains( contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("date", "Date of Issue", SubmissionFormFieldMatcher.matchFormFieldDefinition("date", "Date of Issue",
"You must enter at least the year.", false, null, "You must enter at least the year.", false,
"Please give the date", "col-sm-4", "Please give the date", "col-sm-4",
"dc.date.issued"), "dc.date.issued"),
SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Publisher", SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Publisher", null,
null, false,"Enter the name of", null, false,"Enter the name of",
"col-sm-8","dc.publisher")))) "col-sm-8","dc.publisher"))))
; ;
@@ -144,18 +145,18 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone"))) .startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone")))
.andExpect(jsonPath("$.rows[0].fields", contains( .andExpect(jsonPath("$.rows[0].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("name", "Author", SubmissionFormFieldMatcher.matchFormFieldDefinition("name", "Author", null,
null, true,"Add an author", "dc.contributor.author")))) null, true,"Add an author", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains( .andExpect(jsonPath("$.rows[1].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Title", SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Title", null,
"You must enter a main title for this item.", false, "You must enter a main title for this item.", false,
"Enter the main title of the item.", "dc.title")))) "Enter the main title of the item.", "dc.title"))))
.andExpect(jsonPath("$.rows[3].fields",contains( .andExpect(jsonPath("$.rows[3].fields",contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("date", "Date of Issue", SubmissionFormFieldMatcher.matchFormFieldDefinition("date", "Date of Issue", null,
"You must enter at least the year.", false, "You must enter at least the year.", false,
"Please give the date", "col-sm-4", "Please give the date", "col-sm-4",
"dc.date.issued"), "dc.date.issued"),
SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Publisher", SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Publisher", null,
null, false,"Enter the name of", null, false,"Enter the name of",
"col-sm-8","dc.publisher")))); "col-sm-8","dc.publisher"))));
} }
@@ -220,20 +221,20 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
// dc.subject fields with in separate rows all linked to an authority with different // dc.subject fields with in separate rows all linked to an authority with different
// presentation modes (suggestion, name-lookup, lookup) // presentation modes (suggestion, name-lookup, lookup)
.andExpect(jsonPath("$.rows[0].fields", contains( .andExpect(jsonPath("$.rows[0].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Author", SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Author", null,
null, true, null, true,
"Author field that can be associated with an authority providing suggestion", "Author field that can be associated with an authority providing suggestion",
null, "dc.contributor.author", "SolrAuthorAuthority") null, "dc.contributor.author", "SolrAuthorAuthority")
))) )))
.andExpect(jsonPath("$.rows[1].fields", contains( .andExpect(jsonPath("$.rows[1].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("lookup-name", "Editor", SubmissionFormFieldMatcher.matchFormFieldDefinition("lookup-name", "Editor", null,
null, false, null, false,
"Editor field that can be associated with an authority " "Editor field that can be associated with an authority "
+ "providing the special name lookup", + "providing the special name lookup",
null, "dc.contributor.editor", "SolrEditorAuthority") null, "dc.contributor.editor", "SolrEditorAuthority")
))) )))
.andExpect(jsonPath("$.rows[2].fields", contains( .andExpect(jsonPath("$.rows[2].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("lookup", "Subject", SubmissionFormFieldMatcher.matchFormFieldDefinition("lookup", "Subject", null,
null, true, null, true,
"Subject field that can be associated with an authority providing lookup", "Subject field that can be associated with an authority providing lookup",
null, "dc.subject", "SolrSubjectAuthority") null, "dc.subject", "SolrSubjectAuthority")
@@ -266,7 +267,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone"))) .startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone")))
// our test configuration include the dc.type field with a value pair in the 8th row // our test configuration include the dc.type field with a value pair in the 8th row
.andExpect(jsonPath("$.rows[7].fields", contains( .andExpect(jsonPath("$.rows[7].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("dropdown", "Type", SubmissionFormFieldMatcher.matchFormFieldDefinition("dropdown", "Type", null,
null, true, null, true,
"Select the type(s) of content of the item. To select more than one value in the " + "Select the type(s) of content of the item. To select more than one value in the " +
"list, you may have to hold down the \"CTRL\" or \"Shift\" key.", "list, you may have to hold down the \"CTRL\" or \"Shift\" key.",
@@ -275,6 +276,35 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
; ;
} }
@Test
public void findFieldWithTypeBindConfig() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissionforms/traditionalpageone"))
// The status has to be 200 OK
.andExpect(status().isOk())
// We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
// Check that the JSON root matches the expected "traditionalpageone" input forms
.andExpect(jsonPath("$.id", is("traditionalpageone")))
.andExpect(jsonPath("$.name", is("traditionalpageone")))
.andExpect(jsonPath("$.type", is("submissionform")))
.andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone")))
// check a row with type-bind 'Technical Report'
.andExpect(jsonPath("$.rows[5].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("series", "Series/Report No.",
"Technical Report", null, true,
"Enter the series and number assigned to this item by your community.",
"dc.relation.ispartofseries"))))
// check the same row with a NON-matching type-bind 'Article' (expect false)
.andExpect(((jsonPath("$.rows[5].fields", not(contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("series", "Series/Report No.",
"Article", null, true,
"Enter the series and number assigned to this item by your community.",
"dc.relation.ispartofseries")))))));
}
@Test @Test
public void findOpenRelationshipConfig() throws Exception { public void findOpenRelationshipConfig() throws Exception {
String token = getAuthToken(admin.getEmail(), password); String token = getAuthToken(admin.getEmail(), password);
@@ -352,14 +382,15 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Autore", "\u00C8" + " richiesto almeno un autore", true, .matchFormFieldDefinition("name", "Autore", null,
"\u00C8" + " richiesto almeno un autore", true,
"Aggiungi un autore", "dc.contributor.author")))) "Aggiungi un autore", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Titolo", .matchFormFieldDefinition("onebox", "Titolo", null,
"\u00C8" + " necessario inserire un titolo principale per questo item", false, "\u00C8" + " necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title")))) "Inserisci titolo principale di questo item", "dc.title"))))
.andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("dropdown", "Lingua", null, false, .matchFormFieldDefinition("dropdown", "Lingua", null, null, false,
"Selezionare la lingua del contenuto principale dell'item." "Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)." + " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua" + " Se il contenuto non ha davvero una lingua"
@@ -376,14 +407,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Автор", "Потрібно ввести хочаб одного автора!", .matchFormFieldDefinition("name", "Автор", null, "Потрібно ввести хочаб одного автора!",
true, "Додати автора", "dc.contributor.author")))) true, "Додати автора", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Заголовок", .matchFormFieldDefinition("onebox", "Заголовок", null,
"Заговолок файла обов'язковий !", false, "Заговолок файла обов'язковий !", false,
"Ввести основний заголовок файла", "dc.title")))) "Ввести основний заголовок файла", "dc.title"))))
.andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("dropdown", "Мова", null, false, .matchFormFieldDefinition("dropdown", "Мова", null, null, false,
"Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)." "Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)."
+ " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)",
null, "dc.language.iso", "common_iso_languages")))); null, "dc.language.iso", "common_iso_languages"))));
@@ -431,14 +462,15 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Autore", "\u00C8" + " richiesto almeno un autore", true, .matchFormFieldDefinition("name", "Autore", null,
"\u00C8" + " richiesto almeno un autore", true,
"Aggiungi un autore", "dc.contributor.author")))) "Aggiungi un autore", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Titolo", .matchFormFieldDefinition("onebox", "Titolo", null,
"\u00C8" + " necessario inserire un titolo principale per questo item", false, "\u00C8" + " necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title")))) "Inserisci titolo principale di questo item", "dc.title"))))
.andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("dropdown", "Lingua", null, false, .matchFormFieldDefinition("dropdown", "Lingua", null, null, false,
"Selezionare la lingua del contenuto principale dell'item." "Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)." + " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua" + " Se il contenuto non ha davvero una lingua"
@@ -455,14 +487,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Автор", "Потрібно ввести хочаб одного автора!", .matchFormFieldDefinition("name", "Автор", null, "Потрібно ввести хочаб одного автора!",
true, "Додати автора", "dc.contributor.author")))) true, "Додати автора", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Заголовок", .matchFormFieldDefinition("onebox", "Заголовок", null,
"Заговолок файла обов'язковий !", false, "Заговолок файла обов'язковий !", false,
"Ввести основний заголовок файла", "dc.title")))) "Ввести основний заголовок файла", "dc.title"))))
.andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("dropdown", "Мова", null, false, .matchFormFieldDefinition("dropdown", "Мова", null, null, false,
"Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)." "Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)."
+ " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)",
null, "dc.language.iso", "common_iso_languages")))); null, "dc.language.iso", "common_iso_languages"))));
@@ -505,14 +537,15 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Autore", "\u00C8" + " richiesto almeno un autore", true, .matchFormFieldDefinition("name", "Autore", null,
"\u00C8" + " richiesto almeno un autore", true,
"Aggiungi un autore", "dc.contributor.author")))) "Aggiungi un autore", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Titolo", .matchFormFieldDefinition("onebox", "Titolo", null,
"\u00C8" + " necessario inserire un titolo principale per questo item", false, "\u00C8" + " necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title")))) "Inserisci titolo principale di questo item", "dc.title"))))
.andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[2].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("dropdown", "Lingua", null, false, .matchFormFieldDefinition("dropdown", "Lingua", null, null, false,
"Selezionare la lingua del contenuto principale dell'item." "Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)." + " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua" + " Se il contenuto non ha davvero una lingua"
@@ -547,10 +580,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Autore", "\u00C8 richiesto almeno un autore", true, .matchFormFieldDefinition("name", "Autore", null, "\u00C8 richiesto almeno un autore", true,
"Aggiungi un autore", "dc.contributor.author")))) "Aggiungi un autore", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Titolo", .matchFormFieldDefinition("onebox", "Titolo", null,
"\u00C8 necessario inserire un titolo principale per questo item", false, "\u00C8 necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title")))); "Inserisci titolo principale di questo item", "dc.title"))));
resetLocalesConfiguration(); resetLocalesConfiguration();
@@ -582,10 +615,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(jsonPath("$._links.self.href", Matchers .andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/languagetest"))) .startsWith(REST_SERVER_URL + "config/submissionforms/languagetest")))
.andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[0].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("name", "Autore", "\u00C8 richiesto almeno un autore", true, .matchFormFieldDefinition("name", "Autore", null, "\u00C8 richiesto almeno un autore", true,
"Aggiungi un autore", "dc.contributor.author")))) "Aggiungi un autore", "dc.contributor.author"))))
.andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher .andExpect(jsonPath("$.rows[1].fields", contains(SubmissionFormFieldMatcher
.matchFormFieldDefinition("onebox", "Titolo", .matchFormFieldDefinition("onebox", "Titolo", null,
"\u00C8 necessario inserire un titolo principale per questo item", false, "\u00C8 necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title")))); "Inserisci titolo principale di questo item", "dc.title"))));

View File

@@ -1935,6 +1935,141 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration
; ;
} }
@Test
/**
* Test the update of metadata for fields configured with type-bind
*
* @throws Exception
*/
public void patchUpdateMetadataWithBindTest() throws Exception {
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
String authToken = getAuthToken(eperson.getEmail(), password);
WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1)
.withTitle("Workspace Item 1")
.withIssueDate("2017-10-17")
.withSubject("ExtraEntry")
.grantLicense()
.build();
//disable file upload mandatory
configurationService.setProperty("webui.submit.upload.required", false);
context.restoreAuthSystemState();
// Try to add isPartOfSeries (type bound to technical report) - this should not work and instead we'll get
// no JSON path for that field
List<Operation> updateSeries = new ArrayList<Operation>();
List<Map<String, String>> seriesValues = new ArrayList<>();
Map<String, String> value = new HashMap<String, String>();
value.put("value", "New Series");
seriesValues.add(value);
updateSeries.add(new AddOperation("/sections/traditionalpageone/dc.relation.ispartofseries", seriesValues));
String patchBody = getPatchContent(updateSeries);
getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
// Check this - we should match an item with no series or type
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem, null, null))));
// Verify that the metadata isn't in the workspace item
getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
// Check this - we should match an item with no series or type
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem, null, null))));
// Set the type to Technical Report confirm it worked
List<Operation> updateType = new ArrayList<>();
List<Map<String, String>> typeValues = new ArrayList<>();
value = new HashMap<String, String>();
value.put("value", "Technical Report");
typeValues.add(value);
updateType.add(new AddOperation("/sections/traditionalpageone/dc.type", typeValues));
patchBody = getPatchContent(updateType);
getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
// Check this - we should now match an item with the expected type and series
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem, "Technical Report",
null))));
getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem, "Technical Report",
null))));
// Another test, this time adding the series value should be successful and we'll see the value
patchBody = getPatchContent(updateSeries);
getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
// Check this - we should match an item with the expected series and type
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem,
"Technical Report", "New Series"))));
// Verify that the metadata isn't in the workspace item
getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
// Check this - we should match an item with the expected series and type
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem,
"Technical Report", "New Series"))));
// One final update, to a different type, this should lose the series as we're back to a non-matching type
updateType = new ArrayList<>();
typeValues = new ArrayList<>();
value = new HashMap<String, String>();
value.put("value", "Article");
typeValues.add(value);
updateType.add(new AddOperation("/sections/traditionalpageone/dc.type", typeValues));
patchBody = getPatchContent(updateType);
getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
// Check this - we should NOT match an item with the series "New Series"
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem, "Article",
null))));
getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.errors").doesNotExist())
.andExpect(jsonPath("$",
Matchers.is(WorkspaceItemMatcher.matchItemWithTypeAndSeries(witem, "Article",
null))));
}
@Test @Test
public void patchUpdateMetadataForbiddenTest() throws Exception { public void patchUpdateMetadataForbiddenTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();

View File

@@ -50,6 +50,7 @@ public class ItemMatcher {
*/ */
public static Matcher<? super Object> matchFullEmbeds() { public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds( return matchEmbeds(
"accessStatus",
"bundles[]", "bundles[]",
"mappedCollections[]", "mappedCollections[]",
"owningCollection", "owningCollection",
@@ -65,6 +66,7 @@ public class ItemMatcher {
*/ */
public static Matcher<? super Object> matchLinks(UUID uuid) { public static Matcher<? super Object> matchLinks(UUID uuid) {
return HalMatcher.matchLinks(REST_SERVER_URL + "core/items/" + uuid, return HalMatcher.matchLinks(REST_SERVER_URL + "core/items/" + uuid,
"accessStatus",
"bundles", "bundles",
"mappedCollections", "mappedCollections",
"owningCollection", "owningCollection",

View File

@@ -10,6 +10,7 @@ package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@@ -28,13 +29,15 @@ public class SubmissionFormFieldMatcher {
/** /**
* Shortcut for the * Shortcut for the
* {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String, String)} * {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, String, boolean, String, String, String, String)}
* with a null style and vocabulary name * with a null style and vocabulary name
* *
* @param type * @param type
* the expected input type * the expected input type
* @param label * @param label
* the expected label * the expected label
* @param typeBind
* the expected type-bind field(s)
* @param mandatoryMessage * @param mandatoryMessage
* the expected mandatoryMessage, can be null. If not empty the fiedl is expected to be flagged as * the expected mandatoryMessage, can be null. If not empty the fiedl is expected to be flagged as
* mandatory * mandatory
@@ -46,21 +49,23 @@ public class SubmissionFormFieldMatcher {
* the expected metadata * the expected metadata
* @return a Matcher for all the condition above * @return a Matcher for all the condition above
*/ */
public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String mandatoryMessage, public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String typeBind,
boolean repeatable, String mandatoryMessage, boolean repeatable,
String hints, String metadata) { String hints, String metadata) {
return matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, null, metadata); return matchFormFieldDefinition(type, label, typeBind, mandatoryMessage, repeatable, hints, null, metadata);
} }
/** /**
* Shortcut for the * Shortcut for the
* {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String, String)} * {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, String, boolean, String, String, String, String)}
* with a null controlled vocabulary * with a null controlled vocabulary
* *
* @param type * @param type
* the expected input type * the expected input type
* @param label * @param label
* the expected label * the expected label
* @param typeBind
* the expected type-bind field(s)
* @param mandatoryMessage * @param mandatoryMessage
* the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as * the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as
* mandatory * mandatory
@@ -75,10 +80,10 @@ public class SubmissionFormFieldMatcher {
* the expected metadata * the expected metadata
* @return a Matcher for all the condition above * @return a Matcher for all the condition above
*/ */
public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String mandatoryMessage, public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String typeBind,
boolean repeatable, String mandatoryMessage, boolean repeatable, String hints, String style, String metadata) {
String hints, String style, String metadata) { return matchFormFieldDefinition(type, label, typeBind, mandatoryMessage, repeatable, hints, style, metadata,
return matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, style, metadata, null); null);
} }
/** /**
@@ -88,6 +93,8 @@ public class SubmissionFormFieldMatcher {
* the expected input type * the expected input type
* @param label * @param label
* the expected label * the expected label
* @param typeBind
* the expected type-bind field(s)
* @param mandatoryMessage * @param mandatoryMessage
* the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as * the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as
* mandatory * mandatory
@@ -100,18 +107,20 @@ public class SubmissionFormFieldMatcher {
* missing * missing
* @param metadata * @param metadata
* the expected metadata * the expected metadata
* @param controlled vocabulary * @param controlledVocabulary
* the expected controlled vocabulary, can be null. If null the corresponding json path is expected to be * the expected controlled vocabulary, can be null. If null the corresponding json path is expected to be
* missing * missing
* @return a Matcher for all the condition above * @return a Matcher for all the condition above
*/ */
public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String mandatoryMessage, public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String typeBind,
boolean repeatable, String hints, String style, String mandatoryMessage, boolean repeatable,
String metadata, String controlledVocabulary) { String hints, String style, String metadata,
String controlledVocabulary) {
return allOf( return allOf(
// check each field definition // check each field definition
hasJsonPath("$.input.type", is(type)), hasJsonPath("$.input.type", is(type)),
hasJsonPath("$.label", containsString(label)), hasJsonPath("$.label", containsString(label)),
typeBind != null ? hasJsonPath("$.typeBind", contains(typeBind)) : hasNoJsonPath("$.typeBind[0]"),
hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)), hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)),
controlledVocabulary != null ? hasJsonPath("$.selectableMetadata[0].controlledVocabulary", controlledVocabulary != null ? hasJsonPath("$.selectableMetadata[0].controlledVocabulary",
is(controlledVocabulary)) : hasNoJsonPath("$.selectableMetadata[0].controlledVocabulary"), is(controlledVocabulary)) : hasNoJsonPath("$.selectableMetadata[0].controlledVocabulary"),
@@ -166,7 +175,7 @@ public class SubmissionFormFieldMatcher {
hasJsonPath("$.selectableRelationship.filter", is(filter)), hasJsonPath("$.selectableRelationship.filter", is(filter)),
hasJsonPath("$.selectableRelationship.searchConfiguration", is(searchConfiguration)), hasJsonPath("$.selectableRelationship.searchConfiguration", is(searchConfiguration)),
hasJsonPath("$.selectableRelationship.nameVariants", is(String.valueOf(nameVariants))), hasJsonPath("$.selectableRelationship.nameVariants", is(String.valueOf(nameVariants))),
matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, metadata)); matchFormFieldDefinition(type, label, null, mandatoryMessage, repeatable, hints, metadata));
} }
/** /**

View File

@@ -82,6 +82,30 @@ public class WorkspaceItemMatcher {
matchLinks(witem)); matchLinks(witem));
} }
/**
* Check that the workspace item has the expected type and series values
* (used in type bind evaluation)
* @param witem the workspace item
* @param type the dc.type value eg. Technical Report
* @param series the series value eg. 11-23
* @return Matcher result
*/
public static Matcher matchItemWithTypeAndSeries(WorkspaceItem witem, String type, String series) {
return allOf(
// Check workspaceitem properties
matchProperties(witem),
// Check type appears or is null
type != null ?
hasJsonPath("$.sections.traditionalpageone['dc.type'][0].value", is(type)) :
hasNoJsonPath("$.sections.traditionalpageone['dc.type'][0].value"),
// Check series as it appears (for type bind testing)
series != null ?
hasJsonPath("$.sections.traditionalpageone['dc.relation.ispartofseries'][0].value", is(series)) :
hasNoJsonPath("$.sections.traditionalpageone['dc.relation.ispartofseries'][0].value"),
matchLinks(witem)
);
}
/** /**
* Check that the id and type are exposed * Check that the id and type are exposed
* *

View File

@@ -0,0 +1,39 @@
/**
* 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.app.rest.model;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.dspace.access.status.DefaultAccessStatusHelper;
import org.junit.Before;
import org.junit.Test;
/**
* Test the AccessStatusRestTest class
*/
public class AccessStatusRestTest {
AccessStatusRest accessStatusRest;
@Before
public void setUp() throws Exception {
accessStatusRest = new AccessStatusRest();
}
@Test
public void testAccessStatusIsNullBeforeStatusSet() throws Exception {
assertNull(accessStatusRest.getStatus());
}
@Test
public void testAccessStatusIsNotNullAfterStatusSet() throws Exception {
accessStatusRest.setStatus(DefaultAccessStatusHelper.UNKNOWN);
assertNotNull(accessStatusRest.getStatus());
}
}

View File

@@ -806,6 +806,22 @@ plugin.single.org.dspace.embargo.EmbargoSetter = org.dspace.embargo.DefaultEmbar
# implementation of embargo lifter plugin - - replace with local implementation if applicable # implementation of embargo lifter plugin - - replace with local implementation if applicable
plugin.single.org.dspace.embargo.EmbargoLifter = org.dspace.embargo.DefaultEmbargoLifter plugin.single.org.dspace.embargo.EmbargoLifter = org.dspace.embargo.DefaultEmbargoLifter
# values for the forever embargo date threshold
# This threshold date is used in the default access status helper to dermine if an item is
# restricted or embargoed based on the start date of the primary (or first) file policies.
# In this case, if the policy start date is inferior to the threshold date, the status will
# be embargo, else it will be restricted.
# You might want to change this threshold based on your needs. For example: some databases
# doesn't accept a date superior to 31 december 9999.
access.status.embargo.forever.year = 10000
access.status.embargo.forever.month = 1
access.status.embargo.forever.day = 1
# implementation of access status helper plugin - replace with local implementation if applicable
# This default access status helper provides an item status based on the policies of the primary
# bitstream (or first bitstream in the original bundles if no primary file is specified).
plugin.single.org.dspace.access.status.AccessStatusHelper = org.dspace.access.status.DefaultAccessStatusHelper
#### Checksum Checker Settings #### #### Checksum Checker Settings ####
# Default dispatcher in case none specified # Default dispatcher in case none specified
plugin.single.org.dspace.checker.BitstreamDispatcher=org.dspace.checker.SimpleDispatcher plugin.single.org.dspace.checker.BitstreamDispatcher=org.dspace.checker.SimpleDispatcher
@@ -920,6 +936,11 @@ metadata.hide.dc.description.provenance = true
# Defaults to true; If set to 'false', submitter has option to skip upload # Defaults to true; If set to 'false', submitter has option to skip upload
#webui.submit.upload.required = true #webui.submit.upload.required = true
# Which field should be used for type-bind
# Defaults to 'dc.type'; If changing this value, you must also update the related
# dspace-angular environment configuration property submission.typeBind.field
#submit.type-bind.field = dc.type
#### Creative Commons settings ###### #### Creative Commons settings ######
# The url to the web service API # The url to the web service API
@@ -1584,6 +1605,12 @@ request.item.type = all
# Should all Request Copy emails go to the helpdesk instead of the item submitter? # Should all Request Copy emails go to the helpdesk instead of the item submitter?
request.item.helpdesk.override = false request.item.helpdesk.override = false
#------------------------------------------------------------------#
#------------------SUBMISSION CONFIGURATION------------------------#
#------------------------------------------------------------------#
# Field to use for type binding, default dc.type
submit.type-bind.field = dc.type
#------------------------------------------------------------------# #------------------------------------------------------------------#
#-------------------MODULE CONFIGURATIONS--------------------------# #-------------------MODULE CONFIGURATIONS--------------------------#
#------------------------------------------------------------------# #------------------------------------------------------------------#

View File

@@ -33,6 +33,7 @@ rest.projection.specificLevel.maxEmbed = 5
rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask
rest.properties.exposed = google.analytics.key rest.properties.exposed = google.analytics.key
rest.properties.exposed = versioning.item.history.include.submitter rest.properties.exposed = versioning.item.history.include.submitter
rest.properties.exposed = submit.type-bind.field
#---------------------------------------------------------------# #---------------------------------------------------------------#
# These configs are used by the deprecated REST (v4-6) module # # These configs are used by the deprecated REST (v4-6) module #

View File

@@ -27,6 +27,7 @@
<bean id="coreServiceFactory" class="org.dspace.core.factory.CoreServiceFactoryImpl"/> <bean id="coreServiceFactory" class="org.dspace.core.factory.CoreServiceFactoryImpl"/>
<bean id="accessStatusServiceFactory" class="org.dspace.access.status.factory.AccessStatusServiceFactoryImpl"/>
<bean id="disseminateServiceFactory" class="org.dspace.disseminate.factory.DisseminateServiceFactoryImpl"/> <bean id="disseminateServiceFactory" class="org.dspace.disseminate.factory.DisseminateServiceFactoryImpl"/>
<bean id="embargoServiceFactory" class="org.dspace.embargo.factory.EmbargoServiceFactoryImpl"/> <bean id="embargoServiceFactory" class="org.dspace.embargo.factory.EmbargoServiceFactoryImpl"/>
<bean id="ePersonServiceFactory" class="org.dspace.eperson.factory.EPersonServiceFactoryImpl"/> <bean id="ePersonServiceFactory" class="org.dspace.eperson.factory.EPersonServiceFactoryImpl"/>

View File

@@ -86,8 +86,9 @@
<bean class="org.dspace.disseminate.CitationDocumentServiceImpl"/> <bean class="org.dspace.disseminate.CitationDocumentServiceImpl"/>
<!-- Ensure EmbargoService is initialized properly via init() method --> <!-- Ensure EmbargoService and AccessStatusService are initialized properly via init() method -->
<bean class="org.dspace.embargo.EmbargoServiceImpl" init-method="init"/> <bean class="org.dspace.embargo.EmbargoServiceImpl" init-method="init"/>
<bean class="org.dspace.access.status.AccessStatusServiceImpl" init-method="init"/>
<bean class="org.dspace.eperson.AccountServiceImpl"/> <bean class="org.dspace.eperson.AccountServiceImpl"/>
<bean class="org.dspace.eperson.EPersonServiceImpl"/> <bean class="org.dspace.eperson.EPersonServiceImpl"/>