86201: Fix RelationshipService place handling

Correctly take into account the place of other Relationships and/or MDVs when creating/modifying/deleting Relationships
Simplify RelationshipService public API to avoid having to call updatePlaceInRelationship explicitly
Additional tests to cover issues with the previous implementation
This commit is contained in:
Yura Bondarenko
2022-02-01 14:48:51 +01:00
parent 69345ff3fc
commit 9664296af6
14 changed files with 3182 additions and 284 deletions

View File

@@ -924,11 +924,10 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
rightItem = item; rightItem = item;
} }
// Create the relationship // Create the relationship, appending to the end
int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem); Relationship persistedRelationship = relationshipService.create(
int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); c, leftItem, rightItem, foundRelationshipType, -1, -1
Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, );
foundRelationshipType, leftPlace, rightPlace);
relationshipService.update(c, persistedRelationship); relationshipService.update(c, persistedRelationship);
} }

View File

@@ -636,8 +636,14 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
}); });
for (MetadataValue metadataValue : metadataValues) { for (MetadataValue metadataValue : metadataValues) {
//Retrieve & store the place for each metadata value //Retrieve & store the place for each metadata value
if (StringUtils.startsWith(metadataValue.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX) && if (
((RelationshipMetadataValue) metadataValue).isUseForPlace()) { // For virtual MDVs with useForPlace=true,
// update both the place of the metadatum and the place of the Relationship.
// E.g. for an Author relationship,
// the place should be updated using the same principle as dc.contributor.author.
StringUtils.startsWith(metadataValue.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX)
&& ((RelationshipMetadataValue) metadataValue).isUseForPlace()
) {
int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue);
metadataValue.setPlace(mvPlace); metadataValue.setPlace(mvPlace);
String authority = metadataValue.getAuthority(); String authority = metadataValue.getAuthority();
@@ -650,8 +656,16 @@ public abstract class DSpaceObjectServiceImpl<T extends DSpaceObject> implements
} }
relationshipService.update(context, relationship); relationshipService.update(context, relationship);
} else if (!StringUtils.startsWith(metadataValue.getAuthority(), } else if (
Constants.VIRTUAL_AUTHORITY_PREFIX)) { // Otherwise, just set the place of the metadatum
// ...unless the metadatum in question is a relation.* metadatum.
// This case is a leftover from when a Relationship is removed and copied to metadata.
// If we let its place change the order of any remaining Relationships will be affected.
// todo: this makes it so these leftover MDVs can't be reordered later on
!StringUtils.equals(
metadataValue.getMetadataField().getMetadataSchema().getName(), "relation"
)
) {
int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue);
metadataValue.setPlace(mvPlace); metadataValue.setPlace(mvPlace);
} }

View File

