Merged main into DURACOM-126

This commit is contained in:
Giuseppe Digilio
2023-05-02 13:59:46 +00:00
26 changed files with 1106 additions and 134 deletions

View File

@@ -51,6 +51,7 @@ import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.indexobject.IndexableCollection;
import org.dspace.discovery.indexobject.IndexableCommunity;
@@ -91,6 +92,7 @@ public class SyndicationFeed {
// default DC fields for entry
protected String defaultTitleField = "dc.title";
protected String defaultDescriptionField = "dc.description";
protected String defaultAuthorField = "dc.contributor.author";
protected String defaultDateField = "dc.date.issued";
private static final String[] defaultDescriptionFields =
@@ -196,15 +198,15 @@ public class SyndicationFeed {
// dso is null for the whole site, or a search without scope
if (dso == null) {
defaultTitle = configurationService.getProperty("dspace.name");
feed.setDescription(localize(labels, MSG_FEED_DESCRIPTION));
defaultDescriptionField = localize(labels, MSG_FEED_DESCRIPTION);
objectURL = resolveURL(request, null);
} else {
Bitstream logo = null;
if (dso instanceof IndexableCollection) {
Collection col = ((IndexableCollection) dso).getIndexedObject();
defaultTitle = col.getName();
feed.setDescription(collectionService.getMetadataFirstValue(col,
CollectionService.MD_SHORT_DESCRIPTION, Item.ANY));
defaultDescriptionField = collectionService.getMetadataFirstValue(col,
CollectionService.MD_SHORT_DESCRIPTION, Item.ANY);
logo = col.getLogo();
String cols = configurationService.getProperty("webui.feed.podcast.collections");
if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) {
@@ -214,8 +216,8 @@ public class SyndicationFeed {
} else if (dso instanceof IndexableCommunity) {
Community comm = ((IndexableCommunity) dso).getIndexedObject();
defaultTitle = comm.getName();
feed.setDescription(communityService.getMetadataFirstValue(comm,
CommunityService.MD_SHORT_DESCRIPTION, Item.ANY));
defaultDescriptionField = communityService.getMetadataFirstValue(comm,
CommunityService.MD_SHORT_DESCRIPTION, Item.ANY);
logo = comm.getLogo();
String comms = configurationService.getProperty("webui.feed.podcast.communities");
if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) {
@@ -230,6 +232,12 @@ public class SyndicationFeed {
}
feed.setTitle(labels.containsKey(MSG_FEED_TITLE) ?
localize(labels, MSG_FEED_TITLE) : defaultTitle);
if (defaultDescriptionField == null || defaultDescriptionField == "") {
defaultDescriptionField = I18nUtil.getMessage("org.dspace.app.util.SyndicationFeed.no-description");
}
feed.setDescription(defaultDescriptionField);
feed.setLink(objectURL);
feed.setPublishedDate(new Date());
feed.setUri(objectURL);

View File

@@ -52,11 +52,6 @@ public class IPAuthentication implements AuthenticationMethod {
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IPAuthentication.class);
/**
* Whether to look for x-forwarded headers for logging IP addresses
*/
protected static Boolean useProxies;
/**
* All the IP matchers
*/
@@ -250,7 +245,7 @@ public class IPAuthentication implements AuthenticationMethod {
log.debug(LogHelper.getHeader(context, "authenticated",
"special_groups=" + gsb.toString()
+ " (by IP=" + addr + ", useProxies=" + useProxies.toString() + ")"
+ " (by IP=" + addr + ")"
));
}

View File

@@ -48,6 +48,12 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity<jav
@Transient
private StringBuffer eventDetails = null;
/**
* The same order should be applied inside this comparator
* {@link MetadataValueComparators#defaultComparator} to preserve
* ordering while the list has been modified and not yet persisted
* and reloaded.
*/
@OneToMany(fetch = FetchType.LAZY, mappedBy = "dSpaceObject", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("metadataField, place")
private List<MetadataValue> metadata = new ArrayList<>();
@@ -116,7 +122,7 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity<jav
* @return summary of event details, or null if there are none.
*/
public String getDetails() {
return (eventDetails == null ? null : eventDetails.toString());
return eventDetails == null ? null : eventDetails.toString();
}
/**
@@ -145,7 +151,7 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity<jav
* one
*/
public String getHandle() {
return (CollectionUtils.isNotEmpty(handles) ? handles.get(0).getHandle() : null);
return CollectionUtils.isNotEmpty(handles) ? handles.get(0).getHandle() : null;
}
void setHandle(List<Handle> handle) {

View File

@@ -126,6 +126,11 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
}
}
// Sort the metadataValues if they have been modified,
// is used to preserve the default order.
if (dso.isMetadataModified()) {
values.sort(MetadataValueComparators.defaultComparator);
}
// Create an array of matching values
return values;
}
@@ -542,7 +547,7 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
int add = 4 - tokens.length;
if (add > 0) {
tokens = (String[]) ArrayUtils.addAll(tokens, new String[add]);
tokens = ArrayUtils.addAll(tokens, new String[add]);
}
return tokens;
@@ -603,21 +608,18 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
//If two places are the same then the MetadataValue instance will be placed before the
//RelationshipMetadataValue instance.
//This is done to ensure that the order is correct.
metadataValues.sort(new Comparator<MetadataValue>() {
@Override
public int compare(MetadataValue o1, MetadataValue o2) {
int compare = o1.getPlace() - o2.getPlace();
if (compare == 0) {
if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) {
return compare;
} else if (o1 instanceof RelationshipMetadataValue) {
return 1;
} else if (o2 instanceof RelationshipMetadataValue) {
return -1;
}
metadataValues.sort((o1, o2) -> {
int compare = o1.getPlace() - o2.getPlace();
if (compare == 0) {
if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) {
return compare;
} else if (o1 instanceof RelationshipMetadataValue) {
return 1;
} else if (o2 instanceof RelationshipMetadataValue) {
return -1;
}
return compare;
}
return compare;
});
for (MetadataValue metadataValue : metadataValues) {
//Retrieve & store the place for each metadata value
@@ -634,7 +636,7 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
String authority = metadataValue.getAuthority();
String relationshipId = StringUtils.split(authority, "::")[1];
Relationship relationship = relationshipService.find(context, Integer.parseInt(relationshipId));
if (relationship.getLeftItem().equals((Item) dso)) {
if (relationship.getLeftItem().equals(dso)) {
relationship.setLeftPlace(mvPlace);
} else {
relationship.setRightPlace(mvPlace);

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
@@ -288,9 +287,10 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
return itemDAO.findAll(context, true, true);
}
@Override
public Iterator<Item> findAllRegularItems(Context context) throws SQLException {
return itemDAO.findAllRegularItems(context);
};
}
@Override
public Iterator<Item> findBySubmitter(Context context, EPerson eperson) throws SQLException {
@@ -1054,7 +1054,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
List<Collection> linkedCollections = item.getCollections();
List<Collection> notLinkedCollections = new ArrayList<>(allCollections.size() - linkedCollections.size());
if ((allCollections.size() - linkedCollections.size()) == 0) {
if (allCollections.size() - linkedCollections.size() == 0) {
return notLinkedCollections;
}
for (Collection collection : allCollections) {
@@ -1149,6 +1149,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
* @return <code>true</code> if the item is an inprogress submission, i.e. a WorkspaceItem or WorkflowItem
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
@Override
public boolean isInProgressSubmission(Context context, Item item) throws SQLException {
return workspaceItemService.findByItem(context, item) != null
|| workflowItemService.findByItem(context, item) != null;
@@ -1179,8 +1180,8 @@ prevent the generation of resource policy entry values with null dspace_object a
if (!authorizeService
.isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
defaultPolicy.getID()) &&
((!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso)) ||
(appendMode && this.shouldBeAppended(context, dso, defaultPolicy)))) {
(!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
appendMode && this.shouldBeAppended(context, dso, defaultPolicy))) {
ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
newPolicy.setdSpaceObject(dso);
newPolicy.setAction(Constants.READ);
@@ -1611,7 +1612,7 @@ prevent the generation of resource policy entry values with null dspace_object a
fullMetadataValueList.addAll(relationshipMetadataService.getRelationshipMetadata(item, true));
fullMetadataValueList.addAll(dbMetadataValues);
item.setCachedMetadata(sortMetadataValueList(fullMetadataValueList));
item.setCachedMetadata(MetadataValueComparators.sort(fullMetadataValueList));
}
log.debug("Called getMetadata for " + item.getID() + " based on cache");
@@ -1653,28 +1654,6 @@ prevent the generation of resource policy entry values with null dspace_object a
}
}
/**
* This method will sort the List of MetadataValue objects based on the MetadataSchema, MetadataField Element,
* MetadataField Qualifier and MetadataField Place in that order.
* @param listToReturn The list to be sorted
* @return The list sorted on those criteria
*/
private List<MetadataValue> sortMetadataValueList(List<MetadataValue> listToReturn) {
Comparator<MetadataValue> comparator = Comparator.comparing(
metadataValue -> metadataValue.getMetadataField().getMetadataSchema().getName(),
Comparator.nullsFirst(Comparator.naturalOrder()));
comparator = comparator.thenComparing(metadataValue -> metadataValue.getMetadataField().getElement(),
Comparator.nullsFirst(Comparator.naturalOrder()));
comparator = comparator.thenComparing(metadataValue -> metadataValue.getMetadataField().getQualifier(),
Comparator.nullsFirst(Comparator.naturalOrder()));
comparator = comparator.thenComparing(metadataValue -> metadataValue.getPlace(),
Comparator.nullsFirst(Comparator.naturalOrder()));
Stream<MetadataValue> metadataValueStream = listToReturn.stream().sorted(comparator);
listToReturn = metadataValueStream.collect(Collectors.toList());
return listToReturn;
}
@Override
public MetadataValue addMetadata(Context context, Item dso, String schema, String element, String qualifier,
String lang, String value, String authority, int confidence, int place) throws SQLException {

View File

@@ -19,6 +19,7 @@ import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.dspace.core.Context;
import org.dspace.core.ReloadableEntity;
@@ -171,6 +172,14 @@ public class MetadataValue implements ReloadableEntity<Integer> {
this.metadataField = metadataField;
}
/**
* @return {@code MetadataField#getID()}
*/
@Transient
protected Integer getMetadataFieldId() {
return getMetadataField().getID();
}
/**
* Get the metadata value.
*

View File

@@ -0,0 +1,51 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* This class contains only static members that can be used
* to sort list of {@link MetadataValue}
*
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
*
*/
public final class MetadataValueComparators {
private MetadataValueComparators() {}
/**
* This is the default comparator that mimics the ordering
* applied by the standard {@code @OrderBy} annotation inside
* {@link DSpaceObject#getMetadata()}
*/
public static final Comparator<MetadataValue> defaultComparator =
Comparator.comparing(MetadataValue::getMetadataFieldId)
.thenComparing(
MetadataValue::getPlace,
Comparator.nullsFirst(Comparator.naturalOrder())
);
/**
* This method creates a new {@code List<MetadataValue>} ordered by the
* {@code MetadataComparators#defaultComparator}.
*
* @param metadataValues
* @return {@code List<MetadataValue>} ordered copy list using stream.
*/
public static final List<MetadataValue> sort(List<MetadataValue> metadataValues) {
return metadataValues
.stream()
.sorted(MetadataValueComparators.defaultComparator)
.collect(Collectors.toList());
}
}

View File

@@ -306,6 +306,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
public DSpaceObject resolve(Context context, String identifier, String... attributes) {
// We can do nothing with this, return null
try {
identifier = handleService.parseHandle(identifier);
return handleService.resolveToObject(context, identifier);
} catch (IllegalStateException | SQLException e) {
log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier),
@@ -426,6 +427,19 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
}
}
DSpaceObject itemWithCanonicalHandle = handleService.resolveToObject(context, canonical);
if (itemWithCanonicalHandle != null) {
if (itemWithCanonicalHandle.getID() != previous.getItem().getID()) {
log.warn("The previous version's item (" + previous.getItem().getID() +
") does not match with the item containing handle " + canonical +
" (" + itemWithCanonicalHandle.getID() + ")");
}
// Move the original handle from whatever item it's on to the newest version
handleService.modifyHandleDSpaceObject(context, canonical, dso);
} else {
handleService.createHandle(context, dso, canonical);
}
// add a new Identifier for this item: 12345/100.x
String idNew = canonical + DOT + version.getVersionNumber();
//Make sure we don't have an old handle hanging around (if our previous version was deleted in the workspace)

View File

@@ -121,12 +121,14 @@ public class PubmedDateMetadatumContributor<T> implements MetadataContributor<T>
int j = 0;
// Use the first dcDate that has been formatted (Config should go from most specific to most lenient)
while (j < dateFormatsToAttempt.size() && dcDate == null) {
while (j < dateFormatsToAttempt.size()) {
String dateFormat = dateFormatsToAttempt.get(j);
try {
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
Date date = formatter.parse(dateString);
dcDate = new DCDate(date);
values.add(metadataFieldMapping.toDCValue(field, formatter.format(date)));
break;
} catch (ParseException e) {
// Multiple dateformats can be configured, we don't want to print the entire stacktrace every
// time one of those formats fails.
@@ -136,9 +138,7 @@ public class PubmedDateMetadatumContributor<T> implements MetadataContributor<T>
}
j++;
}
if (dcDate != null) {
values.add(metadataFieldMapping.toDCValue(field, dcDate.toString()));
} else {
if (dcDate == null) {
log.info(
"Failed parsing " + dateString + ", check " +
"the configured dataformats in config/spring/api/pubmed-integration.xml");

View File

@@ -51,6 +51,7 @@ metadata.bitstream.iiif-virtual.bytes = File size
metadata.bitstream.iiif-virtual.checksum = Checksum
org.dspace.app.itemexport.no-result = The DSpaceObject that you specified has no items.
org.dspace.app.util.SyndicationFeed.no-description = No Description
org.dspace.checker.ResultsLogger.bitstream-format = Bitstream format
org.dspace.checker.ResultsLogger.bitstream-found = Bitstream found
org.dspace.checker.ResultsLogger.bitstream-id = Bitstream ID

View File

@@ -13,15 +13,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- Identifier Service Application Interface. Will be autowired with
any Identifier Providers present in Spring context.
-->
<bean id="org.dspace.identifier.service.IdentifierService"
class="org.dspace.identifier.IdentifierServiceImpl"
autowire="byType"
scope="singleton"/>
<bean id="org.dspace.services.ConfigurationService"
class="org.dspace.servicemanager.config.DSpaceConfigurationService" scope="singleton"/>
@@ -31,12 +22,6 @@
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
</bean-->
<bean id="org.dspace.identifier.VersionedHandleIdentifierProvider"
class="org.dspace.identifier.VersionedHandleIdentifierProvider"
scope="singleton">
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
</bean>
<bean name="org.dspace.core.DBConnection" class="org.dspace.core.HibernateDBConnection" lazy-init="true" scope="prototype"/>
<!-- Register all our Flyway callback classes (which run before/after database migrations) -->

View File

@@ -19,7 +19,18 @@
<bean id="org.dspace.identifier.service.IdentifierService"
class="org.dspace.identifier.IdentifierServiceImpl"
autowire="byType"
scope="singleton"/>
scope="singleton">
<property name="providers">
<list>
<ref bean="org.dspace.identifier.HandleIdentifierProvider"/>
<ref bean="org.dspace.identifier.DOIIdentifierProvider"/>
</list>
</property>
</bean>
<bean id="org.dspace.identifier.HandleIdentifierProvider" class="org.dspace.identifier.VersionedHandleIdentifierProvider" scope="singleton">
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
</bean>
<!-- provider to mint and register DOIs with DSpace.
To mint DOIs you need a registration agency. The DOIIdentifierProvider

View File

@@ -9,6 +9,7 @@ package org.dspace.content.service;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
@@ -112,6 +113,177 @@ public class ItemServiceTest extends AbstractIntegrationTestWithDatabase {
}
}
@Test
public void preserveMetadataOrder() throws Exception {
context.turnOffAuthorisationSystem();
itemService
.addMetadata(
context, item, dcSchema, contributorElement, authorQualifier, null, "test, one", null, 0, 2
);
MetadataValue placeZero =
itemService
.addMetadata(
context, item, dcSchema, contributorElement, authorQualifier, null, "test, two", null, 0, 0
);
itemService
.addMetadata(
context, item, dcSchema, contributorElement, authorQualifier, null, "test, three", null, 0, 1
);
context.commit();
context.restoreAuthSystemState();
// check the correct order using default method `getMetadata`
List<MetadataValue> defaultMetadata =
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
assertThat(defaultMetadata,hasSize(3));
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, two", null, 0, defaultMetadata.get(0)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, three", null, 1, defaultMetadata.get(1)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, one", null, 2, defaultMetadata.get(2)
);
// check the correct order using the method `getMetadata` without virtual fields
List<MetadataValue> nonVirtualMetadatas =
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
// if we don't reload the item the place order is not applied correctly
// item = context.reloadEntity(item);
assertThat(nonVirtualMetadatas,hasSize(3));
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, two", null, 0, nonVirtualMetadatas.get(0)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, three", null, 1, nonVirtualMetadatas.get(1)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, one", null, 2, nonVirtualMetadatas.get(2)
);
context.turnOffAuthorisationSystem();
item = context.reloadEntity(item);
// now just add one metadata to be the last
this.itemService.addMetadata(
context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, latest", null, 0
);
// now just remove first metadata
this.itemService.removeMetadataValues(context, item, List.of(placeZero));
// now just add one metadata to place 0
this.itemService.addAndShiftRightMetadata(
context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, new", null, 0, 0
);
// check the metadata using method `getMetadata`
defaultMetadata =
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
// check correct places
assertThat(defaultMetadata,hasSize(4));
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, new", null, 0, defaultMetadata.get(0)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, three", null, 1, defaultMetadata.get(1)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, one", null, 2, defaultMetadata.get(2)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, defaultMetadata.get(3)
);
// check metadata using nonVirtualMethod
nonVirtualMetadatas =
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
// check correct places
assertThat(nonVirtualMetadatas,hasSize(4));
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, new", null, 0, nonVirtualMetadatas.get(0)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, three", null, 1, nonVirtualMetadatas.get(1)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, one", null, 2, nonVirtualMetadatas.get(2)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, nonVirtualMetadatas.get(3)
);
// check both lists
assertThat(defaultMetadata.size(), equalTo(nonVirtualMetadatas.size()));
assertThat(defaultMetadata.get(0), equalTo(nonVirtualMetadatas.get(0)));
assertThat(defaultMetadata.get(1), equalTo(nonVirtualMetadatas.get(1)));
assertThat(defaultMetadata.get(2), equalTo(nonVirtualMetadatas.get(2)));
assertThat(defaultMetadata.get(3), equalTo(nonVirtualMetadatas.get(3)));
context.commit();
context.restoreAuthSystemState();
item = context.reloadEntity(item);
// check after commit
defaultMetadata =
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
// check correct places
assertThat(defaultMetadata,hasSize(4));
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, new", null, 0, defaultMetadata.get(0)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, three", null, 1, defaultMetadata.get(1)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, one", null, 2, defaultMetadata.get(2)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, defaultMetadata.get(3)
);
// check metadata using nonVirtualMethod
nonVirtualMetadatas =
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
// check correct places
assertThat(nonVirtualMetadatas,hasSize(4));
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, new", null, 0, nonVirtualMetadatas.get(0)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, three", null, 1, nonVirtualMetadatas.get(1)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, one", null, 2, nonVirtualMetadatas.get(2)
);
assertMetadataValue(
authorQualifier, contributorElement, dcSchema, "test, latest", null, 3, nonVirtualMetadatas.get(3)
);
// check both lists
assertThat(defaultMetadata.size(), equalTo(nonVirtualMetadatas.size()));
assertThat(defaultMetadata.get(0), equalTo(nonVirtualMetadatas.get(0)));
assertThat(defaultMetadata.get(1), equalTo(nonVirtualMetadatas.get(1)));
assertThat(defaultMetadata.get(2), equalTo(nonVirtualMetadatas.get(2)));
assertThat(defaultMetadata.get(3), equalTo(nonVirtualMetadatas.get(3)));
}
@Test
public void InsertAndMoveMetadataShiftPlaceTest() throws Exception {
context.turnOffAuthorisationSystem();

View File

@@ -0,0 +1,115 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.identifier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.authorize.AuthorizeException;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.builder.VersionBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.kernel.ServiceManager;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.junit.Before;
import org.junit.Test;
public class VersionedHandleIdentifierProviderTest extends AbstractIntegrationTestWithDatabase {
private ServiceManager serviceManager;
private IdentifierServiceImpl identifierService;
private String firstHandle;
private Collection collection;
private Item itemV1;
private Item itemV2;
private Item itemV3;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
context.turnOffAuthorisationSystem();
serviceManager = DSpaceServicesFactory.getInstance().getServiceManager();
identifierService = serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0);
// Clean out providers to avoid any being used for creation of community and collection
identifierService.setProviders(new ArrayList<>());
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection")
.build();
}
private void registerProvider(Class type) {
// Register our new provider
serviceManager.registerServiceClass(type.getName(), type);
IdentifierProvider identifierProvider =
(IdentifierProvider) serviceManager.getServiceByName(type.getName(), type);
// Overwrite the identifier-service's providers with the new one to ensure only this provider is used
identifierService.setProviders(List.of(identifierProvider));
}
private void createVersions() throws SQLException, AuthorizeException {
itemV1 = ItemBuilder.createItem(context, collection)
.withTitle("First version")
.build();
firstHandle = itemV1.getHandle();
itemV2 = VersionBuilder.createVersion(context, itemV1, "Second version").build().getItem();
itemV3 = VersionBuilder.createVersion(context, itemV1, "Third version").build().getItem();
}
@Test
public void testDefaultVersionedHandleProvider() throws Exception {
registerProvider(VersionedHandleIdentifierProvider.class);
createVersions();
// Confirm the original item only has its original handle
assertEquals(firstHandle, itemV1.getHandle());
assertEquals(1, itemV1.getHandles().size());
// Confirm the second item has the correct version handle
assertEquals(firstHandle + ".2", itemV2.getHandle());
assertEquals(1, itemV2.getHandles().size());
// Confirm the last item has the correct version handle
assertEquals(firstHandle + ".3", itemV3.getHandle());
assertEquals(1, itemV3.getHandles().size());
}
@Test
public void testCanonicalVersionedHandleProvider() throws Exception {
registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class);
createVersions();
// Confirm the original item only has a version handle
assertEquals(firstHandle + ".1", itemV1.getHandle());
assertEquals(1, itemV1.getHandles().size());
// Confirm the second item has the correct version handle
assertEquals(firstHandle + ".2", itemV2.getHandle());
assertEquals(1, itemV2.getHandles().size());
// Confirm the last item has both the correct version handle and the original handle
assertEquals(firstHandle, itemV3.getHandle());
assertEquals(2, itemV3.getHandles().size());
containsHandle(itemV3, firstHandle + ".3");
}
private void containsHandle(Item item, String handle) {
assertTrue(item.getHandles().stream().anyMatch(h -> handle.equals(h.getHandle())));
}
}

View File

@@ -249,4 +249,24 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest {
</OpenSearchDescription>
*/
}
@Test
public void emptyDescriptionTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection collection1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1")
.build();
getClient().perform(get("/opensearch/search")
.param("format", "rss")
.param("scope", collection1.getID().toString())
.param("query", "*"))
.andExpect(status().isOk())
.andExpect(xpath("rss/channel/description").string("No Description"));
}
}

