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.CommunityService;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
import org.dspace.discovery.IndexableObject; import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCollection;
import org.dspace.discovery.indexobject.IndexableCommunity; import org.dspace.discovery.indexobject.IndexableCommunity;
@@ -91,6 +92,7 @@ public class SyndicationFeed {
// default DC fields for entry // default DC fields for entry
protected String defaultTitleField = "dc.title"; protected String defaultTitleField = "dc.title";
protected String defaultDescriptionField = "dc.description";
protected String defaultAuthorField = "dc.contributor.author"; protected String defaultAuthorField = "dc.contributor.author";
protected String defaultDateField = "dc.date.issued"; protected String defaultDateField = "dc.date.issued";
private static final String[] defaultDescriptionFields = private static final String[] defaultDescriptionFields =
@@ -196,15 +198,15 @@ public class SyndicationFeed {
// dso is null for the whole site, or a search without scope // dso is null for the whole site, or a search without scope
if (dso == null) { if (dso == null) {
defaultTitle = configurationService.getProperty("dspace.name"); defaultTitle = configurationService.getProperty("dspace.name");
feed.setDescription(localize(labels, MSG_FEED_DESCRIPTION)); defaultDescriptionField = localize(labels, MSG_FEED_DESCRIPTION);
objectURL = resolveURL(request, null); objectURL = resolveURL(request, null);
} else { } else {
Bitstream logo = null; Bitstream logo = null;
if (dso instanceof IndexableCollection) { if (dso instanceof IndexableCollection) {
Collection col = ((IndexableCollection) dso).getIndexedObject(); Collection col = ((IndexableCollection) dso).getIndexedObject();
defaultTitle = col.getName(); defaultTitle = col.getName();
feed.setDescription(collectionService.getMetadataFirstValue(col, defaultDescriptionField = collectionService.getMetadataFirstValue(col,
CollectionService.MD_SHORT_DESCRIPTION, Item.ANY)); CollectionService.MD_SHORT_DESCRIPTION, Item.ANY);
logo = col.getLogo(); logo = col.getLogo();
String cols = configurationService.getProperty("webui.feed.podcast.collections"); String cols = configurationService.getProperty("webui.feed.podcast.collections");
if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) { if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) {
@@ -214,8 +216,8 @@ public class SyndicationFeed {
} else if (dso instanceof IndexableCommunity) { } else if (dso instanceof IndexableCommunity) {
Community comm = ((IndexableCommunity) dso).getIndexedObject(); Community comm = ((IndexableCommunity) dso).getIndexedObject();
defaultTitle = comm.getName(); defaultTitle = comm.getName();
feed.setDescription(communityService.getMetadataFirstValue(comm, defaultDescriptionField = communityService.getMetadataFirstValue(comm,
CommunityService.MD_SHORT_DESCRIPTION, Item.ANY)); CommunityService.MD_SHORT_DESCRIPTION, Item.ANY);
logo = comm.getLogo(); logo = comm.getLogo();
String comms = configurationService.getProperty("webui.feed.podcast.communities"); String comms = configurationService.getProperty("webui.feed.podcast.communities");
if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) { if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) {
@@ -230,6 +232,12 @@ public class SyndicationFeed {
} }
feed.setTitle(labels.containsKey(MSG_FEED_TITLE) ? feed.setTitle(labels.containsKey(MSG_FEED_TITLE) ?
localize(labels, MSG_FEED_TITLE) : defaultTitle); 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.setLink(objectURL);
feed.setPublishedDate(new Date()); feed.setPublishedDate(new Date());
feed.setUri(objectURL); 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); 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 * All the IP matchers
*/ */
@@ -250,7 +245,7 @@ public class IPAuthentication implements AuthenticationMethod {
log.debug(LogHelper.getHeader(context, "authenticated", log.debug(LogHelper.getHeader(context, "authenticated",
"special_groups=" + gsb.toString() "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 @Transient
private StringBuffer eventDetails = null; 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) @OneToMany(fetch = FetchType.LAZY, mappedBy = "dSpaceObject", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("metadataField, place") @OrderBy("metadataField, place")
private List<MetadataValue> metadata = new ArrayList<>(); 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. * @return summary of event details, or null if there are none.
*/ */
public String getDetails() { 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 * one
*/ */
public String getHandle() { 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) { 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 // Create an array of matching values
return values; return values;
} }
@@ -542,7 +547,7 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
int add = 4 - tokens.length; int add = 4 - tokens.length;
if (add > 0) { if (add > 0) {
tokens = (String[]) ArrayUtils.addAll(tokens, new String[add]); tokens = ArrayUtils.addAll(tokens, new String[add]);
} }
return tokens; 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 //If two places are the same then the MetadataValue instance will be placed before the
//RelationshipMetadataValue instance. //RelationshipMetadataValue instance.
//This is done to ensure that the order is correct. //This is done to ensure that the order is correct.
metadataValues.sort(new Comparator<MetadataValue>() { metadataValues.sort((o1, o2) -> {
@Override int compare = o1.getPlace() - o2.getPlace();
public int compare(MetadataValue o1, MetadataValue o2) { if (compare == 0) {
int compare = o1.getPlace() - o2.getPlace(); if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) {
if (compare == 0) { return compare;
if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) { } else if (o1 instanceof RelationshipMetadataValue) {
return compare; return 1;
} else if (o1 instanceof RelationshipMetadataValue) { } else if (o2 instanceof RelationshipMetadataValue) {
return 1; return -1;
} else if (o2 instanceof RelationshipMetadataValue) {
return -1;
}
} }
return compare;
} }
return compare;
}); });
for (MetadataValue metadataValue : metadataValues) { for (MetadataValue metadataValue : metadataValues) {
//Retrieve & store the place for each metadata value //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 authority = metadataValue.getAuthority();
String relationshipId = StringUtils.split(authority, "::")[1]; String relationshipId = StringUtils.split(authority, "::")[1];
Relationship relationship = relationshipService.find(context, Integer.parseInt(relationshipId)); Relationship relationship = relationshipService.find(context, Integer.parseInt(relationshipId));
if (relationship.getLeftItem().equals((Item) dso)) { if (relationship.getLeftItem().equals(dso)) {
relationship.setLeftPlace(mvPlace); relationship.setLeftPlace(mvPlace);
} else { } else {
relationship.setRightPlace(mvPlace); relationship.setRightPlace(mvPlace);

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@@ -288,9 +287,10 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
return itemDAO.findAll(context, true, true); return itemDAO.findAll(context, true, true);
} }
@Override
public Iterator<Item> findAllRegularItems(Context context) throws SQLException { public Iterator<Item> findAllRegularItems(Context context) throws SQLException {
return itemDAO.findAllRegularItems(context); return itemDAO.findAllRegularItems(context);
}; }
@Override @Override
public Iterator<Item> findBySubmitter(Context context, EPerson eperson) throws SQLException { 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> linkedCollections = item.getCollections();
List<Collection> notLinkedCollections = new ArrayList<>(allCollections.size() - linkedCollections.size()); List<Collection> notLinkedCollections = new ArrayList<>(allCollections.size() - linkedCollections.size());
if ((allCollections.size() - linkedCollections.size()) == 0) { if (allCollections.size() - linkedCollections.size() == 0) {
return notLinkedCollections; return notLinkedCollections;
} }
for (Collection collection : allCollections) { 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 * @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. * @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 { public boolean isInProgressSubmission(Context context, Item item) throws SQLException {
return workspaceItemService.findByItem(context, item) != null return workspaceItemService.findByItem(context, item) != null
|| workflowItemService.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 if (!authorizeService
.isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ, .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
defaultPolicy.getID()) && defaultPolicy.getID()) &&
((!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso)) || (!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
(appendMode && this.shouldBeAppended(context, dso, defaultPolicy)))) { appendMode && this.shouldBeAppended(context, dso, defaultPolicy))) {
ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy); ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
newPolicy.setdSpaceObject(dso); newPolicy.setdSpaceObject(dso);
newPolicy.setAction(Constants.READ); newPolicy.setAction(Constants.READ);
@@ -1222,7 +1223,7 @@ prevent the generation of resource policy entry values with null dspace_object a
* Check if the provided default policy should be appended or not to the final * Check if the provided default policy should be appended or not to the final
* item. If an item has at least one custom READ policy any anonymous READ * item. If an item has at least one custom READ policy any anonymous READ
* policy with empty start/end date should be skipped * policy with empty start/end date should be skipped
* *
* @param context DSpace context * @param context DSpace context
* @param dso DSpace object to check for custom read RP * @param dso DSpace object to check for custom read RP
* @param defaultPolicy The policy to check * @param defaultPolicy The policy to check
@@ -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(relationshipMetadataService.getRelationshipMetadata(item, true));
fullMetadataValueList.addAll(dbMetadataValues); fullMetadataValueList.addAll(dbMetadataValues);
item.setCachedMetadata(sortMetadataValueList(fullMetadataValueList)); item.setCachedMetadata(MetadataValueComparators.sort(fullMetadataValueList));
} }
log.debug("Called getMetadata for " + item.getID() + " based on cache"); 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 @Override
public MetadataValue addMetadata(Context context, Item dso, String schema, String element, String qualifier, 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 { 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.ManyToOne;
import javax.persistence.SequenceGenerator; import javax.persistence.SequenceGenerator;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.ReloadableEntity; import org.dspace.core.ReloadableEntity;
@@ -171,6 +172,14 @@ public class MetadataValue implements ReloadableEntity<Integer> {
this.metadataField = metadataField; this.metadataField = metadataField;
} }
/**
* @return {@code MetadataField#getID()}
*/
@Transient
protected Integer getMetadataFieldId() {
return getMetadataField().getID();
}
/** /**
* Get the metadata value. * 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) { public DSpaceObject resolve(Context context, String identifier, String... attributes) {
// We can do nothing with this, return null // We can do nothing with this, return null
try { try {
identifier = handleService.parseHandle(identifier);
return handleService.resolveToObject(context, identifier); return handleService.resolveToObject(context, identifier);
} catch (IllegalStateException | SQLException e) { } catch (IllegalStateException | SQLException e) {
log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), 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 // add a new Identifier for this item: 12345/100.x
String idNew = canonical + DOT + version.getVersionNumber(); 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) //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; int j = 0;
// Use the first dcDate that has been formatted (Config should go from most specific to most lenient) // 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); String dateFormat = dateFormatsToAttempt.get(j);
try { try {
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat); SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
Date date = formatter.parse(dateString); Date date = formatter.parse(dateString);
dcDate = new DCDate(date); dcDate = new DCDate(date);
values.add(metadataFieldMapping.toDCValue(field, formatter.format(date)));
break;
} catch (ParseException e) { } catch (ParseException e) {
// Multiple dateformats can be configured, we don't want to print the entire stacktrace every // Multiple dateformats can be configured, we don't want to print the entire stacktrace every
// time one of those formats fails. // time one of those formats fails.
@@ -136,9 +138,7 @@ public class PubmedDateMetadatumContributor<T> implements MetadataContributor<T>
} }
j++; j++;
} }
if (dcDate != null) { if (dcDate == null) {
values.add(metadataFieldMapping.toDCValue(field, dcDate.toString()));
} else {
log.info( log.info(
"Failed parsing " + dateString + ", check " + "Failed parsing " + dateString + ", check " +
"the configured dataformats in config/spring/api/pubmed-integration.xml"); "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 metadata.bitstream.iiif-virtual.checksum = Checksum
org.dspace.app.itemexport.no-result = The DSpaceObject that you specified has no items. 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-format = Bitstream format
org.dspace.checker.ResultsLogger.bitstream-found = Bitstream found org.dspace.checker.ResultsLogger.bitstream-found = Bitstream found
org.dspace.checker.ResultsLogger.bitstream-id = Bitstream ID org.dspace.checker.ResultsLogger.bitstream-id = Bitstream ID

View File

@@ -13,15 +13,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> 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" <bean id="org.dspace.services.ConfigurationService"
class="org.dspace.servicemanager.config.DSpaceConfigurationService" scope="singleton"/> class="org.dspace.servicemanager.config.DSpaceConfigurationService" scope="singleton"/>
@@ -31,12 +22,6 @@
<property name="configurationService" ref="org.dspace.services.ConfigurationService"/> <property name="configurationService" ref="org.dspace.services.ConfigurationService"/>
</bean--> </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"/> <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) --> <!-- 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" <bean id="org.dspace.identifier.service.IdentifierService"
class="org.dspace.identifier.IdentifierServiceImpl" class="org.dspace.identifier.IdentifierServiceImpl"
autowire="byType" 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. <!-- provider to mint and register DOIs with DSpace.
To mint DOIs you need a registration agency. The DOIIdentifierProvider 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.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail; 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 @Test
public void InsertAndMoveMetadataShiftPlaceTest() throws Exception { public void InsertAndMoveMetadataShiftPlaceTest() throws Exception {
context.turnOffAuthorisationSystem(); 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> </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.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 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.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.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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.BitstreamMatcher;
import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.BundleMatcher;
import org.dspace.app.rest.matcher.HalMatcher; 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.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.app.rest.test.MetadataPatchSuite;
import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.authorize.service.ResourcePolicyService;
@@ -45,6 +49,7 @@ import org.dspace.content.Bundle;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
@@ -1222,6 +1227,92 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest
+ parentCommunity.getLogo().getID(), expectedStatus); + 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 @Test
public void testHiddenMetadataForAnonymousUser() throws Exception { 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.MetadataValueRest;
import org.dspace.app.rest.model.patch.MoveOperation; import org.dspace.app.rest.model.patch.MoveOperation;
import org.dspace.app.rest.model.patch.Operation; 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.AbstractControllerIntegrationTest;
import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.authorize.service.ResourcePolicyService;
@@ -51,6 +52,8 @@ import org.dspace.content.Bitstream;
import org.dspace.content.Bundle; import org.dspace.content.Bundle;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Item; 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.content.service.ItemService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
@@ -68,6 +71,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired @Autowired
ItemService itemService; ItemService itemService;
@Autowired
BundleService bundleService;
private Collection collection; private Collection collection;
private Item item; private Item item;
private Bundle bundle1; 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 @Test
public void deleteBundle() throws Exception { public void deleteBundle() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();

View File

@@ -69,6 +69,7 @@ import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.EntityType; import org.dspace.content.EntityType;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.CollectionService; import org.dspace.content.service.CollectionService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
@@ -499,13 +500,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
getClient(tokenParentAdmin).perform(get("/api/core/collections/" + col1.getID())) getClient(tokenParentAdmin).perform(get("/api/core/collections/" + col1.getID()))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$", .andExpect(jsonPath("$",
Matchers.is((CollectionMatcher.matchCollection(col1))))); Matchers.is(CollectionMatcher.matchCollection(col1))));
String tokenCol1Admin = getAuthToken(col1Admin.getEmail(), "qwerty02"); String tokenCol1Admin = getAuthToken(col1Admin.getEmail(), "qwerty02");
getClient(tokenCol1Admin).perform(get("/api/core/collections/" + col1.getID())) getClient(tokenCol1Admin).perform(get("/api/core/collections/" + col1.getID()))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$", .andExpect(jsonPath("$",
Matchers.is((CollectionMatcher.matchCollection(col1))))); Matchers.is(CollectionMatcher.matchCollection(col1))));
String tokenCol2Admin = getAuthToken(col2Admin.getEmail(), "qwerty03"); String tokenCol2Admin = getAuthToken(col2Admin.getEmail(), "qwerty03");
getClient(tokenCol2Admin).perform(get("/api/core/collections/" + col1.getID())) getClient(tokenCol2Admin).perform(get("/api/core/collections/" + col1.getID()))
@@ -1206,7 +1207,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
) )
))) )))
.andDo(result -> idRef .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") getClient(authToken).perform(post("/api/core/collections")
@@ -3101,6 +3102,81 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
.andExpect(status().isUnauthorized()); .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 @Test
public void patchMetadataCheckReindexingTest() throws Exception { public void patchMetadataCheckReindexingTest() throws Exception {
context.turnOffAuthorisationSystem(); 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.http.MediaType.parseMediaType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 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.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.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 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.GroupRest;
import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest; 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.projection.Projection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.app.rest.test.MetadataPatchSuite;
@@ -56,6 +59,8 @@ import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.service.CommunityService; import org.dspace.content.service.CommunityService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
@@ -1935,6 +1940,78 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest
runPatchMetadataTests(eperson, 403); 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 { private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); 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.builder.WorkflowItemBuilder;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.core.I18nUtil; import org.dspace.core.I18nUtil;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group; import org.dspace.eperson.Group;
@@ -155,7 +156,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds()))
.andDo(result -> idRefNoEmbeds .andDo(result -> idRefNoEmbeds
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));; .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
} finally { } finally {
EPersonBuilder.deleteEPerson(idRef.get()); EPersonBuilder.deleteEPerson(idRef.get());
@@ -1217,7 +1218,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.content(patchBody) .content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.canLogIn", Matchers.is(true)));; .andExpect(jsonPath("$.canLogIn", Matchers.is(true)));
List<Operation> ops2 = new ArrayList<Operation>(); List<Operation> ops2 = new ArrayList<Operation>();
@@ -1295,7 +1296,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.content(patchBody) .content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.requireCertificate", Matchers.is(true)));; .andExpect(jsonPath("$.requireCertificate", Matchers.is(true)));
List<Operation> ops2 = new ArrayList<Operation>(); List<Operation> ops2 = new ArrayList<Operation>();
ReplaceOperation replaceOperation2 = new ReplaceOperation("/certificate",null); ReplaceOperation replaceOperation2 = new ReplaceOperation("/certificate",null);
@@ -1858,6 +1859,78 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
matchMetadata("eperson.firstname", newName))))); 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 @Test
public void patchOwnMetadataByNonAdminUser() throws Exception { 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.EPersonMatcher;
import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.GroupMatcher;
import org.dspace.app.rest.matcher.HalMatcher; 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.GroupRest;
import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataRest;
import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.MetadataValueRest;
@@ -56,6 +57,8 @@ import org.dspace.builder.GroupBuilder;
import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.builder.ResourcePolicyBuilder;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; 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.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService; import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService; 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 @Test
public void patchGroupWithParentUnprocessable() throws Exception { public void patchGroupWithParentUnprocessable() throws Exception {
context.turnOffAuthorisationSystem(); 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.equalTo;
import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; 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.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; 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.post;
@@ -23,8 +23,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.dspace.app.rest.matcher.MetadataMatcher; 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.ItemService;
import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.RelationshipTypeService;
import org.dspace.content.service.WorkspaceItemService; import org.dspace.content.service.WorkspaceItemService;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
@@ -63,6 +67,13 @@ import org.springframework.http.MediaType;
*/ */
public class PatchMetadataIT extends AbstractEntityIntegrationTest { 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 @Autowired
private RelationshipTypeService relationshipTypeService; private RelationshipTypeService relationshipTypeService;
@@ -75,6 +86,9 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
@Autowired @Autowired
private WorkspaceItemService workspaceItemService; private WorkspaceItemService workspaceItemService;
@Autowired
private ConfigurationService configurationService;
private Collection collection; private Collection collection;
private Collection collection2; private Collection collection2;
private WorkspaceItem publicationWorkspaceItem; private WorkspaceItem publicationWorkspaceItem;
@@ -297,8 +311,6 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
.withEntityType("Publication") .withEntityType("Publication")
.build(); .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 // 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()); WorkspaceItem publication = workspaceItemService.find(context, publicationWorkspaceItem.getID());
itemService.addMetadata(context, publication.getItem(), itemService.addMetadata(context, publication.getItem(),
@@ -920,6 +932,41 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
replaceTraditionalPageOneAuthorTest(3, expectedOrder); 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" * 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); ops.add(moveOperation);
String patchBody = getPatchContent(ops); String patchBody = getPatchContent(ops);
String token = getAuthToken(admin.getEmail(), password); assertReplacementOrder(expectedOrder, patchBody);
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))
)));
} }
/** /**
@@ -1450,33 +1480,66 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
* @param expectedOrder A list of author names sorted in the expected order * @param expectedOrder A list of author names sorted in the expected order
*/ */
private void replaceTraditionalPageOneAuthorTest(int path, List<String> expectedOrder) throws Exception { private void replaceTraditionalPageOneAuthorTest(int path, List<String> expectedOrder) throws Exception {
List<Operation> ops = new ArrayList<Operation>(); String patchBody =
MetadataValueRest value = new MetadataValueRest(); getPatchContent(
value.setValue(replacedAuthor); 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); 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); String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) getClient(token)
.content(patchBody) .perform(
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())
.andExpect(status().isOk()); .content(patchBody)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)
)
.andExpect(status().isOk());
String authorField = "dc.contributor.author"; String authorField = "dc.contributor.author";
getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) getClient(token)
.andExpect(status().isOk()) .perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()))
.andExpect(content().contentType(contentType)) .andExpect(status().isOk())
.andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( .andExpect(
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), content().contentType(contentType)
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), )
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), .andExpect(
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), jsonPath(
Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) "$.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>(); List<Operation> ops = new ArrayList<Operation>();
MetadataValueRest value = new MetadataValueRest(); MetadataValueRest value = new MetadataValueRest();
value.setValue(addedAuthor); value.setValue(addedAuthor);
AddOperation addOperation = new AddOperation("/sections/traditionalpageone/dc.contributor.author/" + path, AddOperation addOperation = new AddOperation(getPath(path), value);
value);
ops.add(addOperation); ops.add(addOperation);
String patchBody = getPatchContent(ops); String patchBody = getPatchContent(ops);
@@ -1525,8 +1587,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
*/ */
private void removeTraditionalPageOneAuthorTest(int path, List<String> expectedOrder) throws Exception { private void removeTraditionalPageOneAuthorTest(int path, List<String> expectedOrder) throws Exception {
List<Operation> ops = new ArrayList<Operation>(); List<Operation> ops = new ArrayList<Operation>();
RemoveOperation removeOperation = new RemoveOperation("/sections/traditionalpageone/dc.contributor.author/" RemoveOperation removeOperation = new RemoveOperation(getPath(path));
+ path);
ops.add(removeOperation); ops.add(removeOperation);
String patchBody = getPatchContent(ops); String patchBody = getPatchContent(ops);
@@ -1600,8 +1661,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest {
* @param path The "path" index to use for the Move operation * @param path The "path" index to use for the Move operation
*/ */
private MoveOperation getTraditionalPageOneMoveAuthorOperation(int from, int path) { private MoveOperation getTraditionalPageOneMoveAuthorOperation(int from, int path) {
return new MoveOperation("/sections/traditionalpageone/dc.contributor.author/" + path, return new MoveOperation(
"/sections/traditionalpageone/dc.contributor.author/" + from); getPath(path),
getPath(from)
);
} }
/** /**

View File

@@ -9,22 +9,34 @@ package org.dspace.app.rest;
import static org.hamcrest.Matchers.is; 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.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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.dspace.app.rest.matcher.MetadataMatcher;
import org.dspace.app.rest.matcher.SiteMatcher; 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.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.app.rest.test.MetadataPatchSuite;
import org.dspace.builder.SiteBuilder; import org.dspace.builder.SiteBuilder;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.Site; import org.dspace.content.Site;
import org.dspace.content.service.SiteService;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest { public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private SiteService siteService;
@Test @Test
public void findAll() throws Exception { public void findAll() throws Exception {
@@ -77,6 +89,75 @@ public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest {
runPatchMetadataTests(eperson, 403); 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 { private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
Site site = SiteBuilder.createSite(context).build(); Site site = SiteBuilder.createSite(context).build();

View File

@@ -56,10 +56,12 @@
<constructor-arg value="dc.description.abstract"/> <constructor-arg value="dc.description.abstract"/>
</bean> </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="field" ref="arxiv.published"/>
<property name="query" value="ns:published"/> <property name="query" value="ns:published"/>
<property name="prefixToNamespaceMapping" ref="arxivBasePrefixToNamespaceMapping"/> <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>
<bean id="arxiv.published" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig"> <bean id="arxiv.published" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.date.issued"/> <constructor-arg value="dc.date.issued"/>

View File

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

View File

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