@@ -8,10 +8,13 @@
package org.dspace.content; package org.dspace.content;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -24,6 +27,7 @@ import org.dspace.content.service.EntityTypeService;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipService;
import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.RelationshipTypeService;
import org.dspace.content.virtual.VirtualMetadataConfiguration;
import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.content.virtual.VirtualMetadataPopulator;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
@@ -97,7 +101,7 @@ public class RelationshipServiceImpl implements RelationshipService {
// This order of execution should be handled in the creation (create, updateplace, update relationship) // This order of execution should be handled in the creation (create, updateplace, update relationship)
// for a proper place allocation // for a proper place allocation
Relationship relationshipToReturn = relationshipDAO.create(context, relationship); Relationship relationshipToReturn = relationshipDAO.create(context, relationship);
updatePlaceInRelationship(context, relationshipToReturn); updatePlaceInRelationship(context, relationshipToReturn, null, null, true, true);
update(context, relationshipToReturn); update(context, relationshipToReturn);
updateItemsInRelationship(context, relationship); updateItemsInRelationship(context, relationship);
return relationshipToReturn; return relationshipToReturn;
@@ -112,71 +116,364 @@ public class RelationshipServiceImpl implements RelationshipService {
} }
@Override @Override
public void updatePlaceInRelationship(Context context, Relationship relationship) public Relationship move(
throws SQLException, AuthorizeException { Context context, Relationship relationship, Integer newLeftPlace, Integer newRightPlace
) throws SQLException, AuthorizeException {
if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) ||
authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) {
// Don't do anything if neither the leftPlace nor rightPlace was updated
if (newLeftPlace != null || newRightPlace != null) {
// This order of execution should be handled in the creation (create, updateplace, update relationship)
// for a proper place allocation
updatePlaceInRelationship(context, relationship, newLeftPlace, newRightPlace, false, false);
update(context, relationship);
updateItemsInRelationship(context, relationship);
}
return relationship;
} else {
throw new AuthorizeException(
"You do not have write rights on this relationship's items");
}
}
@Override
public Relationship move(
Context context, Relationship relationship, Item newLeftItem, Item newRightItem
) throws SQLException, AuthorizeException {
// If the new Item is the same as the current Item, don't move
newLeftItem = newLeftItem != relationship.getLeftItem() ? newLeftItem : null;
newRightItem = newRightItem != relationship.getRightItem() ? newRightItem : null;
// Don't do anything if neither the leftItem nor rightItem was updated
if (newLeftItem != null || newRightItem != null) {
// First move the Relationship to the back within the current Item's lists
// This ensures that we won't have any gaps once we move the Relationship to a different Item
move(
context, relationship,
newLeftItem != null ? -1 : null,
newRightItem != null ? -1 : null
);
boolean insertLeft = false;
boolean insertRight = false;
// If Item has been changed, mark the previous Item as modified to make sure we discard the old relation.*
// metadata on the next update.
// Set the Relationship's Items to the new ones, appending to the end
if (newLeftItem != null) {
relationship.getLeftItem().setMetadataModified();
relationship.setLeftItem(newLeftItem);
relationship.setLeftPlace(-1);
insertLeft = true;
}
if (newRightItem != null) {
relationship.getRightItem().setMetadataModified();
relationship.setRightItem(newRightItem);
relationship.setRightPlace(-1);
insertRight = true;
}
// This order of execution should be handled in the creation (create, updateplace, update relationship)
// for a proper place allocation
updatePlaceInRelationship(context, relationship, null, null, insertLeft, insertRight);
update(context, relationship);
updateItemsInRelationship(context, relationship);
}
return relationship;
}
/**
* This method will update the place for the Relationship and all other relationships found by the items and
* relationship type of the given Relationship.
*
* @param context The relevant DSpace context
* @param relationship The Relationship object that will have its place updated and that will be used
* to retrieve the other relationships whose place might need to be updated.
* @param newLeftPlace If the Relationship in question is to be moved, the leftPlace it is to be moved to.
* Set this to null if the Relationship has not been moved, i.e. it has just been created,
* deleted or when its Items have been modified.
* @param newRightPlace If the Relationship in question is to be moved, the rightPlace it is to be moved to.
* Set this to null if the Relationship has not been moved, i.e. it has just been created,
* deleted or when its Items have been modified.
* @param insertLeft Whether the Relationship in question should be inserted into the left Item.
* Should be set to true when creating or moving to a different Item.
* @param insertRight Whether the Relationship in question should be inserted into the right Item.
* Should be set to true when creating or moving to a different Item.
* @throws SQLException If something goes wrong
* @throws AuthorizeException
* If the user is not authorized to update the Relationship or its Items
*/
private void updatePlaceInRelationship(
Context context, Relationship relationship,
Integer newLeftPlace, Integer newRightPlace, boolean insertLeft, boolean insertRight
) throws SQLException, AuthorizeException {
Item leftItem = relationship.getLeftItem(); Item leftItem = relationship.getLeftItem();
// Max value is used to ensure that these will get added to the back of the list and thus receive the highest
// (last) place as it's set to a -1 for creation
if (relationship.getLeftPlace() == -1) {
relationship.setLeftPlace(Integer.MAX_VALUE);
}
Item rightItem = relationship.getRightItem(); Item rightItem = relationship.getRightItem();
if (relationship.getRightPlace() == -1) {
relationship.setRightPlace(Integer.MAX_VALUE);
}
List<Relationship> leftRelationships = findByItemAndRelationshipType(context,
leftItem,
relationship.getRelationshipType(), true);
List<Relationship> rightRelationships = findByItemAndRelationshipType(context,
rightItem,
relationship.getRelationshipType(),
false);
// These relationships are only deleted from the temporary lists incase they're present in them so that we can List<Relationship> leftRelationships = findByItemAndRelationshipType(
context, leftItem, relationship.getRelationshipType(), true
);
List<Relationship> rightRelationships = findByItemAndRelationshipType(
context, rightItem, relationship.getRelationshipType(), false
);
// These relationships are only deleted from the temporary lists in case they're present in them so that we can
// properly perform our place calculation later down the line in this method. // properly perform our place calculation later down the line in this method.
if (leftRelationships.contains(relationship)) { boolean deletedFromLeft = !leftRelationships.contains(relationship);
leftRelationships.remove(relationship); boolean deletedFromRight = !rightRelationships.contains(relationship);
} leftRelationships.remove(relationship);
if (rightRelationships.contains(relationship)) { rightRelationships.remove(relationship);
rightRelationships.remove(relationship);
} List<MetadataValue> leftMetadata = getSiblingMetadata(leftItem, relationship, true);
List<MetadataValue> rightMetadata = getSiblingMetadata(rightItem, relationship, false);
// For new relationships added to the end, this will be -1.
// For new relationships added at a specific position, this will contain that position.
// For existing relationships, this will contain the place before it was moved.
// For deleted relationships, this will contain the place before it was deleted.
int oldLeftPlace = relationship.getLeftPlace();
int oldRightPlace = relationship.getRightPlace();
boolean movedUpLeft = resolveRelationshipPlace(
relationship, true, leftRelationships, leftMetadata, oldLeftPlace, newLeftPlace
);
boolean movedUpRight = resolveRelationshipPlace(
relationship, false, rightRelationships, rightMetadata, oldRightPlace, newRightPlace
);
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
//If useForPlace for the leftwardType is false for the relationshipType,
// we need to sort the relationships here based on leftplace.
if (!virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), true)) {
if (!leftRelationships.isEmpty()) {
leftRelationships.sort(Comparator.comparingInt(Relationship::getLeftPlace));
for (int i = 0; i < leftRelationships.size(); i++) {
leftRelationships.get(i).setLeftPlace(i);
}
relationship.setLeftPlace(leftRelationships.size());
} else {
relationship.setLeftPlace(0);
}
} else {
updateItem(context, leftItem);
} shiftSiblings(
relationship, true, oldLeftPlace, movedUpLeft, insertLeft, deletedFromLeft,
leftRelationships, leftMetadata
);
shiftSiblings(
relationship, false, oldRightPlace, movedUpRight, insertRight, deletedFromRight,
rightRelationships, rightMetadata
);
//If useForPlace for the rightwardType is false for the relationshipType, updateItem(context, leftItem);
// we need to sort the relationships here based on the rightplace. updateItem(context, rightItem);
if (!virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), false)) {
if (!rightRelationships.isEmpty()) {
rightRelationships.sort(Comparator.comparingInt(Relationship::getRightPlace));
for (int i = 0; i < rightRelationships.size(); i++) {
rightRelationships.get(i).setRightPlace(i);
}
relationship.setRightPlace(rightRelationships.size());
} else {
relationship.setRightPlace(0);
}
} else {
updateItem(context, rightItem);
}
context.restoreAuthSystemState(); context.restoreAuthSystemState();
}
/**
* Return the MDVs in the Item's MDF corresponding to the given Relationship.
* Return an empty list if the Relationship isn't mapped to any MDF
* or if the mapping is configured with useForPlace=false.
*
* This returns actual metadata (not virtual) which in the same metadata field as the useForPlace.
* For a publication with 2 author relationships and 3 plain text dc.contributor.author values,
* it would return the 3 plain text dc.contributor.author values.
* For a person related to publications, it would return an empty list.
*/
private List<MetadataValue> getSiblingMetadata(
Item item, Relationship relationship, boolean isLeft
) {
List<MetadataValue> metadata = new ArrayList<>();
if (virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), isLeft)) {
HashMap<String, VirtualMetadataConfiguration> mapping;
if (isLeft) {
mapping = virtualMetadataPopulator.getMap().get(relationship.getRelationshipType().getLeftwardType());
} else {
mapping = virtualMetadataPopulator.getMap().get(relationship.getRelationshipType().getRightwardType());
}
if (mapping != null) {
for (String mdf : mapping.keySet()) {
metadata.addAll(
// Make sure we're only looking at database MDVs; if the relationship currently overlaps
// one of these, its virtual MDV will overwrite the database MDV in itemService.getMetadata()
// The relationship pass should be sufficient to move any sibling virtual MDVs.
item.getMetadata()
.stream()
.filter(mdv -> mdv.getMetadataField().toString().equals(mdf.replace(".", "_")))
.collect(Collectors.toList())
);
}
}
}
return metadata;
}
/**
* Set the left/right place of a Relationship
* - To a new place in case it's being moved
* - Resolve -1 to the actual last place based on the places of its sibling Relationships and/or MDVs
* and determine if it has been moved up in the list.
*
* Examples:
* - Insert a Relationship at place 3
* newPlace starts out as null and is not updated. Return movedUp=false
* - Insert a Relationship at place -1
* newPlace starts out as null and is resolved to e.g. 6. Update the Relationship and return movedUp=false
* - Move a Relationship from place 4 to 2
* Update the Relationship and return movedUp=false.
* - Move a Relationship from place 2 to -1
* newPlace starts out as -1 and is resolved to e.g. 5. Update the relationship and return movedUp=true.
* - Remove a relationship from place 1
* Return movedUp=false
*
* @param relationship the Relationship that's being updated
* @param isLeft whether to consider the left side of the Relationship.
* This method should be called twice, once with isLeft=true and once with isLeft=false.
* Make sure this matches the provided relationships/metadata/oldPlace/newPlace.
* @param relationships the list of sibling Relationships
* @param metadata the list of sibling MDVs
* @param oldPlace the previous place for this Relationship, in case it has been moved.
* Otherwise, the current place of a deleted Relationship
* or the place a Relationship has been inserted.
* @param newPlace The new place for this Relationship. Will be null on insert/delete.
* @return true if the Relationship was moved and newPlace > oldPlace
*/
private boolean resolveRelationshipPlace(
Relationship relationship, boolean isLeft,
List<Relationship> relationships, List<MetadataValue> metadata,
int oldPlace, Integer newPlace
) {
boolean movedUp = false;
if (newPlace != null) {
// We're moving an existing Relationship...
if (newPlace == -1) {
// ...to the end of the list
int nextPlace = getNextPlace(relationships, metadata, isLeft);
if (nextPlace == oldPlace) {
// If this Relationship is already at the end, do nothing.
newPlace = oldPlace;
} else {
// Subtract 1 from the next place since we're moving, not inserting and
// the total number of Relationships stays the same.
newPlace = nextPlace - 1;
}
}
if (newPlace > oldPlace) {
// ...up the list. We have to keep track of this in order to shift correctly later on
movedUp = true;
}
} else if (oldPlace == -1) {
// We're _not_ moving an existing Relationship. The newPlace is already set in the Relationship object.
// We only need to resolve it to the end of the list if it's set to -1, otherwise we can just keep it as is.
newPlace = getNextPlace(relationships, metadata, isLeft);
}
if (newPlace != null) {
setPlace(relationship, isLeft, newPlace);
}
return movedUp;
}
/**
* Return the index of the next place in a list of Relationships and Metadata.
* By not relying on the size of both lists we can support one-to-many virtual MDV mappings.
* @param isLeft whether to take the left or right place of each Relationship
*/
private int getNextPlace(List<Relationship> relationships, List<MetadataValue> metadata, boolean isLeft) {
return Stream.concat(
metadata.stream().map(MetadataValue::getPlace),
relationships.stream().map(r -> getPlace(r, isLeft))
).max(Integer::compare)
.map(integer -> integer + 1)
.orElse(0);
}
/**
* Adjust the left/right place of sibling Relationships and MDVs
*
* Examples: with sibling Relationships R,S,T and metadata a,b,c
* - Insert T at place 1 aRbSc -> a T RbSc
* Shift all siblings with place >= 1 one place to the right
* - Delete R from place 2 aT R bSc -> aTbSc
* Shift all siblings with place > 2 one place to the left
* - Move S from place 3 to place 2 (movedUp=false) aTb S c -> aT S bc
* Shift all siblings with 2 < place <= 3 one place to the right
* - Move T from place 1 to place 3 (movedUp=true) a T Sbc -> aSb T c
* Shift all siblings with 1 < place <= 3 one place to the left
*
* @param relationship the Relationship that's being updated
* @param isLeft whether to consider the left side of the Relationship.
* This method should be called twice, once with isLeft=true and once with isLeft=false.
* Make sure this matches the provided relationships/metadata/oldPlace/newPlace.
* @param oldPlace the previous place for this Relationship, in case it has been moved.
* Otherwise, the current place of a deleted Relationship
* or the place a Relationship has been inserted.
* @param movedUp if this Relationship has been moved up the list, e.g. from place 2 to place 4
* @param deleted whether this Relationship has been deleted
* @param relationships the list of sibling Relationships
* @param metadata the list of sibling MDVs
*/
private void shiftSiblings(
Relationship relationship, boolean isLeft, int oldPlace, boolean movedUp, boolean inserted, boolean deleted,
List<Relationship> relationships, List<MetadataValue> metadata
) {
int newPlace = getPlace(relationship, isLeft);
for (Relationship sibling : relationships) {
int siblingPlace = getPlace(sibling, isLeft);
if (
(deleted && siblingPlace > newPlace)
// If the relationship was deleted, all relationships after it should shift left
// We must make the distinction between deletes and moves because for inserts oldPlace == newPlace
|| (movedUp && siblingPlace <= newPlace && siblingPlace > oldPlace)
// If the relationship was moved up e.g. from place 2 to 5, all relationships
// with place > 2 (the old place) and <= to 5 should shift left
) {
setPlace(sibling, isLeft, siblingPlace - 1);
} else if (
(inserted && siblingPlace >= newPlace)
// If the relationship was inserted, all relationships starting from that place should shift right
// We must make the distinction between inserts and moves because for inserts oldPlace == newPlace
|| (!movedUp && siblingPlace >= newPlace && siblingPlace < oldPlace)
// If the relationship was moved down e.g. from place 5 to 2, all relationships
// with place >= 2 and < 5 (the old place) should shift right
) {
setPlace(sibling, isLeft, siblingPlace + 1);
}
}
for (MetadataValue mdv : metadata) {
int mdvPlace = mdv.getPlace();
if (
(deleted && mdvPlace > newPlace)
// If the relationship was deleted, all metadata after it should shift left
// We must make the distinction between deletes and moves because for inserts oldPlace == newPlace
// If the reltionship was copied to metadata on deletion:
// - the plain text can be after the relationship (in which case it's moved forward again)
// - or before the relationship (in which case it remains in place)
|| (movedUp && mdvPlace <= newPlace && mdvPlace > oldPlace)
// If the relationship was moved up e.g. from place 2 to 5, all metadata
// with place > 2 (the old place) and <= to 5 should shift left
) {
mdv.setPlace(mdvPlace - 1);
} else if (
(inserted && mdvPlace >= newPlace)
// If the relationship was inserted, all relationships starting from that place should shift right
// We must make the distinction between inserts and moves because for inserts oldPlace == newPlace
|| (!movedUp && mdvPlace >= newPlace && mdvPlace < oldPlace)
// If the relationship was moved down e.g. from place 5 to 2, all relationships
// with place >= 2 and < 5 (the old place) should shift right
) {
mdv.setPlace(mdvPlace + 1);
}
}
}
private int getPlace(Relationship relationship, boolean isLeft) {
if (isLeft) {
return relationship.getLeftPlace();
} else {
return relationship.getRightPlace();
}
}
private void setPlace(Relationship relationship, boolean isLeft, int place) {
if (isLeft) {
relationship.setLeftPlace(place);
} else {
relationship.setRightPlace(place);
}
} }
@Override @Override
@@ -186,16 +483,6 @@ public class RelationshipServiceImpl implements RelationshipService {
itemService.update(context, relatedItem); itemService.update(context, relatedItem);
} }
@Override
public int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException {
return relationshipDAO.findNextLeftPlaceByLeftItem(context, item);
}
@Override
public int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException {
return relationshipDAO.findNextRightPlaceByRightItem(context, item);
}
private boolean isRelationshipValidToCreate(Context context, Relationship relationship) throws SQLException { private boolean isRelationshipValidToCreate(Context context, Relationship relationship) throws SQLException {
RelationshipType relationshipType = relationship.getRelationshipType(); RelationshipType relationshipType = relationship.getRelationshipType();
@@ -375,7 +662,7 @@ public class RelationshipServiceImpl implements RelationshipService {
if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) ||
authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) {
relationshipDAO.delete(context, relationship); relationshipDAO.delete(context, relationship);
updatePlaceInRelationship(context, relationship); updatePlaceInRelationship(context, relationship, null, null, false, false);
updateItemsInRelationship(context, relationship); updateItemsInRelationship(context, relationship);
} else { } else {
throw new AuthorizeException( throw new AuthorizeException(
@@ -508,6 +795,9 @@ public class RelationshipServiceImpl implements RelationshipService {
/** /**
* Converts virtual metadata from RelationshipMetadataValue objects to actual item metadata. * Converts virtual metadata from RelationshipMetadataValue objects to actual item metadata.
* The resulting MDVs are added in front or behind the Relationship's virtual MDVs.
* The Relationship's virtual MDVs may be shifted right, and all subsequent metadata will be shifted right.
* So this method ensures the places are still valid.
* *
* @param context The relevant DSpace context * @param context The relevant DSpace context
* @param relationship The relationship containing the left and right items * @param relationship The relationship containing the left and right items
@@ -524,7 +814,15 @@ public class RelationshipServiceImpl implements RelationshipService {
relationshipMetadataService.findRelationshipMetadataValueForItemRelationship(context, relationshipMetadataService.findRelationshipMetadataValueForItemRelationship(context,
relationship.getLeftItem(), entityTypeString, relationship, true); relationship.getLeftItem(), entityTypeString, relationship, true);
for (RelationshipMetadataValue relationshipMetadataValue : relationshipMetadataValues) { for (RelationshipMetadataValue relationshipMetadataValue : relationshipMetadataValues) {
itemService.addAndShiftRightMetadata(context, relationship.getLeftItem(), // This adds the plain text metadata values on the same spot as the virtual values.
// This will be overruled in org.dspace.content.DSpaceObjectServiceImpl.update
// in the line below but it's not important whether the plain text or virtual values end up on top.
// The virtual values will eventually be deleted, and the others shifted
// This is required because addAndShiftRightMetadata has issues on metadata fields containing
// relationship values which are not useForPlace, while the relationhip type has useForPlace
// E.g. when using addAndShiftRightMetadata on relation.isAuthorOfPublication, it will break the order
// from dc.contributor.author
itemService.addMetadata(context, relationship.getLeftItem(),
relationshipMetadataValue.getMetadataField(). relationshipMetadataValue.getMetadataField().
getMetadataSchema().getName(), getMetadataSchema().getName(),
relationshipMetadataValue.getMetadataField().getElement(), relationshipMetadataValue.getMetadataField().getElement(),
@@ -533,6 +831,7 @@ public class RelationshipServiceImpl implements RelationshipService {
relationshipMetadataValue.getValue(), null, -1, relationshipMetadataValue.getValue(), null, -1,
relationshipMetadataValue.getPlace()); relationshipMetadataValue.getPlace());
} }
//This will ensure the new values no longer overlap, but won't break the order
itemService.update(context, relationship.getLeftItem()); itemService.update(context, relationship.getLeftItem());
} }
if (copyToRightItem) { if (copyToRightItem) {
@@ -542,7 +841,7 @@ public class RelationshipServiceImpl implements RelationshipService {
relationshipMetadataService.findRelationshipMetadataValueForItemRelationship(context, relationshipMetadataService.findRelationshipMetadataValueForItemRelationship(context,
relationship.getRightItem(), entityTypeString, relationship, true); relationship.getRightItem(), entityTypeString, relationship, true);
for (RelationshipMetadataValue relationshipMetadataValue : relationshipMetadataValues) { for (RelationshipMetadataValue relationshipMetadataValue : relationshipMetadataValues) {
itemService.addAndShiftRightMetadata(context, relationship.getRightItem(), itemService.addMetadata(context, relationship.getRightItem(),
relationshipMetadataValue.getMetadataField(). relationshipMetadataValue.getMetadataField().
getMetadataSchema().getName(), getMetadataSchema().getName(),
relationshipMetadataValue.getMetadataField().getElement(), relationshipMetadataValue.getMetadataField().getElement(),

View File

@@ -53,28 +53,6 @@ public interface RelationshipDAO extends GenericDAO<Relationship> {
List<Relationship> findByItem(Context context, Item item, Integer limit, Integer offset, boolean excludeTilted) List<Relationship> findByItem(Context context, Item item, Integer limit, Integer offset, boolean excludeTilted)
throws SQLException; throws SQLException;
/**
* This method returns the next leftplace integer to use for a relationship with this item as the leftItem
*
* @param context The relevant DSpace context
* @param item The item to be matched on leftItem
* @return The next integer to be used for the leftplace of a relationship with the given item
* as a left item
* @throws SQLException If something goes wrong
*/
int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException;
/**
* This method returns the next rightplace integer to use for a relationship with this item as the rightItem
*
* @param context The relevant DSpace context
* @param item The item to be matched on rightItem
* @return The next integer to be used for the rightplace of a relationship with the given item
* as a right item
* @throws SQLException If something goes wrong
*/
int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException;
/** /**
* This method returns a list of Relationship objects for the given RelationshipType object. * This method returns a list of Relationship objects for the given RelationshipType object.
* It will construct a list of all Relationship objects that have the given RelationshipType object * It will construct a list of all Relationship objects that have the given RelationshipType object

View File

@@ -85,38 +85,6 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO<Relationship> impl
return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); return count(context, criteriaQuery, criteriaBuilder, relationshipRoot);
} }
@Override
public int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class);
Root<Relationship> relationshipRoot = criteriaQuery.from(Relationship.class);
criteriaQuery.select(relationshipRoot);
criteriaQuery.where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item));
List<Relationship> list = list(context, criteriaQuery, false, Relationship.class, -1, -1);
list.sort((o1, o2) -> o2.getLeftPlace() - o1.getLeftPlace());
if (!list.isEmpty()) {
return list.get(0).getLeftPlace() + 1;
} else {
return 0;
}
}
@Override
public int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class);
Root<Relationship> relationshipRoot = criteriaQuery.from(Relationship.class);
criteriaQuery.select(relationshipRoot);
criteriaQuery.where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item));
List<Relationship> list = list(context, criteriaQuery, false, Relationship.class, -1, -1);
list.sort((o1, o2) -> o2.getRightPlace() - o1.getRightPlace());
if (!list.isEmpty()) {
return list.get(0).getRightPlace() + 1;
} else {
return 0;
}
}
@Override @Override
public List<Relationship> findByRelationshipType(Context context, RelationshipType relationshipType) public List<Relationship> findByRelationshipType(Context context, RelationshipType relationshipType)
throws SQLException { throws SQLException {

View File

@@ -78,26 +78,49 @@ public interface RelationshipService extends DSpaceCRUDService<Relationship> {
public Relationship create(Context context, Relationship relationship) throws SQLException, AuthorizeException; public Relationship create(Context context, Relationship relationship) throws SQLException, AuthorizeException;
/** /**
* This method returns the next leftplace integer to use for a relationship with this item as the leftItem * Move the given relationship to a new leftPlace and/or rightPlace.
* *
* @param context The relevant DSpace context * This will
* @param item The item that has to be the leftItem of a relationship for it to qualify * 1. verify whether the move is authorized
* @return The next integer to be used for the leftplace of a relationship with the given item * 2. move the relationship to the specified left/right place
* as a left item * 3. update the left/right place of other relationships and/or metadata in order to resolve the move without
* @throws SQLException If something goes wrong * leaving any gaps
*
* At least one of the new places should be non-null, otherwise no changes will be made.
*
* @param context The relevant DSpace context
* @param relationship The Relationship to move
* @param newLeftPlace The value to set the leftPlace of this Relationship to
* @param newRightPlace The value to set the rightPlace of this Relationship to
* @return The moved relationship with updated place variables
* @throws SQLException If something goes wrong
* @throws AuthorizeException If the user is not authorized to update the Relationship or its Items
*/ */
int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException; Relationship move(Context context, Relationship relationship, Integer newLeftPlace, Integer newRightPlace)
throws SQLException, AuthorizeException;
/** /**
* This method returns the next rightplace integer to use for a relationship with this item as the rightItem * Move the given relationship to a new leftItem and/or rightItem.
* *
* @param context The relevant DSpace context * This will
* @param item The item that has to be the rightitem of a relationship for it to qualify * 1. move the relationship to the last place in its current left or right Item. This ensures that we don't leave
* @return The next integer to be used for the rightplace of a relationship with the given item * any gaps when moving the relationship to a new Item.
* as a right item * If only one of the relationship's Items is changed,the order of relationships and metadatain the other
* @throws SQLException If something goes wrong * will not be affected
* 2. insert the relationship into the new Item(s)
*
* At least one of the new Items should be non-null, otherwise no changes will be made.
*
* @param context The relevant DSpace context
* @param relationship The Relationship to move
* @param newLeftItem The value to set the leftItem of this Relationship to
* @param newRightItem The value to set the rightItem of this Relationship to
* @return The moved relationship with updated left/right Items variables
* @throws SQLException If something goes wrong
* @throws AuthorizeException If the user is not authorized to update the Relationship or its Items
*/ */
int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException; Relationship move(Context context, Relationship relationship, Item newLeftItem, Item newRightItem)
throws SQLException, AuthorizeException;
/** /**
* This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given
@@ -143,19 +166,6 @@ public interface RelationshipService extends DSpaceCRUDService<Relationship> {
int limit, int offset) int limit, int offset)
throws SQLException; throws SQLException;
/**
* This method will update the place for the Relationship and all other relationships found by the items and
* relationship type of the given Relationship. It will give this Relationship the last place in both the
* left and right place determined by querying for the list of leftRelationships and rightRelationships
* by the leftItem, rightItem and relationshipType of the given Relationship.
* @param context The relevant DSpace context
* @param relationship The Relationship object that will have it's place updated and that will be used
* to retrieve the other relationships whose place might need to be updated
* @throws SQLException If something goes wrong
*/
public void updatePlaceInRelationship(Context context, Relationship relationship)
throws SQLException, AuthorizeException;
/** /**
* This method will update the given item's metadata order. * This method will update the given item's metadata order.
* If the relationships for the item have been modified and will calculate the place based on a * If the relationships for the item have been modified and will calculate the place based on a

View File

@@ -117,7 +117,8 @@ public class RelationshipBuilder extends AbstractBuilder<Relationship, Relations
this.context = context; this.context = context;
try { try {
relationship = relationshipService.create(context, leftItem, rightItem, relationshipType, 0, 0); //place -1 will add it to the end
relationship = relationshipService.create(context, leftItem, rightItem, relationshipType, -1, -1);
} catch (SQLException | AuthorizeException e) { } catch (SQLException | AuthorizeException e) {
log.warn("Failed to create relationship", e); log.warn("Failed to create relationship", e);
} }

View File

@@ -599,49 +599,6 @@ public class RelationshipMetadataServiceIT extends AbstractIntegrationTestWithDa
.size(), equalTo(1)); .size(), equalTo(1));
} }
@Test
public void testGetNextRightPlace() throws Exception {
assertThat(relationshipService.findNextRightPlaceByRightItem(context, rightItem), equalTo(0));
initPublicationAuthor();
assertThat(relationshipService.findNextRightPlaceByRightItem(context, rightItem), equalTo(1));
context.turnOffAuthorisationSystem();
Community community = CommunityBuilder.createCommunity(context).build();
Collection col = CollectionBuilder.createCollection(context, community).build();
Item secondItem = ItemBuilder.createItem(context, col).withEntityType("Publication").build();
RelationshipBuilder.createRelationshipBuilder(context, secondItem, rightItem,
isAuthorOfPublicationRelationshipType).build();
context.restoreAuthSystemState();
assertThat(relationshipService.findNextRightPlaceByRightItem(context, rightItem), equalTo(2));
}
@Test
public void testGetNextLeftPlace() throws Exception {
assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, leftItem), equalTo(0));
initPublicationAuthor();
assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, leftItem), equalTo(1));
context.turnOffAuthorisationSystem();
Community community = CommunityBuilder.createCommunity(context).build();
Collection col = CollectionBuilder.createCollection(context, community).build();
Item secondAuthor = ItemBuilder.createItem(context, col).withEntityType("Author")
.withPersonIdentifierFirstName("firstName")
.withPersonIdentifierLastName("familyName").build();
RelationshipBuilder.createRelationshipBuilder(context, leftItem, secondAuthor,
isAuthorOfPublicationRelationshipType).build();
context.restoreAuthSystemState();
assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, leftItem), equalTo(2));
}
@Test @Test
public void testGetVirtualMetadata() throws SQLException, AuthorizeException { public void testGetVirtualMetadata() throws SQLException, AuthorizeException {
// Journal, JournalVolume, JournalIssue, Publication items, related to each other using the relationship types // Journal, JournalVolume, JournalIssue, Publication items, related to each other using the relationship types

View File

@@ -122,32 +122,6 @@ public class RelationshipServiceImplTest {
} }
} }
@Test
public void testFindLeftPlaceByLeftItem() throws Exception {
// Declare objects utilized in unit test
Item item = mock(Item.class);
// Mock DAO to return mocked left place as 0
when(relationshipDAO.findNextLeftPlaceByLeftItem(context, item)).thenReturn(0);
// The left place reported from out mocked item should match the DAO's report of the left place
assertEquals("TestFindLeftPlaceByLeftItem 0", relationshipDAO.findNextLeftPlaceByLeftItem(context, item),
relationshipService.findNextLeftPlaceByLeftItem(context, item));
}
@Test
public void testFindRightPlaceByRightItem() throws Exception {
// Declare objects utilized in unit test
Item item = mock(Item.class);
// Mock lower level DAO to return mocked right place as 0
when(relationshipDAO.findNextRightPlaceByRightItem(context, item)).thenReturn(0);
// The right place reported from out mocked item should match the DAO's report of the right place
assertEquals("TestFindRightPlaceByRightItem 0", relationshipDAO.findNextRightPlaceByRightItem(context, item),
relationshipService.findNextRightPlaceByRightItem(context, item));
}
@Test @Test
public void testFindByItemAndRelationshipType() throws Exception { public void testFindByItemAndRelationshipType() throws Exception {
// Declare objects utilized in unit test // Declare objects utilized in unit test

View File

@@ -138,28 +138,6 @@ public class RelationshipDAOImplTest extends AbstractIntegrationTest {
-1, -1, false)); -1, -1, false));
} }
/**
* Test findNextLeftPlaceByLeftItem should return 0 given our test left Item itemOne.
*
* @throws Exception
*/
@Test
public void testFindNextLeftPlaceByLeftItem() throws Exception {
assertEquals("TestNextLeftPlaceByLeftItem 0", 1, relationshipService.findNextLeftPlaceByLeftItem(context,
itemOne));
}
/**
* Test findNextRightPlaceByRightItem should return 0 given our test right Item itemTwo.
*
* @throws Exception
*/
@Test
public void testFindNextRightPlaceByRightItem() throws Exception {
assertEquals("TestNextRightPlaceByRightItem 0", 1, relationshipService.findNextRightPlaceByRightItem(context,
itemTwo));
}
/** /**
* Test findByRelationshipType should return our defined relationshipsList given our test RelationshipType * Test findByRelationshipType should return our defined relationshipsList given our test RelationshipType
* relationshipType * relationshipType

View File

@@ -0,0 +1,202 @@
/**
* 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.service;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
import java.sql.SQLException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.logging.log4j.Logger;
import org.dspace.AbstractUnitTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.junit.Before;
import org.junit.Test;
public class ItemServiceTest extends AbstractUnitTest {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemServiceTest.class);
protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService();
protected RelationshipTypeService relationshipTypeService = ContentServiceFactory.getInstance()
.getRelationshipTypeService();
protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService();
protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
Community community;
Collection col;
Item item;
String authorQualifier = "author";
String contributorElement = "contributor";
String dcSchema = "dc";
/**
* This method will be run before every test as per @Before. It will
* initialize resources required for the tests.
*/
@Before
@Override
public void init() {
super.init();
try {
context.turnOffAuthorisationSystem();
community = communityService.create(null, context);
col = collectionService.create(context, community);
WorkspaceItem is = workspaceItemService.create(context, col, false);
WorkspaceItem authorIs = workspaceItemService.create(context, col, false);
item = installItemService.installItem(context, is);
itemService.addMetadata(context, item, "dspace", "entity", "type", null, "Publication");
context.restoreAuthSystemState();
} catch (AuthorizeException ex) {
log.error("Authorization Error in init", ex);
fail("Authorization Error in init: " + ex.getMessage());
} catch (SQLException ex) {
log.error("SQL Error in init", ex);
fail("SQL Error in init: " + ex.getMessage());
}
}
@Test
public void InsertAndMoveMetadataShiftPlaceTest() throws Exception {
context.turnOffAuthorisationSystem();
// Here we add the first set of metadata to the item
itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one");
itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two");
itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three");
context.restoreAuthSystemState();
// The code below performs the mentioned assertions to ensure the place is correct
List<MetadataValue> list = itemService
.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
assertThat(list.size(), equalTo(3));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2));
context.turnOffAuthorisationSystem();
// This is where we add metadata at place=1
itemService.addAndShiftRightMetadata(
context, item, dcSchema, contributorElement, authorQualifier, null, "test, four", null, -1, 1
);
// Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned
list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY)
.stream()
.sorted(Comparator.comparingInt(MetadataValue::getPlace))
.collect(Collectors.toList());
assertThat(list.size(), equalTo(4));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 1, list.get(1));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 2, list.get(2));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3));
// And move metadata from place=2 to place=0
itemService.moveMetadata(context, item, dcSchema, contributorElement, authorQualifier, 2, 0);
context.restoreAuthSystemState();
// Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned
list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY)
.stream()
.sorted(Comparator.comparingInt(MetadataValue::getPlace))
.collect(Collectors.toList());
assertThat(list.size(), equalTo(4));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 0, list.get(0));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 1, list.get(1));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 2, list.get(2));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3));
}
@Test
public void InsertAndMoveMetadataOnePlaceForwardTest() throws Exception {
context.turnOffAuthorisationSystem();
// Here we add the first set of metadata to the item
itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one");
itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two");
itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three");
context.restoreAuthSystemState();
// The code below performs the mentioned assertions to ensure the place is correct
List<MetadataValue> list = itemService
.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
assertThat(list.size(), equalTo(3));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2));
context.turnOffAuthorisationSystem();
// This is where we add metadata at place=1
itemService.addAndShiftRightMetadata(
context, item, dcSchema, contributorElement, authorQualifier, null, "test, four", null, -1, 1
);
// Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned
list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY)
.stream()
.sorted(Comparator.comparingInt(MetadataValue::getPlace))
.collect(Collectors.toList());
assertThat(list.size(), equalTo(4));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 1, list.get(1));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 2, list.get(2));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3));
// And move metadata from place=1 to place=2
itemService.moveMetadata(context, item, dcSchema, contributorElement, authorQualifier, 1, 2);
context.restoreAuthSystemState();
// Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned
list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY)
.stream()
.sorted(Comparator.comparingInt(MetadataValue::getPlace))
.collect(Collectors.toList());
assertThat(list.size(), equalTo(4));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 2, list.get(2));
assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3));
}
private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value,
String authority, int place, MetadataValue metadataValue) {
assertThat(metadataValue.getValue(), equalTo(value));
assertThat(metadataValue.getMetadataField().getMetadataSchema().getName(), equalTo(dcSchema));
assertThat(metadataValue.getMetadataField().getElement(), equalTo(contributorElement));
assertThat(metadataValue.getMetadataField().getQualifier(), equalTo(authorQualifier));
assertThat(metadataValue.getAuthority(), equalTo(authority));
assertThat(metadataValue.getPlace(), equalTo(place));
}
}