View File

@@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -30,6 +31,9 @@ import org.dspace.app.rest.matcher.BitstreamFormatMatcher;
import org.dspace.app.rest.matcher.BitstreamMatcher;
import org.dspace.app.rest.matcher.BundleMatcher;
import org.dspace.app.rest.matcher.HalMatcher;
import org.dspace.app.rest.matcher.MetadataMatcher;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.ReplaceOperation;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite;
import org.dspace.authorize.service.ResourcePolicyService;
@@ -45,6 +49,7 @@ import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.ItemService;
@@ -1222,6 +1227,92 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
+ parentCommunity.getLogo().getID(), expectedStatus);
}
@Test
public void patchReplaceMultipleDescriptionBitstream() throws Exception {
context.turnOffAuthorisationSystem();
List<String> bitstreamDescriptions = List.of(
"FIRST",
"SECOND",
"THIRD"
);
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();
Item publicItem1 = ItemBuilder.createItem(context, col1).withTitle("Test").build();
String bitstreamContent = "ThisIsSomeDummyText";
Bitstream bitstream = null;
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
bitstream = BitstreamBuilder.
createBitstream(context, publicItem1, is)
.withName("Bitstream")
.withMimeType("text/plain")
.build();
}
this.bitstreamService
.addMetadata(
context, bitstream,
MetadataSchemaEnum.DC.getName(), "description", null,
Item.ANY, bitstreamDescriptions
);
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/core/bitstreams/" + bitstream.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(0), 0),
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(1), 1),
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(2), 2)
)
)
);
List<Operation> ops = List.of(
new ReplaceOperation("/metadata/dc.description/0", bitstreamDescriptions.get(2)),
new ReplaceOperation("/metadata/dc.description/1", bitstreamDescriptions.get(0)),
new ReplaceOperation("/metadata/dc.description/2", bitstreamDescriptions.get(1))
);
String requestBody = getPatchContent(ops);
getClient(token)
.perform(patch("/api/core/bitstreams/" + bitstream.getID())
.content(requestBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(1), 2)
)
)
);
getClient(token)
.perform(get("/api/core/bitstreams/" + bitstream.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", bitstreamDescriptions.get(1), 2)
)
)
);
}
@Test
public void testHiddenMetadataForAnonymousUser() throws Exception {

View File

@@ -37,6 +37,7 @@ import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.app.rest.model.patch.MoveOperation;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.ReplaceOperation;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.ResourcePolicyService;
@@ -51,6 +52,8 @@ import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.BundleService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.eperson.EPerson;
@@ -68,6 +71,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
ItemService itemService;
@Autowired
BundleService bundleService;
private Collection collection;
private Item item;
private Bundle bundle1;
@@ -515,6 +521,77 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest {
)));
}
@Test
public void patchReplaceMultipleDescriptionBundle() throws Exception {
context.turnOffAuthorisationSystem();
List<String> bundleDescriptions = List.of(
"FIRST",
"SECOND",
"THIRD"
);
bundle1 = BundleBuilder.createBundle(context, item)
.withName("testname")
.build();
this.bundleService
.addMetadata(
context, bundle1,
MetadataSchemaEnum.DC.getName(), "description", null,
Item.ANY, bundleDescriptions
);
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/core/bundles/" + bundle1.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(0), 0),
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(1), 1),
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(2), 2)
)
)
);
List<Operation> ops = List.of(
new ReplaceOperation("/metadata/dc.description/0", bundleDescriptions.get(2)),
new ReplaceOperation("/metadata/dc.description/1", bundleDescriptions.get(0)),
new ReplaceOperation("/metadata/dc.description/2", bundleDescriptions.get(1))
);
String requestBody = getPatchContent(ops);
getClient(token)
.perform(patch("/api/core/bundles/" + bundle1.getID())
.content(requestBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(1), 2)
)
)
);
getClient(token)
.perform(get("/api/core/bundles/" + bundle1.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", bundleDescriptions.get(1), 2)
)
)
);
}
@Test
public void deleteBundle() throws Exception {
context.turnOffAuthorisationSystem();

View File

@@ -69,6 +69,7 @@ import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.EntityType;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.CollectionService;
import org.dspace.core.Constants;
import org.dspace.eperson.EPerson;
@@ -499,13 +500,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
getClient(tokenParentAdmin).perform(get("/api/core/collections/" + col1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
Matchers.is((CollectionMatcher.matchCollection(col1)))));
Matchers.is(CollectionMatcher.matchCollection(col1))));
String tokenCol1Admin = getAuthToken(col1Admin.getEmail(), "qwerty02");
getClient(tokenCol1Admin).perform(get("/api/core/collections/" + col1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
Matchers.is((CollectionMatcher.matchCollection(col1)))));
Matchers.is(CollectionMatcher.matchCollection(col1))));
String tokenCol2Admin = getAuthToken(col2Admin.getEmail(), "qwerty03");
getClient(tokenCol2Admin).perform(get("/api/core/collections/" + col1.getID()))
@@ -1206,7 +1207,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
)
)))
.andDo(result -> idRef
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));;
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
getClient(authToken).perform(post("/api/core/collections")
@@ -3101,6 +3102,81 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
.andExpect(status().isUnauthorized());
}
@Test
public void patchReplaceMultipleDescriptionCollection() throws Exception {
context.turnOffAuthorisationSystem();
List<String> collectionDescriptions = List.of(
"FIRST",
"SECOND",
"THIRD"
);
parentCommunity =
CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Collection col =
CollectionBuilder.createCollection(context, parentCommunity)
.withName("MyTest")
.build();
this.collectionService
.addMetadata(
context, col, MetadataSchemaEnum.DC.getName(), "description", null, Item.ANY, collectionDescriptions
);
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/core/collections/" + col.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(0), 0),
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(1), 1),
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(2), 2)
)
)
);
List<Operation> ops = List.of(
new ReplaceOperation("/metadata/dc.description/0", collectionDescriptions.get(2)),
new ReplaceOperation("/metadata/dc.description/1", collectionDescriptions.get(0)),
new ReplaceOperation("/metadata/dc.description/2", collectionDescriptions.get(1))
);
String requestBody = getPatchContent(ops);
getClient(token)
.perform(patch("/api/core/collections/" + col.getID())
.content(requestBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(1), 2)
)
)
);
getClient(token)
.perform(get("/api/core/collections/" + col.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", collectionDescriptions.get(1), 2)
)
)
);
}
@Test
public void patchMetadataCheckReindexingTest() throws Exception {
context.turnOffAuthorisationSystem();

View File

@@ -20,6 +20,7 @@ import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_
import static org.springframework.http.MediaType.parseMediaType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -44,6 +45,8 @@ import org.dspace.app.rest.model.CommunityRest;
import org.dspace.app.rest.model.GroupRest;
import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.ReplaceOperation;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite;
@@ -56,6 +59,8 @@ import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.CommunityService;
import org.dspace.core.Constants;
import org.dspace.eperson.EPerson;
@@ -1935,6 +1940,78 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest
runPatchMetadataTests(eperson, 403);
}
@Test
public void patchReplaceMultipleDescriptionCommunity() throws Exception {
context.turnOffAuthorisationSystem();
List<String> communityDescriptions = List.of(
"FIRST",
"SECOND",
"THIRD"
);
parentCommunity =
CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
this.communityService
.addMetadata(
context, parentCommunity,
MetadataSchemaEnum.DC.getName(), "description", null,
Item.ANY, communityDescriptions
);
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/core/communities/" + parentCommunity.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(0), 0),
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(1), 1),
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(2), 2)
)
)
);
List<Operation> ops = List.of(
new ReplaceOperation("/metadata/dc.description/0", communityDescriptions.get(2)),
new ReplaceOperation("/metadata/dc.description/1", communityDescriptions.get(0)),
new ReplaceOperation("/metadata/dc.description/2", communityDescriptions.get(1))
);
String requestBody = getPatchContent(ops);
getClient(token)
.perform(patch("/api/core/communities/" + parentCommunity.getID())
.content(requestBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(1), 2)
)
)
);
getClient(token)
.perform(get("/api/core/communities/" + parentCommunity.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", communityDescriptions.get(1), 2)
)
)
);
}
private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build();

View File

@@ -72,6 +72,7 @@ import org.dspace.builder.GroupBuilder;
import org.dspace.builder.WorkflowItemBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.core.I18nUtil;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
@@ -155,7 +156,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", HalMatcher.matchNoEmbeds()))
.andDo(result -> idRefNoEmbeds
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));;
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
} finally {
EPersonBuilder.deleteEPerson(idRef.get());
@@ -1217,7 +1218,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.canLogIn", Matchers.is(true)));;
.andExpect(jsonPath("$.canLogIn", Matchers.is(true)));
List<Operation> ops2 = new ArrayList<Operation>();
@@ -1295,7 +1296,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.requireCertificate", Matchers.is(true)));;
.andExpect(jsonPath("$.requireCertificate", Matchers.is(true)));
List<Operation> ops2 = new ArrayList<Operation>();
ReplaceOperation replaceOperation2 = new ReplaceOperation("/certificate",null);
@@ -1858,6 +1859,78 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
matchMetadata("eperson.firstname", newName)))));
}
@Test
public void patchMultipleReplaceMetadataByAdmin() throws Exception {
context.turnOffAuthorisationSystem();
String first = "First";
String second = "Second";
String third = "Third";
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withEmail("Johndoe@example.com")
.build();
this.ePersonService
.addMetadata(context, ePerson, "eperson", "firstname", null, Item.ANY, List.of(first, second, third));
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
// The replacement of the eperson.firstname value is persisted
getClient(token).perform(get("/api/eperson/epersons/" + ePerson.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("eperson.firstname", first, 0),
MetadataMatcher.matchMetadata("eperson.firstname", second, 1),
MetadataMatcher.matchMetadata("eperson.firstname", third, 2)
)
)
);
List<Operation> ops = new ArrayList<Operation>();
ReplaceOperation replaceFirst = new ReplaceOperation("/metadata/eperson.firstname/0", third);
ReplaceOperation replaceSecond = new ReplaceOperation("/metadata/eperson.firstname/1", second);
ReplaceOperation replaceThird = new ReplaceOperation("/metadata/eperson.firstname/2", first);
ops.add(replaceFirst);
ops.add(replaceSecond);
ops.add(replaceThird);
String patchBody = getPatchContent(ops);
getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("eperson.firstname", third, 0),
MetadataMatcher.matchMetadata("eperson.firstname", second, 1),
MetadataMatcher.matchMetadata("eperson.firstname", first, 2)
)
)
);
getClient(token).perform(get("/api/eperson/epersons/" + ePerson.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("eperson.firstname", third, 0),
MetadataMatcher.matchMetadata("eperson.firstname", second, 1),
MetadataMatcher.matchMetadata("eperson.firstname", first, 2)
)
)
);
}
@Test
public void patchOwnMetadataByNonAdminUser() throws Exception {

View File

@@ -40,6 +40,7 @@ import org.dspace.app.rest.exception.GroupNameNotProvidedException;
import org.dspace.app.rest.matcher.EPersonMatcher;
import org.dspace.app.rest.matcher.GroupMatcher;
import org.dspace.app.rest.matcher.HalMatcher;
import org.dspace.app.rest.matcher.MetadataMatcher;
import org.dspace.app.rest.model.GroupRest;
import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest;
@@ -56,6 +57,8 @@ import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
@@ -558,6 +561,68 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
));
}
@Test
public void patchReplaceMultipleDescriptionGroupName() throws Exception {
context.turnOffAuthorisationSystem();
List<String> groupDescription = List.of(
"FIRST",
"SECOND",
"THIRD"
);
Group group =
GroupBuilder.createGroup(context)
.build();
GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
groupService
.addMetadata(
context, group, MetadataSchemaEnum.DC.getName(), "description", null, Item.ANY, groupDescription
);
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/eperson/groups/" + group.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", groupDescription.get(0), 0),
MetadataMatcher.matchMetadata("dc.description", groupDescription.get(1), 1),
MetadataMatcher.matchMetadata("dc.description", groupDescription.get(2), 2)
)
)
);
List<Operation> ops = List.of(
new ReplaceOperation("/metadata/dc.description/0", groupDescription.get(2)),
new ReplaceOperation("/metadata/dc.description/1", groupDescription.get(0)),
new ReplaceOperation("/metadata/dc.description/2", groupDescription.get(1))
);
String requestBody = getPatchContent(ops);
getClient(token)
.perform(
patch("/api/eperson/groups/" + group.getID())
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON)
)
.andExpect(status().isOk());
getClient(token)
.perform(get("/api/eperson/groups/" + group.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", groupDescription.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", groupDescription.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", groupDescription.get(1), 2)
)
)
);
}
@Test
public void patchGroupWithParentUnprocessable() throws Exception {
context.turnOffAuthorisationSystem();

View File

@@ -11,8 +11,8 @@ import static com.jayway.jsonpath.JsonPath.read;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -23,8 +23,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.dspace.app.rest.matcher.MetadataMatcher;
@@ -50,6 +53,7 @@ import org.dspace.content.service.EntityTypeService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.RelationshipTypeService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
@@ -63,6 +67,13 @@ import org.springframework.http.MediaType;
*/
public class PatchMetadataIT extends AbstractEntityIntegrationTest {
private static final String SECTIONS_TRADITIONALPAGEONE_DC_CONTRIBUTOR_AUTHOR =
"/sections/traditionalpageone/dc.contributor.author/%1$s";
private static final String getPath(Object element) {
return String.format(SECTIONS_TRADITIONALPAGEONE_DC_CONTRIBUTOR_AUTHOR, element);
}
@Autowired
private RelationshipTypeService relationshipTypeService;
@@ -75,6 +86,9 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
@Autowired
private WorkspaceItemService workspaceItemService;
@Autowired
private ConfigurationService configurationService;
private Collection collection;
private Collection collection2;
private WorkspaceItem publicationWorkspaceItem;
@@ -297,8 +311,6 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
.withEntityType("Publication")
.build();
String adminToken = getAuthToken(admin.getEmail(), password);
// Make sure we grab the latest instance of the Item from the database before adding a regular author
WorkspaceItem publication = workspaceItemService.find(context, publicationWorkspaceItem.getID());
itemService.addMetadata(context, publication.getItem(),
@@ -920,6 +932,41 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
replaceTraditionalPageOneAuthorTest(3, expectedOrder);
}
@Test
public void replaceMultipleTraditionalPageOnePlainTextAuthorTest() throws Exception {
final boolean virtualMetadataEnabled =
configurationService.getBooleanProperty("item.enable-virtual-metadata", false);
configurationService.setProperty("item.enable-virtual-metadata", false);
try {
initPlainTextPublicationWorkspace();
Map<Integer, String> replacedAuthors =
Map.of(
0, authorsOriginalOrder.get(4),
1, authorsOriginalOrder.get(1),
2, authorsOriginalOrder.get(2),
3, authorsOriginalOrder.get(3),
4, authorsOriginalOrder.get(0)
);
List<String> expectedOrder =
List.of(
authorsOriginalOrder.get(4),
authorsOriginalOrder.get(1),
authorsOriginalOrder.get(2),
authorsOriginalOrder.get(3),
authorsOriginalOrder.get(0)
);
replaceTraditionalPageMultipleAuthorsTest(replacedAuthors, expectedOrder);
} catch (Exception e) {
throw e;
} finally {
configurationService.setProperty("item.enable-virtual-metadata", virtualMetadataEnabled);
}
}
/**
* This test will add an author (dc.contributor.author) within a workspace publication's "traditionalpageone"
@@ -1393,24 +1440,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
ops.add(moveOperation);
String patchBody = getPatchContent(ops);
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())
.content(patchBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk());
String authorField = "dc.contributor.author";
getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf(
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4))
)));
assertReplacementOrder(expectedOrder, patchBody);
}
/**
@@ -1450,33 +1480,66 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
* @param expectedOrder A list of author names sorted in the expected order
*/
private void replaceTraditionalPageOneAuthorTest(int path, List<String> expectedOrder) throws Exception {
List<Operation> ops = new ArrayList<Operation>();
MetadataValueRest value = new MetadataValueRest();
value.setValue(replacedAuthor);
String patchBody =
getPatchContent(
List.of(
this.mapToReplaceOperation(path, replacedAuthor)
)
);
assertReplacementOrder(expectedOrder, patchBody);
}
private void replaceTraditionalPageMultipleAuthorsTest(
Map<Integer, String> values, List<String> expectedOrder
) throws Exception {
List<Operation> ops =
values
.entrySet()
.stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(entry -> mapToReplaceOperation(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
ReplaceOperation replaceOperation = new ReplaceOperation("/sections/traditionalpageone/dc.contributor.author/"
+ path, value);
ops.add(replaceOperation);
String patchBody = getPatchContent(ops);
assertReplacementOrder(expectedOrder, patchBody);
}
private ReplaceOperation mapToReplaceOperation(int path, String author) {
return new ReplaceOperation(getPath(path), new MetadataValueRest(author));
}
private void assertReplacementOrder(List<String> expectedOrder, String patchBody) throws Exception, SQLException {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())
.content(patchBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk());
getClient(token)
.perform(
patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())
.content(patchBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)
)
.andExpect(status().isOk());
String authorField = "dc.contributor.author";
getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf(
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4))
)));
getClient(token)
.perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()))
.andExpect(status().isOk())
.andExpect(
content().contentType(contentType)
)
.andExpect(
jsonPath(
"$.sections.traditionalpageone",
Matchers.allOf(
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)),
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4))
)
)
);
}
/**
@@ -1490,8 +1553,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
List<Operation> ops = new ArrayList<Operation>();
MetadataValueRest value = new MetadataValueRest();
value.setValue(addedAuthor);
AddOperation addOperation = new AddOperation("/sections/traditionalpageone/dc.contributor.author/" + path,
value);
AddOperation addOperation = new AddOperation(getPath(path), value);
ops.add(addOperation);
String patchBody = getPatchContent(ops);
@@ -1525,8 +1587,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
*/
private void removeTraditionalPageOneAuthorTest(int path, List<String> expectedOrder) throws Exception {
List<Operation> ops = new ArrayList<Operation>();
RemoveOperation removeOperation = new RemoveOperation("/sections/traditionalpageone/dc.contributor.author/"
+ path);
RemoveOperation removeOperation = new RemoveOperation(getPath(path));
ops.add(removeOperation);
String patchBody = getPatchContent(ops);
@@ -1600,8 +1661,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
* @param path The "path" index to use for the Move operation
*/
private MoveOperation getTraditionalPageOneMoveAuthorOperation(int from, int path) {
return new MoveOperation("/sections/traditionalpageone/dc.contributor.author/" + path,
"/sections/traditionalpageone/dc.contributor.author/" + from);
return new MoveOperation(
getPath(path),
getPath(from)
);
}
/**

View File

@@ -9,22 +9,34 @@ package org.dspace.app.rest;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
import java.util.UUID;
import org.dspace.app.rest.matcher.MetadataMatcher;
import org.dspace.app.rest.matcher.SiteMatcher;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.ReplaceOperation;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite;
import org.dspace.builder.SiteBuilder;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.Site;
import org.dspace.content.service.SiteService;
import org.dspace.eperson.EPerson;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private SiteService siteService;
@Test
public void findAll() throws Exception {
@@ -77,6 +89,75 @@ public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest {
runPatchMetadataTests(eperson, 403);
}
@Test
public void patchReplaceMultipleDescriptionSite() throws Exception {
context.turnOffAuthorisationSystem();
List<String> siteDescriptions = List.of(
"FIRST",
"SECOND",
"THIRD"
);
Site site = SiteBuilder.createSite(context).build();
this.siteService
.addMetadata(
context, site,
MetadataSchemaEnum.DC.getName(), "description", null,
Item.ANY, siteDescriptions
);
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/core/sites/" + site.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(0), 0),
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(1), 1),
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(2), 2)
)
)
);
List<Operation> ops = List.of(
new ReplaceOperation("/metadata/dc.description/0", siteDescriptions.get(2)),
new ReplaceOperation("/metadata/dc.description/1", siteDescriptions.get(0)),
new ReplaceOperation("/metadata/dc.description/2", siteDescriptions.get(1))
);
String requestBody = getPatchContent(ops);
getClient(token)
.perform(patch("/api/core/sites/" + site.getID())
.content(requestBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(1), 2)
)
)
);
getClient(token)
.perform(get("/api/core/sites/" + site.getID()))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.metadata",
Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(2), 0),
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(0), 1),
MetadataMatcher.matchMetadata("dc.description", siteDescriptions.get(1), 2)
)
)
);
}
private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception {
context.turnOffAuthorisationSystem();
Site site = SiteBuilder.createSite(context).build();

View File

@@ -56,10 +56,12 @@
<constructor-arg value="dc.description.abstract"/>
</bean>
<bean id="arxivPublishedContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleXpathMetadatumContributor">
<bean id="arxivPublishedContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleXpathDateFormatMetadataContributor">
<property name="field" ref="arxiv.published"/>
<property name="query" value="ns:published"/>
<property name="prefixToNamespaceMapping" ref="arxivBasePrefixToNamespaceMapping"/>
<property name="dateFormatFrom" value="yyyy-MM-dd'T'HH:mm:ss'Z'"/>
<property name="dateFormatTo" value="yyyy-MM-dd"></property>
</bean>
<bean id="arxiv.published" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.date.issued"/>

View File

@@ -17,11 +17,9 @@
The VersionedHandleIdentifierProvider creates a new versioned
handle for every new version.
-->
<!--
<bean id="org.dspace.identifier.HandleIdentifierProvider" class="org.dspace.identifier.VersionedHandleIdentifierProvider" scope="singleton">
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
</bean>
-->
<!--
The VersionedHandleIdentifierProviderWithCanonicalHandles
preserves the first handle for every new version. Whenever

View File

@@ -38,6 +38,7 @@
<value>yyyy-MMM-dd</value>
<value>yyyy-MMM</value>
<value>yyyy-MM-dd</value>
<value>yyyy</value>
</list>
</property>
<property name="year" ref="yearContrib"/>