View File

@@ -165,25 +165,21 @@ public class RelationshipRestRepository extends DSpaceRestRepository<Relationshi
} }
List<DSpaceObject> dSpaceObjects = utils.constructDSpaceObjectList(context, stringList); List<DSpaceObject> dSpaceObjects = utils.constructDSpaceObjectList(context, stringList);
if (dSpaceObjects.size() == 1 && dSpaceObjects.get(0).getType() == Constants.ITEM) { if (dSpaceObjects.size() == 1 && dSpaceObjects.get(0).getType() == Constants.ITEM) {
Item replacementItemInRelationship = (Item) dSpaceObjects.get(0); Item replacementItemInRelationship = (Item) dSpaceObjects.get(0);
Item leftItem; Item newLeftItem;
Item rightItem; Item newRightItem;
if (itemToReplaceIsRight) { if (itemToReplaceIsRight) {
leftItem = relationship.getLeftItem(); newLeftItem = null;
rightItem = replacementItemInRelationship; newRightItem = replacementItemInRelationship;
} else { } else {
leftItem = replacementItemInRelationship; newLeftItem = replacementItemInRelationship;
rightItem = relationship.getRightItem(); newRightItem = null;
} }
if (isAllowedToModifyRelationship(context, relationship, leftItem, rightItem)) { if (isAllowedToModifyRelationship(context, relationship, newLeftItem, newRightItem)) {
relationship.setLeftItem(leftItem);
relationship.setRightItem(rightItem);
try { try {
relationshipService.updatePlaceInRelationship(context, relationship); relationshipService.move(context, relationship, newLeftItem, newRightItem);
relationshipService.update(context, relationship);
context.commit(); context.commit();
context.reloadEntity(relationship); context.reloadEntity(relationship);
} catch (AuthorizeException e) { } catch (AuthorizeException e) {
@@ -242,15 +238,17 @@ public class RelationshipRestRepository extends DSpaceRestRepository<Relationshi
relationship.setLeftwardValue(relationshipRest.getLeftwardValue()); relationship.setLeftwardValue(relationshipRest.getLeftwardValue());
relationship.setRightwardValue(relationshipRest.getRightwardValue()); relationship.setRightwardValue(relationshipRest.getRightwardValue());
Integer newRightPlace = null;
Integer newLeftPlace = null;
if (jsonNode.hasNonNull("rightPlace")) { if (jsonNode.hasNonNull("rightPlace")) {
relationship.setRightPlace(relationshipRest.getRightPlace()); newRightPlace = relationshipRest.getRightPlace();
} }
if (jsonNode.hasNonNull("leftPlace")) { if (jsonNode.hasNonNull("leftPlace")) {
relationship.setLeftPlace(relationshipRest.getLeftPlace()); newLeftPlace = relationshipRest.getLeftPlace();
} }
relationshipService.update(context, relationship); relationshipService.move(context, relationship, newLeftPlace, newRightPlace);
context.commit(); context.commit();
context.reloadEntity(relationship); context.reloadEntity(relationship);
@@ -272,10 +270,13 @@ public class RelationshipRestRepository extends DSpaceRestRepository<Relationshi
*/ */
private boolean isAllowedToModifyRelationship(Context context, Relationship relationship, Item leftItem, private boolean isAllowedToModifyRelationship(Context context, Relationship relationship, Item leftItem,
Item rightItem) throws SQLException { Item rightItem) throws SQLException {
return (authorizeService.authorizeActionBoolean(context, leftItem, Constants.WRITE) || return (
authorizeService.authorizeActionBoolean(context, rightItem, Constants.WRITE)) && // Authorized to write new Items (if specified)
(authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || (leftItem == null || authorizeService.authorizeActionBoolean(context, leftItem, Constants.WRITE))
authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE) || (rightItem == null || authorizeService.authorizeActionBoolean(context, rightItem, Constants.WRITE))
// Authorized to write old Items
&& (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE)
|| authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE))
); );
} }

View File

@@ -23,11 +23,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
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.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.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -41,6 +43,8 @@ import org.apache.solr.client.solrj.response.QueryResponse;
import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.PageMatcher;
import org.dspace.app.rest.matcher.RelationshipMatcher; import org.dspace.app.rest.matcher.RelationshipMatcher;
import org.dspace.app.rest.model.RelationshipRest; import org.dspace.app.rest.model.RelationshipRest;
import org.dspace.app.rest.model.patch.AddOperation;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.app.rest.test.AbstractEntityIntegrationTest;
import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.AuthorizeService;
import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CollectionBuilder;
@@ -151,6 +155,8 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest
.withTitle("Author2") .withTitle("Author2")
.withIssueDate("2016-02-13") .withIssueDate("2016-02-13")
.withAuthor("Smith, Maria") .withAuthor("Smith, Maria")
.withPersonIdentifierLastName("Smith")
.withPersonIdentifierFirstName("Maria")
.withEntityType("Person") .withEntityType("Person")
.build(); .build();
@@ -520,6 +526,80 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest
} }
} }
@Test
public void createMultipleRelationshipsAppendToEndTest() throws Exception {
context.turnOffAuthorisationSystem();
authorizeService.addPolicy(context, publication1, Constants.WRITE, user1);
authorizeService.addPolicy(context, author1, Constants.WRITE, user1);
authorizeService.addPolicy(context, author2, Constants.WRITE, user1);
context.setCurrentUser(user1);
context.restoreAuthSystemState();
AtomicReference<Integer> idRef = new AtomicReference<>();
AtomicReference<Integer> idRef2 = new AtomicReference<>();
try {
String token = getAuthToken(user1.getEmail(), password);
// Add a relationship @ leftPlace 2
getClient(token).perform(post("/api/core/relationships")
.param("relationshipType",
isAuthorOfPublicationRelationshipType.getID()
.toString())
.contentType(MediaType.parseMediaType
(org.springframework.data.rest.webmvc.RestMediaTypes
.TEXT_URI_LIST_VALUE))
.content(
"https://localhost:8080/server/api/core/items/" + publication1
.getID() + "\n" +
"https://localhost:8080/server/api/core/items/" + author1
.getID()))
.andExpect(status().isCreated())
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id")));
getClient().perform(get("/api/core/relationships/" + idRef))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(idRef.get())))
.andExpect(jsonPath("$.leftPlace", is(1)));
getClient(token).perform(post("/api/core/relationships")
.param("relationshipType",
isAuthorOfPublicationRelationshipType.getID()
.toString())
.contentType(MediaType.parseMediaType
(org.springframework.data.rest.webmvc.RestMediaTypes
.TEXT_URI_LIST_VALUE))
.content(
"https://localhost:8080/server/api/core/items/" + publication1
.getID() + "\n" +
"https://localhost:8080/server/api/core/items/" + author2
.getID()))
.andExpect(status().isCreated())
.andDo(result -> idRef2.set(read(result.getResponse().getContentAsString(), "$.id")));
getClient().perform(get("/api/core/relationships/" + idRef2))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(idRef2.get())))
.andExpect(jsonPath("$.leftPlace", is(2)));
// Check Item author order
getClient().perform(get("/api/core/items/" + publication1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.metadata", allOf(
matchMetadata("dc.contributor.author", "Testy, TEst", 0),
matchMetadata("dc.contributor.author", "Smith, Donald", 1),
matchMetadata("dc.contributor.author", "Smith, Maria", 2)
)));
} finally {
RelationshipBuilder.deleteRelationship(idRef.get());
if (idRef2.get() != null) {
RelationshipBuilder.deleteRelationship(idRef2.get());
}
}
}
@Test @Test
public void createRelationshipAndAddLeftWardValueAfterwards() throws Exception { public void createRelationshipAndAddLeftWardValueAfterwards() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -2516,6 +2596,104 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest
} }
@Test
public void putRelationshipWithJsonMoveInFrontOtherMetadata() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
Integer idRef = null;
Integer idRef2 = null;
try {
// Add a relationship
MvcResult mvcResult = getClient(token)
.perform(post("/api/core/relationships")
.param("relationshipType", isAuthorOfPublicationRelationshipType.getID().toString())
.contentType(MediaType.parseMediaType(
org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE))
.content(
"https://localhost:8080/server/api/core/items/" + publication1.getID() + "\n" +
"https://localhost:8080/server/api/core/items/" + author1.getID()))
.andExpect(status().isCreated())
.andReturn();
ObjectMapper mapper = new ObjectMapper();
String content = mvcResult.getResponse().getContentAsString();
Map<String, Object> map = mapper.readValue(content, Map.class);
String id = String.valueOf(map.get("id"));
idRef = Integer.parseInt(id);
// Add some more metadata
List<Operation> ops = new ArrayList<Operation>();
ops.add(new AddOperation("/metadata/dc.contributor.author/-", "Metadata, First"));
ops.add(new AddOperation("/metadata/dc.contributor.author/-", "Metadata, Second"));
getClient(token).perform(patch("/api/core/items/" + publication1.getID())
.content(getPatchContent(ops))
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON));
// Add another relationship
mvcResult = getClient(token)
.perform(post("/api/core/relationships")
.param("relationshipType", isAuthorOfPublicationRelationshipType.getID().toString())
.contentType(MediaType.parseMediaType(
org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE))
.content(
"https://localhost:8080/server/api/core/items/" + publication1.getID() + "\n" +
"https://localhost:8080/server/api/core/items/" + author2.getID()))
.andExpect(status().isCreated())
.andReturn();
content = mvcResult.getResponse().getContentAsString();
map = mapper.readValue(content, Map.class);
id = String.valueOf(map.get("id"));
idRef2 = Integer.parseInt(id);
// Check Item author order
getClient().perform(get("/api/core/items/" + publication1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.metadata", allOf(
matchMetadata("dc.contributor.author", "Testy, TEst", 0),
matchMetadata("dc.contributor.author", "Smith, Donald", 1), // first relationship
matchMetadata("dc.contributor.author", "Metadata, First", 2),
matchMetadata("dc.contributor.author", "Metadata, Second", 3),
matchMetadata("dc.contributor.author", "Smith, Maria", 4) // second relationship
)));
RelationshipRest relationshipRest = new RelationshipRest();
relationshipRest.setLeftPlace(0);
relationshipRest.setRightPlace(1);
relationshipRest.setLeftwardValue(null);
relationshipRest.setRightwardValue(null);
// Modify the place of the second relationship -> put it in front of all other metadata
getClient(token).perform(put("/api/core/relationships/" + idRef2)
.contentType(contentType)
.content(mapper.writeValueAsBytes(relationshipRest)))
.andExpect(status().isOk());
// Verify the place has changed to the new value
getClient(token).perform(get("/api/core/relationships/" + idRef2))
.andExpect(status().isOk())
.andExpect(jsonPath("$.leftPlace", is(0)))
.andExpect(jsonPath("$.rightPlace", is(1)));
// Verify the other metadata have moved back
getClient().perform(get("/api/core/items/" + publication1.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.metadata", allOf(
matchMetadata("dc.contributor.author", "Smith, Maria", 0), // second relationship
matchMetadata("dc.contributor.author", "Testy, TEst", 1),
matchMetadata("dc.contributor.author", "Smith, Donald", 2), // first relationship
matchMetadata("dc.contributor.author", "Metadata, First", 3),
matchMetadata("dc.contributor.author", "Metadata, Second", 4)
)));
} finally {
RelationshipBuilder.deleteRelationship(idRef);
RelationshipBuilder.deleteRelationship(idRef2);
}
}
@Test @Test
public void orgUnitAndOrgUnitRelationshipVirtualMetadataTest() throws Exception { public void orgUnitAndOrgUnitRelationshipVirtualMetadataTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();