mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-14 05:23:14 +00:00
Merge remote-tracking branch 'upstream/main' into w2p-76191_Remove-Traditional-Basic-Workflow-from-codebase-and-database
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,7 +19,7 @@ tags
|
||||
overlays/
|
||||
|
||||
## Ignore project files created by NetBeans
|
||||
nbproject/private/
|
||||
nbproject/
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
@@ -41,4 +41,4 @@ nb-configuration.xml
|
||||
.DS_Store
|
||||
|
||||
##Ignore JRebel project configuration
|
||||
rebel.xml
|
||||
rebel.xml
|
||||
|
@@ -145,6 +145,7 @@ public class DCInput {
|
||||
private String relationshipType = null;
|
||||
private String searchConfiguration = null;
|
||||
private String filter;
|
||||
private List<String> externalSources;
|
||||
|
||||
/**
|
||||
* The scope of the input sets, this restricts hidden metadata fields from
|
||||
@@ -226,6 +227,15 @@ public class DCInput {
|
||||
relationshipType = fieldMap.get("relationship-type");
|
||||
searchConfiguration = fieldMap.get("search-configuration");
|
||||
filter = fieldMap.get("filter");
|
||||
externalSources = new ArrayList<>();
|
||||
String externalSourcesDef = fieldMap.get("externalsources");
|
||||
if (StringUtils.isNotBlank(externalSourcesDef)) {
|
||||
String[] sources = StringUtils.split(externalSourcesDef, ",");
|
||||
for (String source: sources) {
|
||||
externalSources.add(StringUtils.trim(source));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,6 +532,10 @@ public class DCInput {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public List<String> getExternalSources() {
|
||||
return externalSources;
|
||||
}
|
||||
|
||||
public boolean isQualdropValue() {
|
||||
if ("qualdrop_value".equals(getInputType())) {
|
||||
return true;
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.content;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
@@ -19,12 +20,14 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.content.dao.RelationshipDAO;
|
||||
import org.dspace.content.service.EntityTypeService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.content.service.RelationshipService;
|
||||
import org.dspace.content.service.RelationshipTypeService;
|
||||
import org.dspace.content.virtual.VirtualMetadataPopulator;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class RelationshipServiceImpl implements RelationshipService {
|
||||
@@ -43,6 +46,12 @@ public class RelationshipServiceImpl implements RelationshipService {
|
||||
@Autowired(required = true)
|
||||
protected RelationshipTypeService relationshipTypeService;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
@Autowired
|
||||
private EntityTypeService entityTypeService;
|
||||
|
||||
@Autowired
|
||||
private RelationshipMetadataService relationshipMetadataService;
|
||||
@Autowired
|
||||
@@ -90,6 +99,7 @@ public class RelationshipServiceImpl implements RelationshipService {
|
||||
Relationship relationshipToReturn = relationshipDAO.create(context, relationship);
|
||||
updatePlaceInRelationship(context, relationshipToReturn);
|
||||
update(context, relationshipToReturn);
|
||||
updateItemsInRelationship(context, relationship);
|
||||
return relationshipToReturn;
|
||||
} else {
|
||||
throw new AuthorizeException(
|
||||
@@ -234,6 +244,10 @@ public class RelationshipServiceImpl implements RelationshipService {
|
||||
Integer maxCardinality,
|
||||
RelationshipType relationshipType,
|
||||
boolean isLeft) throws SQLException {
|
||||
if (maxCardinality == null) {
|
||||
//no need to check the relationships
|
||||
return true;
|
||||
}
|
||||
List<Relationship> rightRelationships = findByItemAndRelationshipType(context, itemToProcess, relationshipType,
|
||||
isLeft);
|
||||
if (maxCardinality != null && rightRelationships.size() >= maxCardinality) {
|
||||
@@ -243,7 +257,8 @@ public class RelationshipServiceImpl implements RelationshipService {
|
||||
}
|
||||
|
||||
private boolean verifyEntityTypes(Item itemToProcess, EntityType entityTypeToProcess) {
|
||||
List<MetadataValue> list = itemService.getMetadata(itemToProcess, "relationship", "type", null, Item.ANY);
|
||||
List<MetadataValue> list = itemService.getMetadata(itemToProcess, "relationship", "type",
|
||||
null, Item.ANY, false);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -337,6 +352,7 @@ public class RelationshipServiceImpl implements RelationshipService {
|
||||
authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) {
|
||||
relationshipDAO.delete(context, relationship);
|
||||
updatePlaceInRelationship(context, relationship);
|
||||
updateItemsInRelationship(context, relationship);
|
||||
} else {
|
||||
throw new AuthorizeException(
|
||||
"You do not have write rights on this relationship's items");
|
||||
@@ -347,6 +363,128 @@ public class RelationshipServiceImpl implements RelationshipService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to ensure discovery is updated for the 2 items
|
||||
* This method is used when creating, modifying or deleting a relationship
|
||||
* The virtual metadata of the 2 items may need to be updated, so they should be re-indexed
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param relationship The relationship which has been created, updated or deleted
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
private void updateItemsInRelationship(Context context, Relationship relationship) throws SQLException {
|
||||
// Since this call is performed after creating, updating or deleting the relationships, the permissions have
|
||||
// already been verified. The following updateItem calls can however call the
|
||||
// ItemService.update() functions which would fail if the user doesn't have permission on both items.
|
||||
// Since we allow this edits to happen under these circumstances, we need to turn off the
|
||||
// authorization system here so that this failure doesn't happen when the items need to be update
|
||||
context.turnOffAuthorisationSystem();
|
||||
try {
|
||||
// Set a limit on the total amount of items to update at once during a relationship change
|
||||
int max = configurationService.getIntProperty("relationship.update.relateditems.max", 20);
|
||||
// Set a limit on the total depth of relationships to traverse during a relationship change
|
||||
int maxDepth = configurationService.getIntProperty("relationship.update.relateditems.maxdepth", 5);
|
||||
// This is the list containing all items which will have changes to their virtual metadata
|
||||
List<Item> itemsToUpdate = new LinkedList<>();
|
||||
itemsToUpdate.add(relationship.getLeftItem());
|
||||
itemsToUpdate.add(relationship.getRightItem());
|
||||
|
||||
if (containsVirtualMetadata(relationship.getRelationshipType().getLeftwardType())) {
|
||||
findModifiedDiscoveryItemsForCurrentItem(context, relationship.getLeftItem(),
|
||||
itemsToUpdate, max, 0, maxDepth);
|
||||
}
|
||||
if (containsVirtualMetadata(relationship.getRelationshipType().getRightwardType())) {
|
||||
findModifiedDiscoveryItemsForCurrentItem(context, relationship.getRightItem(),
|
||||
itemsToUpdate, max, 0, maxDepth);
|
||||
}
|
||||
|
||||
for (Item item : itemsToUpdate) {
|
||||
updateItem(context, item);
|
||||
}
|
||||
} catch (AuthorizeException e) {
|
||||
log.error("Authorization Exception while authorization has been disabled", e);
|
||||
} finally {
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for items whose metadata should be updated in discovery and adds them to itemsToUpdate
|
||||
* It starts from the given item, excludes items already in itemsToUpdate (they're already handled),
|
||||
* and can be limited in amount of items or depth to update
|
||||
*/
|
||||
private void findModifiedDiscoveryItemsForCurrentItem(Context context, Item item, List<Item> itemsToUpdate,
|
||||
int max, int currentDepth, int maxDepth)
|
||||
throws SQLException {
|
||||
if (itemsToUpdate.size() >= max) {
|
||||
log.debug("skipping findModifiedDiscoveryItemsForCurrentItem for item "
|
||||
+ item.getID() + " due to " + itemsToUpdate.size() + " items to be updated");
|
||||
return;
|
||||
}
|
||||
if (currentDepth == maxDepth) {
|
||||
log.debug("skipping findModifiedDiscoveryItemsForCurrentItem for item "
|
||||
+ item.getID() + " due to " + currentDepth + " depth");
|
||||
return;
|
||||
}
|
||||
String entityTypeStringFromMetadata = relationshipMetadataService.getEntityTypeStringFromMetadata(item);
|
||||
EntityType actualEntityType = entityTypeService.findByEntityType(context, entityTypeStringFromMetadata);
|
||||
// Get all types of relations for the current item
|
||||
List<RelationshipType> relationshipTypes = relationshipTypeService.findByEntityType(context, actualEntityType);
|
||||
for (RelationshipType relationshipType : relationshipTypes) {
|
||||
//are we searching for items where the current item is on the left
|
||||
boolean isLeft = relationshipType.getLeftType().equals(actualEntityType);
|
||||
|
||||
// Verify whether there's virtual metadata configured for this type of relation
|
||||
// If it's not present, we don't need to update the virtual metadata in discovery
|
||||
String typeToSearchInVirtualMetadata;
|
||||
if (isLeft) {
|
||||
typeToSearchInVirtualMetadata = relationshipType.getRightwardType();
|
||||
} else {
|
||||
typeToSearchInVirtualMetadata = relationshipType.getLeftwardType();
|
||||
}
|
||||
if (containsVirtualMetadata(typeToSearchInVirtualMetadata)) {
|
||||
// we have a relationship type where the items attached to the current item will inherit
|
||||
// virtual metadata from the current item
|
||||
// retrieving the actual relationships so the related items can be updated
|
||||
List<Relationship> list = findByItemAndRelationshipType(context, item, relationshipType, isLeft);
|
||||
for (Relationship foundRelationship : list) {
|
||||
Item nextItem;
|
||||
if (isLeft) {
|
||||
// current item on the left, next item is on the right
|
||||
nextItem = foundRelationship.getRightItem();
|
||||
} else {
|
||||
nextItem = foundRelationship.getLeftItem();
|
||||
}
|
||||
|
||||
// verify it hasn't been processed yet
|
||||
if (!itemsToUpdate.contains(nextItem)) {
|
||||
itemsToUpdate.add(nextItem);
|
||||
// continue the process for the next item, it may also inherit item from the current item
|
||||
findModifiedDiscoveryItemsForCurrentItem(context, nextItem,
|
||||
itemsToUpdate, max, currentDepth + 1, maxDepth);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug("skipping " + relationshipType.getID()
|
||||
+ " in findModifiedDiscoveryItemsForCurrentItem for item "
|
||||
+ item.getID() + " because no relevant virtual metadata was found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies whether there is virtual metadata generated for the given relationship
|
||||
* If no such virtual metadata exists, there's no need to update the items in discovery
|
||||
* @param typeToSearchInVirtualMetadata a leftWardType or rightWardType of a relationship type
|
||||
* This can be e.g. isAuthorOfPublication
|
||||
* @return true if there is virtual metadata for this relationship
|
||||
*/
|
||||
private boolean containsVirtualMetadata(String typeToSearchInVirtualMetadata) {
|
||||
return virtualMetadataPopulator.getMap().containsKey(typeToSearchInVirtualMetadata)
|
||||
&& virtualMetadataPopulator.getMap().get(typeToSearchInVirtualMetadata).size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts virtual metadata from RelationshipMetadataValue objects to actual item metadata.
|
||||
*
|
||||
|
@@ -18,9 +18,7 @@ import org.dspace.content.Item;
|
||||
import org.dspace.content.Relationship;
|
||||
import org.dspace.content.RelationshipType;
|
||||
import org.dspace.content.service.EntityService;
|
||||
import org.dspace.content.service.EntityTypeService;
|
||||
import org.dspace.content.service.RelationshipService;
|
||||
import org.dspace.content.service.RelationshipTypeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -36,15 +34,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
*/
|
||||
public class Related implements VirtualMetadataConfiguration {
|
||||
|
||||
@Autowired
|
||||
private RelationshipTypeService relationshipTypeService;
|
||||
|
||||
@Autowired
|
||||
private RelationshipService relationshipService;
|
||||
|
||||
@Autowired
|
||||
private EntityTypeService entityTypeService;
|
||||
|
||||
@Autowired
|
||||
private EntityService entityService;
|
||||
|
||||
@@ -172,12 +164,12 @@ public class Related implements VirtualMetadataConfiguration {
|
||||
}
|
||||
|
||||
for (Relationship relationship : relationships) {
|
||||
if (relationship.getRelationshipType().getLeftType() == entityType) {
|
||||
if (relationship.getRelationshipType().getLeftType().equals(entityType)) {
|
||||
if (place == null || relationship.getLeftPlace() == place) {
|
||||
Item otherItem = relationship.getRightItem();
|
||||
return virtualMetadataConfiguration.getValues(context, otherItem);
|
||||
}
|
||||
} else if (relationship.getRelationshipType().getRightType() == entityType) {
|
||||
} else if (relationship.getRelationshipType().getRightType().equals(entityType)) {
|
||||
if (place == null || relationship.getRightPlace() == place) {
|
||||
Item otherItem = relationship.getLeftItem();
|
||||
return virtualMetadataConfiguration.getValues(context, otherItem);
|
||||
|
@@ -130,6 +130,18 @@ public class IndexEventConsumer implements Consumer {
|
||||
}
|
||||
} else {
|
||||
log.debug("consume() adding event to update queue: " + event.toString());
|
||||
if (event.getSubjectType() == Constants.ITEM) {
|
||||
// if it is an item we cannot know about its previous state, so it could be a
|
||||
// workspaceitem that has been deposited right now or an approved/reject
|
||||
// workflowitem.
|
||||
// As the workflow is not necessary enabled it can happen than a workspaceitem
|
||||
// became directly an item without giving us the chance to retrieve a
|
||||
// workflowitem... so we need to force the unindex of all the related data
|
||||
// before to index it again to be sure to don't leave any zombie in solr
|
||||
String detail =
|
||||
Constants.typeText[event.getSubjectType()] + "-" + event.getSubjectID().toString();
|
||||
uniqueIdsToDelete.add(detail);
|
||||
}
|
||||
objectsToUpdate.addAll(indexObjectServiceFactory.getIndexableObjects(ctx, subject));
|
||||
}
|
||||
break;
|
||||
@@ -151,7 +163,7 @@ public class IndexEventConsumer implements Consumer {
|
||||
if (event.getSubjectType() == -1 || event.getSubjectID() == null) {
|
||||
log.warn("got null subject type and/or ID on DELETE event, skipping it.");
|
||||
} else {
|
||||
String detail = event.getSubjectType() + "-" + event.getSubjectID().toString();
|
||||
String detail = Constants.typeText[event.getSubjectType()] + "-" + event.getSubjectID().toString();
|
||||
log.debug("consume() adding event to delete queue: " + event.toString());
|
||||
uniqueIdsToDelete.add(detail);
|
||||
}
|
||||
@@ -175,6 +187,16 @@ public class IndexEventConsumer implements Consumer {
|
||||
public void end(Context ctx) throws Exception {
|
||||
|
||||
try {
|
||||
for (String uid : uniqueIdsToDelete) {
|
||||
try {
|
||||
indexer.unIndexContent(ctx, uid, false);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("UN-Indexed Item, handle=" + uid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed while UN-indexing object: " + uid, e);
|
||||
}
|
||||
}
|
||||
// update the changed Items not deleted because they were on create list
|
||||
for (IndexableObject iu : objectsToUpdate) {
|
||||
/* we let all types through here and
|
||||
@@ -183,7 +205,7 @@ public class IndexEventConsumer implements Consumer {
|
||||
*/
|
||||
iu.setIndexedObject(ctx.reloadEntity(iu.getIndexedObject()));
|
||||
String uniqueIndexID = iu.getUniqueIndexID();
|
||||
if (uniqueIndexID != null && !uniqueIdsToDelete.contains(uniqueIndexID)) {
|
||||
if (uniqueIndexID != null) {
|
||||
try {
|
||||
indexer.indexContent(ctx, iu, true, false);
|
||||
log.debug("Indexed "
|
||||
@@ -195,17 +217,6 @@ public class IndexEventConsumer implements Consumer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String uid : uniqueIdsToDelete) {
|
||||
try {
|
||||
indexer.unIndexContent(ctx, uid, false);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("UN-Indexed Item, handle=" + uid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed while UN-indexing object: " + uid, e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!objectsToUpdate.isEmpty() || !uniqueIdsToDelete.isEmpty()) {
|
||||
|
||||
|
@@ -44,7 +44,7 @@ public class DiscoveryConfigurationService {
|
||||
public DiscoveryConfiguration getDiscoveryConfiguration(IndexableObject dso) {
|
||||
String name;
|
||||
if (dso == null) {
|
||||
name = "site";
|
||||
name = "default";
|
||||
} else if (dso instanceof IndexableDSpaceObject) {
|
||||
name = ((IndexableDSpaceObject) dso).getIndexedObject().getHandle();
|
||||
} else {
|
||||
|
@@ -68,7 +68,7 @@ public abstract class IndexObjectFactoryFactory {
|
||||
*/
|
||||
public IndexFactory getIndexFactoryByType(String indexableFactoryType) {
|
||||
for (IndexFactory indexableObjectFactory : getIndexFactories()) {
|
||||
if (indexableObjectFactory.getType().equals(indexableFactoryType)) {
|
||||
if (StringUtils.equalsIgnoreCase(indexableObjectFactory.getType(), indexableFactoryType)) {
|
||||
return indexableObjectFactory;
|
||||
}
|
||||
}
|
||||
|
@@ -274,9 +274,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
for (Group group: workFlowGroups) {
|
||||
List<EPerson> ePeople = groupService.allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to delete user " + ePerson.getID() + " because it the only member of the workflow group"
|
||||
+ group.getID() + ". Delete the tasks and group first if you want to remove this user.");
|
||||
throw new EmptyWorkflowGroupException(ePerson.getID(), group.getID());
|
||||
}
|
||||
}
|
||||
// check for presence of eperson in tables that
|
||||
|
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.eperson;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* <p>This exception class is used to distinguish the following condition:
|
||||
* EPerson cannot be deleted because that would lead to one (or more)
|
||||
* workflow groups being empty.</p>
|
||||
*
|
||||
* <p>The message of this exception can be disclosed in the REST response to
|
||||
* provide more granular feedback to the user.</p>
|
||||
*
|
||||
* @author Bruno Roemers (bruno.roemers at atmire.com)
|
||||
*/
|
||||
public class EmptyWorkflowGroupException extends IllegalStateException {
|
||||
|
||||
public static final String msgFmt = "Refused to delete user %s because it is the only member of the " +
|
||||
"workflow group %s. Delete the tasks and group first if you want to remove this user.";
|
||||
|
||||
private final UUID ePersonId;
|
||||
private final UUID groupId;
|
||||
|
||||
public EmptyWorkflowGroupException(UUID ePersonId, UUID groupId) {
|
||||
super(String.format(msgFmt, ePersonId, groupId));
|
||||
this.ePersonId = ePersonId;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public UUID getEPersonId() {
|
||||
return ePersonId;
|
||||
}
|
||||
|
||||
public UUID getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
}
|
@@ -1935,3 +1935,9 @@ jsp.dspace-admin.batchimport.itemsimported = Items imported
|
||||
jsp.dspace-admin.batchimport.downloadmapfile = Download mapfile
|
||||
jsp.dspace-admin.batchimport.deleteitems = Delete uploaded items & remove import
|
||||
jsp.dspace-admin.batchimport.resume = Resume upload
|
||||
|
||||
# User exposed error messages
|
||||
org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException.message = Refused to delete user {0} because it is the only member of the \
|
||||
workflow group {1}. Delete the tasks and group first if you want to remove this user.
|
||||
org.dspace.app.rest.exception.EPersonNameNotProvidedException.message = The eperson.firstname and eperson.lastname values need to be filled in
|
||||
org.dspace.app.rest.exception.GroupNameNotProvidedException.message = Cannot create group, no group name is provided
|
||||
|
@@ -84,6 +84,10 @@ loglevel.dspace = INFO
|
||||
###########################################
|
||||
# CUSTOM UNIT / INTEGRATION TEST SETTINGS #
|
||||
###########################################
|
||||
# custom dispatcher to be used by dspace-api IT that doesn't need SOLR
|
||||
event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher
|
||||
event.dispatcher.exclude-discovery.consumers = versioning, eperson
|
||||
|
||||
# Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest)
|
||||
# (This overrides default, commented out settings in dspace.cfg)
|
||||
plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \
|
||||
|
@@ -63,6 +63,7 @@
|
||||
<dc-qualifier>author</dc-qualifier>
|
||||
<input-type>name</input-type>
|
||||
</linked-metadata-field>
|
||||
<externalsources>orcid,my_staff_db</externalsources>
|
||||
<required></required>
|
||||
</relation-field>
|
||||
</row>
|
||||
@@ -242,6 +243,7 @@ it, please enter the types and the actual numbers or codes.</hint>
|
||||
<filter>creativework.publisher:somepublishername</filter>
|
||||
<label>Journal</label>
|
||||
<hint>Select the journal related to this volume.</hint>
|
||||
<externalsources></externalsources>
|
||||
</relation-field>
|
||||
</row>
|
||||
<row>
|
||||
|
@@ -11,7 +11,6 @@ import static org.junit.Assert.fail;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.authorize.factory.AuthorizeServiceFactory;
|
||||
@@ -21,8 +20,6 @@ import org.dspace.core.I18nUtil;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.storage.rdbms.DatabaseUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -123,8 +120,11 @@ public class AbstractUnitTest extends AbstractDSpaceTest {
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
// Ensure all tests run with Solr indexing disabled
|
||||
disableSolrIndexing();
|
||||
|
||||
// we turn this off because
|
||||
// Solr is NOT used in the OLD dspace-api test framework. Instead, Solr/Discovery indexing is
|
||||
// exercised in the new Integration Tests (which use an embedded Solr) and extends
|
||||
// org.dspace.AbstractIntegrationTestWithDatabase
|
||||
context.setDispatcher("exclude-discovery");
|
||||
} catch (AuthorizeException ex) {
|
||||
log.error("Error creating initial eperson or default groups", ex);
|
||||
fail("Error creating initial eperson or default groups in AbstractUnitTest init()");
|
||||
@@ -167,22 +167,4 @@ public class AbstractUnitTest extends AbstractDSpaceTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method which ensures Solr indexing is DISABLED in all Tests. We turn this off because
|
||||
* Solr is NOT used in the dspace-api test framework. Instead, Solr/Discovery indexing is
|
||||
* exercised in the dspace-server Integration Tests (which use an embedded Solr).
|
||||
*/
|
||||
protected static void disableSolrIndexing() {
|
||||
// Get our currently configured list of event consumers
|
||||
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
String[] consumers = configurationService.getArrayProperty("event.dispatcher.default.consumers");
|
||||
|
||||
// Remove "discovery" from the configured consumers (if it exists).
|
||||
// This turns off Discovery/Solr indexing after any object changes.
|
||||
if (ArrayUtils.contains(consumers, "discovery")) {
|
||||
consumers = ArrayUtils.removeElement(consumers, "discovery");
|
||||
configurationService.setProperty("event.dispatcher.default.consumers", consumers);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ import org.dspace.content.WorkspaceItem;
|
||||
import org.dspace.content.service.WorkspaceItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
|
||||
|
||||
/**
|
||||
* Builder to construct WorkspaceItem objects
|
||||
@@ -110,6 +111,13 @@ public class WorkspaceItemBuilder extends AbstractBuilder<WorkspaceItem, Workspa
|
||||
workspaceItem = c.reloadEntity(workspaceItem);
|
||||
if (workspaceItem != null) {
|
||||
delete(c, workspaceItem);
|
||||
} else {
|
||||
item = c.reloadEntity(item);
|
||||
// check if the wsi has been pushed to the workflow
|
||||
XmlWorkflowItem wfi = workflowItemService.findByItem(c, item);
|
||||
if (wfi != null) {
|
||||
workflowItemService.delete(c, wfi);
|
||||
}
|
||||
}
|
||||
item = c.reloadEntity(item);
|
||||
if (item != null) {
|
||||
|
@@ -17,10 +17,13 @@ import java.util.List;
|
||||
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.content.dao.RelationshipDAO;
|
||||
import org.dspace.content.service.EntityTypeService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.content.service.RelationshipTypeService;
|
||||
import org.dspace.content.virtual.VirtualMetadataPopulator;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -56,6 +59,18 @@ public class RelationshipServiceImplTest {
|
||||
@Mock
|
||||
private VirtualMetadataPopulator virtualMetadataPopulator;
|
||||
|
||||
@Mock
|
||||
private RelationshipTypeService relationshipTypeService;
|
||||
|
||||
@Mock
|
||||
private RelationshipMetadataService relationshipMetadataService;
|
||||
|
||||
@Mock
|
||||
private EntityTypeService entityTypeService;
|
||||
|
||||
@Mock
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
relationshipsList = new ArrayList<>();
|
||||
@@ -212,8 +227,8 @@ public class RelationshipServiceImplTest {
|
||||
when(metsList.get(0).getValue()).thenReturn("Entitylabel");
|
||||
when(relationshipService
|
||||
.findByItemAndRelationshipType(context, leftItem, testRel, true)).thenReturn(leftTypelist);
|
||||
when(itemService.getMetadata(leftItem, "relationship", "type", null, Item.ANY)).thenReturn(metsList);
|
||||
when(itemService.getMetadata(rightItem, "relationship", "type", null, Item.ANY)).thenReturn(metsList);
|
||||
when(itemService.getMetadata(leftItem, "relationship", "type", null, Item.ANY, false)).thenReturn(metsList);
|
||||
when(itemService.getMetadata(rightItem, "relationship", "type", null, Item.ANY, false)).thenReturn(metsList);
|
||||
when(relationshipDAO.create(any(), any())).thenReturn(relationship);
|
||||
|
||||
// The reported Relationship should match our defined relationship
|
||||
@@ -290,8 +305,8 @@ public class RelationshipServiceImplTest {
|
||||
relationship = getRelationship(leftItem, rightItem, testRel, 0,0);
|
||||
|
||||
// Mock the state of objects utilized in update() to meet the success criteria of the invocation
|
||||
when(itemService.getMetadata(leftItem, "relationship", "type", null, Item.ANY)).thenReturn(metsList);
|
||||
when(itemService.getMetadata(rightItem, "relationship", "type", null, Item.ANY)).thenReturn(metsList);
|
||||
when(itemService.getMetadata(leftItem, "relationship", "type", null, Item.ANY, false)).thenReturn(metsList);
|
||||
when(itemService.getMetadata(rightItem, "relationship", "type", null, Item.ANY, false)).thenReturn(metsList);
|
||||
when(authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(),
|
||||
Constants.WRITE)).thenReturn(true);
|
||||
|
||||
|
@@ -20,7 +20,7 @@ import org.dspace.scripts.factory.ScriptServiceFactory;
|
||||
import org.dspace.scripts.service.ScriptService;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CurationTest extends AbstractIntegrationTestWithDatabase {
|
||||
public class CurationIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void curationWithoutEPersonParameterTest() throws Exception {
|
@@ -35,11 +35,11 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author mhwood
|
||||
*/
|
||||
public class ITCurator
|
||||
public class CuratorReportTest
|
||||
extends AbstractUnitTest {
|
||||
Logger LOG = LoggerFactory.getLogger(ITCurator.class);
|
||||
Logger LOG = LoggerFactory.getLogger(CuratorReportTest.class);
|
||||
|
||||
public ITCurator() {
|
||||
public CuratorReportTest() {
|
||||
}
|
||||
|
||||
@BeforeClass
|
331
dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java
Normal file
331
dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java
Normal file
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* 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.discovery;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.dspace.AbstractIntegrationTestWithDatabase;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.builder.ClaimedTaskBuilder;
|
||||
import org.dspace.builder.CollectionBuilder;
|
||||
import org.dspace.builder.CommunityBuilder;
|
||||
import org.dspace.builder.PoolTaskBuilder;
|
||||
import org.dspace.builder.WorkflowItemBuilder;
|
||||
import org.dspace.builder.WorkspaceItemBuilder;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.WorkspaceItem;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.content.service.WorkspaceItemService;
|
||||
import org.dspace.discovery.indexobject.IndexableClaimedTask;
|
||||
import org.dspace.discovery.indexobject.IndexableItem;
|
||||
import org.dspace.discovery.indexobject.IndexablePoolTask;
|
||||
import org.dspace.discovery.indexobject.IndexableWorkflowItem;
|
||||
import org.dspace.discovery.indexobject.IndexableWorkspaceItem;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.workflow.WorkflowException;
|
||||
import org.dspace.xmlworkflow.WorkflowConfigurationException;
|
||||
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
|
||||
import org.dspace.xmlworkflow.service.WorkflowRequirementsService;
|
||||
import org.dspace.xmlworkflow.service.XmlWorkflowService;
|
||||
import org.dspace.xmlworkflow.state.Step;
|
||||
import org.dspace.xmlworkflow.state.Workflow;
|
||||
import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig;
|
||||
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
|
||||
import org.dspace.xmlworkflow.storedcomponents.PoolTask;
|
||||
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
|
||||
import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
/**
|
||||
* This class will aim to test Discovery related use cases
|
||||
*/
|
||||
public class DiscoveryIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
|
||||
protected SearchService searchService = SearchUtils.getSearchService();
|
||||
|
||||
XmlWorkflowService workflowService = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService();
|
||||
|
||||
WorkflowRequirementsService workflowRequirementsService = XmlWorkflowServiceFactory.getInstance().
|
||||
getWorkflowRequirementsService();
|
||||
|
||||
ClaimedTaskService claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService();
|
||||
|
||||
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
|
||||
IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServiceByName(IndexingService.class.getName(),
|
||||
IndexingService.class);
|
||||
|
||||
|
||||
@Test
|
||||
public void solrRecordsAfterDepositOrDeletionOfWorkspaceItemTest() throws Exception {
|
||||
context.turnOffAuthorisationSystem();
|
||||
Community community = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Collection col = CollectionBuilder.createCollection(context, community)
|
||||
.withName("Collection without workflow")
|
||||
.build();
|
||||
Collection colWithWorkflow = CollectionBuilder.createCollection(context, community)
|
||||
.withName("Collection WITH workflow")
|
||||
.withWorkflowGroup(1, admin)
|
||||
.build();
|
||||
WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col)
|
||||
.withTitle("No workflow")
|
||||
.withAbstract("headache")
|
||||
.build();
|
||||
WorkspaceItem anotherWorkspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col)
|
||||
.withTitle("Another WS Item in No workflow collection")
|
||||
.withAbstract("headache")
|
||||
.build();
|
||||
WorkspaceItem workspaceItemInWfCollection = WorkspaceItemBuilder.createWorkspaceItem(context, colWithWorkflow)
|
||||
.withTitle("WS Item in workflow collection")
|
||||
.withAbstract("headache")
|
||||
.build();
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
// we start with 3 ws items
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 3);
|
||||
// simulate the deposit
|
||||
deposit(workspaceItem);
|
||||
// now we should have 1 archived item and 2 ws items, no wf items or tasks
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 0);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 0);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 0);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 2);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
|
||||
// simulate the deposit of the ws item in the workflow collection
|
||||
deposit(workspaceItemInWfCollection);
|
||||
// now we should have 1 wf, 1 pool task, 1 ws item and 1 item
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 1);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 1);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 0);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 1);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
|
||||
// simulate the delete of last workspace item
|
||||
deleteSubmission(anotherWorkspaceItem);
|
||||
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 1);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 1);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 0);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 0);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void solrRecordsAfterDealingWithWorkflowTest() throws Exception {
|
||||
context.turnOffAuthorisationSystem();
|
||||
Community community = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Collection collection = CollectionBuilder.createCollection(context, community)
|
||||
.withWorkflowGroup(1, admin)
|
||||
.build();
|
||||
Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(collection);
|
||||
|
||||
ClaimedTask taskToApprove = ClaimedTaskBuilder.createClaimedTask(context, collection, admin)
|
||||
.withTitle("Test workflow item to approve")
|
||||
.withIssueDate("2019-03-06")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
ClaimedTask taskToReject = ClaimedTaskBuilder.createClaimedTask(context, collection, admin)
|
||||
.withTitle("Test workflow item to reject")
|
||||
.withIssueDate("2019-03-06")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
PoolTask taskToClaim = PoolTaskBuilder.createPoolTask(context, collection, admin)
|
||||
.withTitle("Test pool task to claim")
|
||||
.withIssueDate("2019-03-06")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
ClaimedTask taskToUnclaim = ClaimedTaskBuilder.createClaimedTask(context, collection, admin)
|
||||
.withTitle("Test claimed task to unclaim")
|
||||
.withIssueDate("2019-03-06")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
XmlWorkflowItem wfiToDelete = WorkflowItemBuilder.createWorkflowItem(context, collection)
|
||||
.withTitle("Test workflow item to return")
|
||||
.withIssueDate("2019-03-06")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
// we start with 5 workflow items, 3 claimed tasks, 2 pool task
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 5);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 3);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 2);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 0);
|
||||
assertSearchQuery(IndexableItem.TYPE, 0);
|
||||
|
||||
// claim
|
||||
claim(workflow, taskToClaim, admin);
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 5);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 4);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 1);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 0);
|
||||
assertSearchQuery(IndexableItem.TYPE, 0);
|
||||
|
||||
// unclaim
|
||||
returnClaimedTask(taskToUnclaim);
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 5);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 3);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 2);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 0);
|
||||
assertSearchQuery(IndexableItem.TYPE, 0);
|
||||
|
||||
// reject
|
||||
MockHttpServletRequest httpRejectRequest = new MockHttpServletRequest();
|
||||
httpRejectRequest.setParameter("submit_reject", "submit_reject");
|
||||
httpRejectRequest.setParameter("reason", "test");
|
||||
executeWorkflowAction(httpRejectRequest, workflow, taskToReject);
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 4);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 2);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 2);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 1);
|
||||
assertSearchQuery(IndexableItem.TYPE, 0);
|
||||
|
||||
// approve
|
||||
MockHttpServletRequest httpApproveRequest = new MockHttpServletRequest();
|
||||
httpApproveRequest.setParameter("submit_approve", "submit_approve");
|
||||
executeWorkflowAction(httpApproveRequest, workflow, taskToApprove);
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 3);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 1);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 2);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 1);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
|
||||
// abort pool task
|
||||
// as we have already unclaimed this task it is a pool task now
|
||||
abort(taskToUnclaim.getWorkflowItem());
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 2);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 1);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 1);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 2);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
|
||||
// abort claimed task
|
||||
// as we have already claimed this task it is a claimed task now
|
||||
abort(taskToClaim.getWorkflowItem());
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 1);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 0);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 1);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 3);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
|
||||
// delete pool task / workflow item
|
||||
deleteWorkflowItem(wfiToDelete);
|
||||
assertSearchQuery(IndexableWorkflowItem.TYPE, 0);
|
||||
assertSearchQuery(IndexableClaimedTask.TYPE, 0);
|
||||
assertSearchQuery(IndexablePoolTask.TYPE, 0);
|
||||
assertSearchQuery(IndexableWorkspaceItem.TYPE, 3);
|
||||
assertSearchQuery(IndexableItem.TYPE, 1);
|
||||
}
|
||||
|
||||
private void assertSearchQuery(String resourceType, int size) throws SearchServiceException {
|
||||
DiscoverQuery discoverQuery = new DiscoverQuery();
|
||||
discoverQuery.setQuery("*:*");
|
||||
discoverQuery.addFilterQueries("search.resourcetype:" + resourceType);
|
||||
DiscoverResult discoverResult = searchService.search(context, discoverQuery);
|
||||
List<IndexableObject> indexableObjects = discoverResult.getIndexableObjects();
|
||||
assertEquals(size, indexableObjects.size());
|
||||
assertEquals(size, discoverResult.getTotalSearchResults());
|
||||
}
|
||||
|
||||
|
||||
private void deposit(WorkspaceItem workspaceItem)
|
||||
throws SQLException, AuthorizeException, IOException, WorkflowException, SearchServiceException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
workspaceItem = context.reloadEntity(workspaceItem);
|
||||
XmlWorkflowItem workflowItem = workflowService.startWithoutNotify(context, workspaceItem);
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
private void deleteSubmission(WorkspaceItem anotherWorkspaceItem)
|
||||
throws SQLException, AuthorizeException, IOException, SearchServiceException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
anotherWorkspaceItem = context.reloadEntity(anotherWorkspaceItem);
|
||||
workspaceItemService.deleteAll(context, anotherWorkspaceItem);
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
private void deleteWorkflowItem(XmlWorkflowItem workflowItem)
|
||||
throws SQLException, AuthorizeException, IOException, SearchServiceException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
workflowItem = context.reloadEntity(workflowItem);
|
||||
workflowService.deleteWorkflowByWorkflowItem(context, workflowItem, admin);
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
|
||||
private void returnClaimedTask(ClaimedTask taskToUnclaim) throws SQLException, IOException,
|
||||
WorkflowConfigurationException, AuthorizeException, SearchServiceException {
|
||||
final EPerson previousUser = context.getCurrentUser();
|
||||
taskToUnclaim = context.reloadEntity(taskToUnclaim);
|
||||
context.setCurrentUser(taskToUnclaim.getOwner());
|
||||
XmlWorkflowItem workflowItem = taskToUnclaim.getWorkflowItem();
|
||||
workflowService.deleteClaimedTask(context, workflowItem, taskToUnclaim);
|
||||
workflowRequirementsService.removeClaimedUser(context, workflowItem, taskToUnclaim.getOwner(),
|
||||
taskToUnclaim.getStepID());
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.setCurrentUser(previousUser);
|
||||
}
|
||||
|
||||
private void claim(Workflow workflow, PoolTask task, EPerson user)
|
||||
throws Exception {
|
||||
final EPerson previousUser = context.getCurrentUser();
|
||||
task = context.reloadEntity(task);
|
||||
context.setCurrentUser(user);
|
||||
Step step = workflow.getStep(task.getStepID());
|
||||
WorkflowActionConfig currentActionConfig = step.getActionConfig(task.getActionID());
|
||||
workflowService.doState(context, user, null, task.getWorkflowItem().getID(), workflow, currentActionConfig);
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.setCurrentUser(previousUser);
|
||||
}
|
||||
|
||||
private void executeWorkflowAction(HttpServletRequest httpServletRequest, Workflow workflow, ClaimedTask task)
|
||||
throws Exception {
|
||||
final EPerson previousUser = context.getCurrentUser();
|
||||
task = context.reloadEntity(task);
|
||||
context.setCurrentUser(task.getOwner());
|
||||
workflowService.doState(context, task.getOwner(), httpServletRequest, task.getWorkflowItem().getID(), workflow,
|
||||
workflow.getStep(task.getStepID()).getActionConfig(task.getActionID()));
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.setCurrentUser(previousUser);
|
||||
}
|
||||
|
||||
private void abort(XmlWorkflowItem workflowItem)
|
||||
throws SQLException, AuthorizeException, IOException, SearchServiceException {
|
||||
final EPerson previousUser = context.getCurrentUser();
|
||||
workflowItem = context.reloadEntity(workflowItem);
|
||||
context.setCurrentUser(admin);
|
||||
workflowService.abort(context, workflowItem, admin);
|
||||
context.commit();
|
||||
indexer.commit();
|
||||
context.setCurrentUser(previousUser);
|
||||
}
|
||||
}
|
@@ -21,9 +21,9 @@ import org.junit.Test;
|
||||
*
|
||||
* @author mwood
|
||||
*/
|
||||
public class GroupServiceImplIT
|
||||
public class GroupServiceImplTest
|
||||
extends AbstractUnitTest {
|
||||
public GroupServiceImplIT() {
|
||||
public GroupServiceImplTest() {
|
||||
super();
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ import org.junit.Test;
|
||||
/**
|
||||
* Test class for the BitstreamEventProcessor
|
||||
*/
|
||||
public class BitstreamEventProcessorTest extends AbstractIntegrationTestWithDatabase {
|
||||
public class BitstreamEventProcessorIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
|
@@ -41,7 +41,7 @@ import org.mockito.Mock;
|
||||
/**
|
||||
* Test for the ExportEventProcessor class
|
||||
*/
|
||||
public class ExportEventProcessorTest extends AbstractIntegrationTestWithDatabase {
|
||||
public class ExportEventProcessorIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
@Mock
|
||||
private final HttpServletRequest request = mock(HttpServletRequest.class);
|
@@ -29,7 +29,7 @@ import org.junit.Test;
|
||||
/**
|
||||
* Test class for the ItemEventProcessor
|
||||
*/
|
||||
public class ItemEventProcessorTest extends AbstractIntegrationTestWithDatabase {
|
||||
public class ItemEventProcessorIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
|
||||
private final ConfigurationService configurationService
|
@@ -11,6 +11,7 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.rest.model.ScopeEnum;
|
||||
import org.dspace.app.rest.model.SubmissionFormFieldRest;
|
||||
@@ -174,6 +175,9 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
|
||||
selectableRelationship.setFilter(dcinput.getFilter());
|
||||
selectableRelationship.setSearchConfiguration(dcinput.getSearchConfiguration());
|
||||
selectableRelationship.setNameVariants(String.valueOf(dcinput.areNameVariantsAllowed()));
|
||||
if (CollectionUtils.isNotEmpty(dcinput.getExternalSources())) {
|
||||
selectableRelationship.setExternalSources(dcinput.getExternalSources());
|
||||
}
|
||||
return selectableRelationship;
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,9 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.app.rest.security.RestAuthenticationService;
|
||||
import org.dspace.app.rest.utils.ContextUtil;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.Context;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -98,6 +100,24 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
|
||||
HttpStatus.UNPROCESSABLE_ENTITY.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user-friendly error messages to the response body for selected errors.
|
||||
* Since the error messages will be exposed to the API user, the exception classes are expected to implement
|
||||
* {@link TranslatableException} such that the error messages can be translated.
|
||||
*/
|
||||
@ExceptionHandler({
|
||||
RESTEmptyWorkflowGroupException.class,
|
||||
EPersonNameNotProvidedException.class,
|
||||
GroupNameNotProvidedException.class,
|
||||
})
|
||||
protected void handleCustomUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response,
|
||||
TranslatableException ex) throws IOException {
|
||||
Context context = ContextUtil.obtainContext(request);
|
||||
sendErrorResponse(
|
||||
request, response, null, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value()
|
||||
);
|
||||
}
|
||||
|
||||
@ExceptionHandler(QueryMethodParameterConversionException.class)
|
||||
protected void ParameterConversionException(HttpServletRequest request, HttpServletResponse response, Exception ex)
|
||||
throws IOException {
|
||||
|
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.exception;
|
||||
|
||||
import org.dspace.core.I18nUtil;
|
||||
|
||||
/**
|
||||
* <p>Extend {@link UnprocessableEntityException} to provide a specific error message
|
||||
* in the REST response. The error message is added to the response in
|
||||
* {@link DSpaceApiExceptionControllerAdvice#handleCustomUnprocessableEntityException},
|
||||
* hence it should not contain sensitive or security-compromising info.</p>
|
||||
*
|
||||
* @author Bruno Roemers (bruno.roemers at atmire.com)
|
||||
*/
|
||||
public class EPersonNameNotProvidedException extends UnprocessableEntityException implements TranslatableException {
|
||||
|
||||
public static final String MESSAGE_KEY = "org.dspace.app.rest.exception.EPersonNameNotProvidedException.message";
|
||||
|
||||
public EPersonNameNotProvidedException() {
|
||||
super(I18nUtil.getMessage(MESSAGE_KEY));
|
||||
}
|
||||
|
||||
public EPersonNameNotProvidedException(Throwable cause) {
|
||||
super(I18nUtil.getMessage(MESSAGE_KEY), cause);
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return MESSAGE_KEY;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.exception;
|
||||
|
||||
import org.dspace.core.I18nUtil;
|
||||
|
||||
/**
|
||||
* <p>Extend {@link UnprocessableEntityException} to provide a specific error message
|
||||
* in the REST response. The error message is added to the response in
|
||||
* {@link DSpaceApiExceptionControllerAdvice#handleCustomUnprocessableEntityException},
|
||||
* hence it should not contain sensitive or security-compromising info.</p>
|
||||
*
|
||||
* @author Bruno Roemers (bruno.roemers at atmire.com)
|
||||
*/
|
||||
public class GroupNameNotProvidedException extends UnprocessableEntityException implements TranslatableException {
|
||||
|
||||
public static final String MESSAGE_KEY = "org.dspace.app.rest.exception.GroupNameNotProvidedException.message";
|
||||
|
||||
public GroupNameNotProvidedException() {
|
||||
super(I18nUtil.getMessage(MESSAGE_KEY));
|
||||
}
|
||||
|
||||
public GroupNameNotProvidedException(Throwable cause) {
|
||||
super(I18nUtil.getMessage(MESSAGE_KEY), cause);
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return MESSAGE_KEY;
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.exception;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.eperson.EmptyWorkflowGroupException;
|
||||
|
||||
/**
|
||||
* <p>Extend {@link UnprocessableEntityException} to provide a specific error message
|
||||
* in the REST response. The error message is added to the response in
|
||||
* {@link DSpaceApiExceptionControllerAdvice#handleCustomUnprocessableEntityException},
|
||||
* hence it should not contain sensitive or security-compromising info.</p>
|
||||
*
|
||||
* <p>Note there is a similarly named error in the DSpace API module.</p>
|
||||
*
|
||||
* @author Bruno Roemers (bruno.roemers at atmire.com)
|
||||
*/
|
||||
public class RESTEmptyWorkflowGroupException extends UnprocessableEntityException implements TranslatableException {
|
||||
|
||||
/**
|
||||
* @param formatStr string with placeholders, ideally obtained using {@link I18nUtil}
|
||||
* @param cause {@link EmptyWorkflowGroupException}, from which EPerson id and group id are obtained
|
||||
* @return message with EPerson id and group id substituted
|
||||
*/
|
||||
private static String formatMessage(String formatStr, EmptyWorkflowGroupException cause) {
|
||||
MessageFormat fmt = new MessageFormat(formatStr);
|
||||
String[] values = {
|
||||
cause.getEPersonId().toString(), // {0} in formatStr
|
||||
cause.getGroupId().toString(), // {1} in formatStr
|
||||
};
|
||||
return fmt.format(values);
|
||||
}
|
||||
|
||||
public static final String MESSAGE_KEY = "org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException.message";
|
||||
|
||||
private final EmptyWorkflowGroupException cause;
|
||||
|
||||
public RESTEmptyWorkflowGroupException(EmptyWorkflowGroupException cause) {
|
||||
super(formatMessage(
|
||||
I18nUtil.getMessage(MESSAGE_KEY), cause
|
||||
), cause);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public String getMessageKey() {
|
||||
return MESSAGE_KEY;
|
||||
}
|
||||
|
||||
public String getLocalizedMessage(Context context) {
|
||||
return formatMessage(
|
||||
I18nUtil.getMessage(MESSAGE_KEY, context), cause
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -25,4 +25,8 @@ public class RepositoryNotFoundException extends RuntimeException {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("The repository type %s.%s was not found", apiCategory, model);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
package org.dspace.app.rest.exception;
|
||||
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.I18nUtil;
|
||||
|
||||
/**
|
||||
* <p>Implement TranslatableException to make Exceptions or RuntimeExceptions translatable.</p>
|
||||
*
|
||||
* <p>In most cases, only {@link #getMessageKey()} should be implemented;
|
||||
* {@link #getMessage()} and {@link #getLocalizedMessage()} are already provided by {@link Throwable}
|
||||
* and the default implementation of {@link #getLocalizedMessage(Context)} is usually sufficient.</p>
|
||||
*
|
||||
* <p>A locale-aware message can be obtained by calling {@link #getLocalizedMessage(Context)}.</p>
|
||||
*
|
||||
* @author Bruno Roemers (bruno.roemers at atmire.com)
|
||||
*/
|
||||
public interface TranslatableException {
|
||||
|
||||
/**
|
||||
* @return message key (used for lookup with {@link I18nUtil})
|
||||
*/
|
||||
String getMessageKey();
|
||||
|
||||
/**
|
||||
* Already implemented by {@link Throwable}.
|
||||
* @return message for default locale
|
||||
*/
|
||||
String getMessage();
|
||||
|
||||
/**
|
||||
* Already implemented by {@link Throwable}.
|
||||
* @return message for default locale
|
||||
*/
|
||||
String getLocalizedMessage();
|
||||
|
||||
/**
|
||||
* @param context current DSpace context (used to infer current locale)
|
||||
* @return message for current locale (or default locale if current locale did not yield a result)
|
||||
*/
|
||||
default String getLocalizedMessage(Context context) {
|
||||
return I18nUtil.getMessage(getMessageKey(), context);
|
||||
}
|
||||
|
||||
}
|
@@ -7,6 +7,9 @@
|
||||
*/
|
||||
package org.dspace.app.rest.model.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -95,4 +98,19 @@ public enum RestSearchOperator {
|
||||
public String getDspaceOperator() {
|
||||
return dspaceOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of dspace operators of this enum's values, plus "query" which is also allowed, but will be
|
||||
* transformed in {@link org.dspace.app.rest.converter.query.SearchQueryConverter} to any of the others
|
||||
*
|
||||
* @return List of dspace operators of this enum's values, plus "query"
|
||||
*/
|
||||
public static List<String> getListOfAllowedSearchOperatorStrings() {
|
||||
List<String> allowedSearchOperatorStrings = new ArrayList<>();
|
||||
for (RestSearchOperator restSearchOperator: Arrays.asList(RestSearchOperator.values())) {
|
||||
allowedSearchOperatorStrings.add(restSearchOperator.getDspaceOperator());
|
||||
}
|
||||
allowedSearchOperatorStrings.add("query");
|
||||
return allowedSearchOperatorStrings;
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
package org.dspace.app.rest.model.submit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The SelectableRelationship REST Resource. It is not addressable directly, only
|
||||
* used as inline object in the InputForm resource.
|
||||
@@ -24,6 +26,7 @@ public class SelectableRelationship {
|
||||
private String filter;
|
||||
private String searchConfiguration;
|
||||
private String nameVariants;
|
||||
private List<String> externalSources;
|
||||
|
||||
public void setRelationshipType(String relationshipType) {
|
||||
this.relationshipType = relationshipType;
|
||||
@@ -56,4 +59,12 @@ public class SelectableRelationship {
|
||||
public String getNameVariants() {
|
||||
return nameVariants;
|
||||
}
|
||||
|
||||
public List<String> getExternalSources() {
|
||||
return externalSources;
|
||||
}
|
||||
|
||||
public void setExternalSources(List<String> externalSources) {
|
||||
this.externalSources = externalSources;
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.rest.exception.UnprocessableEntityException;
|
||||
import org.dspace.app.rest.model.query.RestSearchOperator;
|
||||
import org.dspace.app.rest.parameter.SearchFilter;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
@@ -29,13 +31,15 @@ public class SearchFilterResolver implements HandlerMethodArgumentResolver {
|
||||
public static final String SEARCH_FILTER_PREFIX = "f.";
|
||||
public static final String FILTER_OPERATOR_SEPARATOR = ",";
|
||||
|
||||
public static final List<String> ALLOWED_SEARCH_OPERATORS =
|
||||
RestSearchOperator.getListOfAllowedSearchOperatorStrings();
|
||||
|
||||
public boolean supportsParameter(final MethodParameter parameter) {
|
||||
return parameter.getParameterType().equals(SearchFilter.class) || isSearchFilterList(parameter);
|
||||
}
|
||||
|
||||
public Object resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer,
|
||||
final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory)
|
||||
throws Exception {
|
||||
final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
|
||||
List<SearchFilter> result = new LinkedList<>();
|
||||
|
||||
Iterator<String> parameterNames = webRequest.getParameterNames();
|
||||
@@ -48,7 +52,7 @@ public class SearchFilterResolver implements HandlerMethodArgumentResolver {
|
||||
for (String value : webRequest.getParameterValues(parameterName)) {
|
||||
String filterValue = StringUtils.substringBeforeLast(value, FILTER_OPERATOR_SEPARATOR);
|
||||
String filterOperator = StringUtils.substringAfterLast(value, FILTER_OPERATOR_SEPARATOR);
|
||||
|
||||
this.checkIfValidOperator(filterOperator);
|
||||
result.add(new SearchFilter(filterName, filterOperator, filterValue));
|
||||
}
|
||||
}
|
||||
@@ -61,6 +65,19 @@ public class SearchFilterResolver implements HandlerMethodArgumentResolver {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfValidOperator(String filterOperator) {
|
||||
if (StringUtils.isNotBlank(filterOperator)) {
|
||||
if (!ALLOWED_SEARCH_OPERATORS.contains(filterOperator.trim())) {
|
||||
throw new UnprocessableEntityException(
|
||||
"The operator can't be \"" + filterOperator + "\", must be the of one of: " +
|
||||
String.join(", ", ALLOWED_SEARCH_OPERATORS));
|
||||
}
|
||||
} else {
|
||||
throw new UnprocessableEntityException(
|
||||
"The operator can't be empty, must be the one of: " + String.join(", ", ALLOWED_SEARCH_OPERATORS));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSearchFilterList(final MethodParameter parameter) {
|
||||
return parameter.getParameterType().equals(List.class)
|
||||
&& parameter.getGenericParameterType() instanceof ParameterizedType
|
||||
|
@@ -22,6 +22,8 @@ import org.dspace.app.rest.Parameter;
|
||||
import org.dspace.app.rest.SearchRestMethod;
|
||||
import org.dspace.app.rest.authorization.AuthorizationFeatureService;
|
||||
import org.dspace.app.rest.exception.DSpaceBadRequestException;
|
||||
import org.dspace.app.rest.exception.EPersonNameNotProvidedException;
|
||||
import org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException;
|
||||
import org.dspace.app.rest.exception.UnprocessableEntityException;
|
||||
import org.dspace.app.rest.model.EPersonRest;
|
||||
import org.dspace.app.rest.model.MetadataRest;
|
||||
@@ -34,6 +36,7 @@ import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.content.service.SiteService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.EmptyWorkflowGroupException;
|
||||
import org.dspace.eperson.RegistrationData;
|
||||
import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
@@ -199,8 +202,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
|
||||
List<MetadataValueRest> epersonLastName = metadataRest.getMap().get("eperson.lastname");
|
||||
if (epersonFirstName == null || epersonLastName == null ||
|
||||
epersonFirstName.isEmpty() || epersonLastName.isEmpty()) {
|
||||
throw new UnprocessableEntityException("The eperson.firstname and eperson.lastname values need to be " +
|
||||
"filled in");
|
||||
throw new EPersonNameNotProvidedException();
|
||||
}
|
||||
}
|
||||
String password = epersonRest.getPassword();
|
||||
@@ -313,8 +315,10 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
|
||||
es.delete(context, eperson);
|
||||
} catch (SQLException | IOException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
} catch (EmptyWorkflowGroupException e) {
|
||||
throw new RESTEmptyWorkflowGroupException(e);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new UnprocessableEntityException(e.getMessage(), e);
|
||||
throw new UnprocessableEntityException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dspace.app.rest.Parameter;
|
||||
import org.dspace.app.rest.SearchRestMethod;
|
||||
import org.dspace.app.rest.converter.MetadataConverter;
|
||||
import org.dspace.app.rest.exception.GroupNameNotProvidedException;
|
||||
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
|
||||
import org.dspace.app.rest.exception.UnprocessableEntityException;
|
||||
import org.dspace.app.rest.model.GroupRest;
|
||||
@@ -71,7 +72,7 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
|
||||
}
|
||||
|
||||
if (isBlank(groupRest.getName())) {
|
||||
throw new UnprocessableEntityException("cannot create group, no group name is provided");
|
||||
throw new GroupNameNotProvidedException();
|
||||
}
|
||||
|
||||
Group group;
|
||||
|
@@ -119,15 +119,6 @@ public class RelationshipRestRepository extends DSpaceRestRepository<Relationshi
|
||||
Relationship relationship = relationshipService.create(context, leftItem, rightItem,
|
||||
relationshipType, -1, -1,
|
||||
leftwardValue, rightwardValue);
|
||||
// The above if check deals with the case that a Relationship can be created if the user has write
|
||||
// rights on one of the two items. The following updateItem calls can however call the
|
||||
// ItemService.update() functions which would fail if the user doesn't have permission on both items.
|
||||
// Since we allow this creation to happen under these circumstances, we need to turn off the
|
||||
// authorization system here so that this failure doesn't happen when the items need to be update
|
||||
context.turnOffAuthorisationSystem();
|
||||
relationshipService.updateItem(context, relationship.getLeftItem());
|
||||
relationshipService.updateItem(context, relationship.getRightItem());
|
||||
context.restoreAuthSystemState();
|
||||
return converter.toRest(relationship, utils.obtainProjection());
|
||||
} else {
|
||||
throw new AccessDeniedException("You do not have write rights on this relationship's items");
|
||||
|
@@ -8,9 +8,12 @@
|
||||
package org.dspace.app.rest.utils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.core.Constants;
|
||||
|
||||
/**
|
||||
@@ -48,4 +51,59 @@ public class URLUtils {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is one URL a prefix of another? Ignores credentials, fragments and queries.
|
||||
* @param pattern the potential prefix.
|
||||
* @param candidate does this URL match the pattern?
|
||||
* @return {@code true} if the URLs have equal protocol, host and port,
|
||||
* and each element of {@code candidate}'s path {@link String#equals}
|
||||
* the corresponding element in {@code pattern}'s path.
|
||||
* @throws IllegalArgumentException if either URL is malformed.
|
||||
*/
|
||||
public static boolean urlIsPrefixOf(String pattern, String candidate)
|
||||
throws IllegalArgumentException {
|
||||
URL patternURL;
|
||||
URL candidateURL;
|
||||
|
||||
try {
|
||||
patternURL = new URL(pattern);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("The pattern URL is not valid: " + pattern);
|
||||
}
|
||||
|
||||
try {
|
||||
candidateURL = new URL(candidate);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("The candidate URL is not valid: " + candidate);
|
||||
}
|
||||
|
||||
// Deal with port defaults.
|
||||
int patternPort = patternURL.getPort();
|
||||
if (patternPort < 0) {
|
||||
patternPort = patternURL.getDefaultPort();
|
||||
}
|
||||
int candidatePort = candidateURL.getPort();
|
||||
if (candidatePort < 0) {
|
||||
candidatePort = candidateURL.getDefaultPort();
|
||||
}
|
||||
|
||||
boolean isPrefix;
|
||||
isPrefix = StringUtils.equals(candidateURL.getProtocol(), patternURL.getProtocol());
|
||||
isPrefix &= StringUtils.equals(candidateURL.getHost(), patternURL.getHost());
|
||||
isPrefix &= candidatePort == patternPort;
|
||||
|
||||
String[] candidateElements = StringUtils.split(candidateURL.getPath(), '/');
|
||||
String[] patternElements = StringUtils.split(patternURL.getPath(), '/');
|
||||
|
||||
// Candidate path cannot be shorter than pattern path.
|
||||
if (patternElements.length > candidateElements.length) {
|
||||
return false;
|
||||
}
|
||||
for (int elementN = 0; elementN < patternElements.length; elementN++) {
|
||||
isPrefix &= candidateElements[elementN].equals(patternElements[elementN]);
|
||||
}
|
||||
|
||||
return isPrefix;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ package org.dspace.app.rest.utils;
|
||||
|
||||
import static java.lang.Integer.parseInt;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.dspace.app.rest.utils.URLUtils.urlIsPrefixOf;
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
@@ -24,6 +25,8 @@ import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -43,7 +46,8 @@ import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.app.rest.converter.ConverterService;
|
||||
import org.dspace.app.rest.exception.PaginationException;
|
||||
import org.dspace.app.rest.exception.RepositoryNotFoundException;
|
||||
@@ -99,7 +103,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
@Component
|
||||
public class Utils {
|
||||
|
||||
private static final Logger log = Logger.getLogger(Utils.class);
|
||||
private static final Logger log = LogManager.getLogger(Utils.class);
|
||||
|
||||
/**
|
||||
* The default page size, if unspecified in the request.
|
||||
@@ -220,9 +224,11 @@ public class Utils {
|
||||
*
|
||||
* @param apiCategory
|
||||
* @param modelPlural
|
||||
* @return
|
||||
* @return the requested repository.
|
||||
* @throws RepositoryNotFoundException passed through.
|
||||
*/
|
||||
public DSpaceRestRepository getResourceRepository(String apiCategory, String modelPlural) {
|
||||
public DSpaceRestRepository getResourceRepository(String apiCategory, String modelPlural)
|
||||
throws RepositoryNotFoundException {
|
||||
String model = makeSingular(modelPlural);
|
||||
return getResourceRepositoryByCategoryAndModel(apiCategory, model);
|
||||
}
|
||||
@@ -233,9 +239,11 @@ public class Utils {
|
||||
*
|
||||
* @param apiCategory
|
||||
* @param modelSingular
|
||||
* @return
|
||||
* @return the requested repository.
|
||||
* @throws RepositoryNotFoundException if no such repository can be found.
|
||||
*/
|
||||
public DSpaceRestRepository getResourceRepositoryByCategoryAndModel(String apiCategory, String modelSingular) {
|
||||
public DSpaceRestRepository getResourceRepositoryByCategoryAndModel(String apiCategory, String modelSingular)
|
||||
throws RepositoryNotFoundException {
|
||||
try {
|
||||
return applicationContext.getBean(apiCategory + "." + modelSingular, DSpaceRestRepository.class);
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
@@ -243,6 +251,10 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the names of all {@link DSpaceRestRepository} implementations.
|
||||
* @return the names of all repository types.
|
||||
*/
|
||||
public String[] getRepositories() {
|
||||
return applicationContext.getBeanNamesForType(DSpaceRestRepository.class);
|
||||
}
|
||||
@@ -304,8 +316,8 @@ public class Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the canonical representation of a metadata key in DSpace. I.e.
|
||||
* <schema>.<element>[.<qualifier>]
|
||||
* Build the canonical representation of a metadata key in DSpace. I.e.
|
||||
* {@code <schema>.<element>[.<qualifier>]}
|
||||
*
|
||||
* @param schema
|
||||
* @param element
|
||||
@@ -413,6 +425,7 @@ public class Utils {
|
||||
* It will then look through all the DSpaceObjectServices to try and match this UUID to a DSpaceObject.
|
||||
* If one is found, this DSpaceObject is added to the List of DSpaceObjects that we will return.
|
||||
* @param context The relevant DSpace context
|
||||
* @param list The interesting UUIDs.
|
||||
* @return The resulting list of DSpaceObjects that we parsed out of the request
|
||||
*/
|
||||
public List<DSpaceObject> constructDSpaceObjectList(Context context, List<String> list) {
|
||||
@@ -699,6 +712,7 @@ public class Utils {
|
||||
/**
|
||||
* Adds embeds (if the maximum embed level has not been exceeded yet) for all properties annotated with
|
||||
* {@code @LinkRel} or whose return types are {@link RestAddressableModel} subclasses.
|
||||
* @param resource the resource to be so augmented.
|
||||
*/
|
||||
public void embedMethodLevelRels(HALResource<? extends RestAddressableModel> resource) {
|
||||
if (resource.getContent().getEmbedLevel() == EMBED_MAX_LEVELS) {
|
||||
@@ -916,26 +930,45 @@ public class Utils {
|
||||
*/
|
||||
public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) throws SQLException {
|
||||
String dspaceUrl = configurationService.getProperty("dspace.server.url");
|
||||
// first check if the uri could be valid
|
||||
if (!StringUtils.startsWith(uri, dspaceUrl)) {
|
||||
throw new IllegalArgumentException("the supplied uri is not valid: " + uri);
|
||||
|
||||
// Convert strings to URL objects.
|
||||
// Do this early to check that inputs are well-formed.
|
||||
URL dspaceUrlObject;
|
||||
URL requestUrlObject;
|
||||
try {
|
||||
dspaceUrlObject = new URL(dspaceUrl);
|
||||
requestUrlObject = new URL(uri);
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Configuration '%s' or request '%s' is malformed", dspaceUrl, uri));
|
||||
}
|
||||
|
||||
// Check whether the URI could be valid.
|
||||
if (!urlIsPrefixOf(dspaceUrl, uri)) {
|
||||
throw new IllegalArgumentException("the supplied uri is not ours: " + uri);
|
||||
}
|
||||
|
||||
// Extract from the URI the category, model and id components.
|
||||
// They start after the dspaceUrl/api/{apiCategory}/{apiModel}/{id}
|
||||
int dspacePathLength = StringUtils.split(dspaceUrlObject.getPath(), '/').length;
|
||||
String[] requestPath = StringUtils.split(requestUrlObject.getPath(), '/');
|
||||
String[] uriParts = Arrays.copyOfRange(requestPath, dspacePathLength,
|
||||
requestPath.length);
|
||||
if ("api".equalsIgnoreCase(uriParts[0])) {
|
||||
uriParts = Arrays.copyOfRange(uriParts, 1, uriParts.length);
|
||||
}
|
||||
// extract from the uri the category, model and id components
|
||||
// they start after the dspaceUrl/api/{apiCategory}/{apiModel}/{id}
|
||||
String[] uriParts = uri.substring(dspaceUrl.length() + (dspaceUrl.endsWith("/") ? 0 : 1) + "api/".length())
|
||||
.split("/", 3);
|
||||
if (uriParts.length != 3) {
|
||||
throw new IllegalArgumentException("the supplied uri is not valid: " + uri);
|
||||
throw new IllegalArgumentException("the supplied uri lacks required path elements: " + uri);
|
||||
}
|
||||
|
||||
DSpaceRestRepository repository;
|
||||
try {
|
||||
repository = getResourceRepository(uriParts[0], uriParts[1]);
|
||||
if (!(repository instanceof ReloadableEntityObjectRepository)) {
|
||||
throw new IllegalArgumentException("the supplied uri is not valid: " + uri);
|
||||
throw new IllegalArgumentException("the supplied uri is not for the right type of repository: " + uri);
|
||||
}
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
throw new IllegalArgumentException("the supplied uri is not valid: " + uri, e);
|
||||
throw new IllegalArgumentException("the repository for the URI '" + uri + "' was not found", e);
|
||||
}
|
||||
|
||||
Serializable pk;
|
||||
@@ -943,7 +976,7 @@ public class Utils {
|
||||
// cast the string id in the uriParts to the real pk class
|
||||
pk = castToPKClass((ReloadableEntityObjectRepository) repository, uriParts[2]);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("the supplied uri is not valid: " + uri, e);
|
||||
throw new IllegalArgumentException("the supplied uri could not be cast to a Primary Key class: " + uri, e);
|
||||
}
|
||||
try {
|
||||
// disable the security as we only need to retrieve the object to further process the authorization
|
||||
|
@@ -2370,7 +2370,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorContains() throws Exception {
|
||||
public void discoverSearchObjectsWithQueryOperatorContains_query() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -2445,7 +2445,83 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotContains() throws Exception {
|
||||
public void discoverSearchObjectsWithQueryOperatorContains() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
//** GIVEN **
|
||||
//1. A community-collection structure with one parent community with sub-community and two collections.
|
||||
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
|
||||
.withName("Sub Community")
|
||||
.build();
|
||||
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
|
||||
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
|
||||
//2. Three public items that are readable by Anonymous with different subjects
|
||||
Item publicItem1 = ItemBuilder.createItem(context, col1)
|
||||
.withTitle("Test")
|
||||
.withIssueDate("2010-10-17")
|
||||
.withAuthor("Smith, Donald")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem2 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Test 2")
|
||||
.withIssueDate("1990-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works")
|
||||
.withSubject("TestingForMore").withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem3 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Public item 2")
|
||||
.withIssueDate("2010-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test")
|
||||
.withAuthor("test2, test2").withAuthor("Maybe, Maybe")
|
||||
.withSubject("AnotherTest").withSubject("TestingForMore")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
UUID scope = col2.getID();
|
||||
//** WHEN **
|
||||
//An anonymous user browses this endpoint to find the the objects in the system
|
||||
//With the given search filter
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "test,contains"))
|
||||
//** THEN **
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//The type has to be 'discover'
|
||||
.andExpect(jsonPath("$.type", is("discover")))
|
||||
//The page object needs to look like this
|
||||
.andExpect(jsonPath("$._embedded.searchResult.page", is(
|
||||
PageMatcher.pageEntry(0, 20)
|
||||
)))
|
||||
//The search results have to contain the items that match the searchFilter
|
||||
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder(
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Test"),
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Test 2")
|
||||
)))
|
||||
//These facets have to show up in the embedded.facets section as well with the given hasMore property
|
||||
// because we don't exceed their default limit for a hasMore true (the default is 10)
|
||||
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
|
||||
FacetEntryMatcher.authorFacet(false),
|
||||
FacetEntryMatcher.entityTypeFacet(false),
|
||||
FacetEntryMatcher.subjectFacet(false),
|
||||
FacetEntryMatcher.dateIssuedFacet(false),
|
||||
FacetEntryMatcher.hasContentInOriginalBundleFacet(false)
|
||||
)))
|
||||
//There always needs to be a self link available
|
||||
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotContains_query() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -2518,6 +2594,81 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotContains() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
//** GIVEN **
|
||||
//1. A community-collection structure with one parent community with sub-community and two collections.
|
||||
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
|
||||
.withName("Sub Community")
|
||||
.build();
|
||||
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
|
||||
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
|
||||
//2. Three public items that are readable by Anonymous with different subjects
|
||||
Item publicItem1 = ItemBuilder.createItem(context, col1)
|
||||
.withTitle("Test")
|
||||
.withIssueDate("2010-10-17")
|
||||
.withAuthor("Smith, Donald")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem2 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Test 2")
|
||||
.withIssueDate("1990-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works")
|
||||
.withSubject("TestingForMore").withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem3 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Public item 2")
|
||||
.withIssueDate("2010-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test")
|
||||
.withAuthor("test2, test2").withAuthor("Maybe, Maybe")
|
||||
.withSubject("AnotherTest").withSubject("TestingForMore")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
UUID scope = col2.getID();
|
||||
//** WHEN **
|
||||
//An anonymous user browses this endpoint to find the the objects in the system
|
||||
//With the given search filter
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "test,notcontains"))
|
||||
//** THEN **
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//The type has to be 'discover'
|
||||
.andExpect(jsonPath("$.type", is("discover")))
|
||||
//The page object needs to look like this
|
||||
.andExpect(jsonPath("$._embedded.searchResult.page", is(
|
||||
PageMatcher.pageEntry(0, 20)
|
||||
)))
|
||||
//The search results have to contain the items that match the searchFilter
|
||||
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItem(
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Public item 2")
|
||||
)))
|
||||
//These facets have to show up in the embedded.facets section as well with the given hasMore property
|
||||
// because we don't exceed their default limit for a hasMore true (the default is 10)
|
||||
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
|
||||
FacetEntryMatcher.authorFacet(false),
|
||||
FacetEntryMatcher.entityTypeFacet(false),
|
||||
FacetEntryMatcher.subjectFacet(false),
|
||||
FacetEntryMatcher.dateIssuedFacet(false),
|
||||
FacetEntryMatcher.hasContentInOriginalBundleFacet(false)
|
||||
)))
|
||||
//There always needs to be a self link available
|
||||
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsTestForMinMaxValues() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
@@ -2673,7 +2824,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorEquals() throws Exception {
|
||||
public void discoverSearchObjectsWithQueryOperatorEquals_query() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -2747,7 +2898,82 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotEquals() throws Exception {
|
||||
public void discoverSearchObjectsWithQueryOperatorEquals() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
//** GIVEN **
|
||||
//1. A community-collection structure with one parent community with sub-community and two collections.
|
||||
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
|
||||
.withName("Sub Community")
|
||||
.build();
|
||||
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
|
||||
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
|
||||
//2. Three public items that are readable by Anonymous with different subjects
|
||||
Item publicItem1 = ItemBuilder.createItem(context, col1)
|
||||
.withTitle("Test")
|
||||
.withIssueDate("2010-10-17")
|
||||
.withAuthor("Smith, Donald")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem2 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Test 2")
|
||||
.withIssueDate("1990-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works")
|
||||
.withSubject("TestingForMore").withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem3 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Public item 2")
|
||||
.withIssueDate("2010-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test")
|
||||
.withAuthor("test2, test2").withAuthor("Maybe, Maybe")
|
||||
.withSubject("AnotherTest").withSubject("TestingForMore")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
UUID scope = col2.getID();
|
||||
//** WHEN **
|
||||
//An anonymous user browses this endpoint to find the the objects in the system
|
||||
//With the given search filter
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "Test,equals"))
|
||||
//** THEN **
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//The type has to be 'discover'
|
||||
.andExpect(jsonPath("$.type", is("discover")))
|
||||
//The page object needs to look like this
|
||||
.andExpect(jsonPath("$._embedded.searchResult.page", is(
|
||||
PageMatcher.pageEntry(0, 20)
|
||||
)))
|
||||
//The search results have to contain the items that match the searchFilter
|
||||
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder(
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Test")
|
||||
)))
|
||||
//These facets have to show up in the embedded.facets section as well with the given hasMore property
|
||||
// because we don't exceed their default limit for a hasMore true (the default is 10)
|
||||
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
|
||||
FacetEntryMatcher.authorFacet(false),
|
||||
FacetEntryMatcher.entityTypeFacet(false),
|
||||
FacetEntryMatcher.subjectFacet(false),
|
||||
FacetEntryMatcher.dateIssuedFacet(false),
|
||||
FacetEntryMatcher.hasContentInOriginalBundleFacet(false)
|
||||
)))
|
||||
//There always needs to be a self link available
|
||||
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotEquals_query() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -2822,7 +3048,83 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotAuthority() throws Exception {
|
||||
public void discoverSearchObjectsWithQueryOperatorNotEquals() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
//** GIVEN **
|
||||
//1. A community-collection structure with one parent community with sub-community and two collections.
|
||||
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
|
||||
.withName("Sub Community")
|
||||
.build();
|
||||
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
|
||||
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
|
||||
//2. Three public items that are readable by Anonymous with different subjects
|
||||
Item publicItem1 = ItemBuilder.createItem(context, col1)
|
||||
.withTitle("Test")
|
||||
.withIssueDate("2010-10-17")
|
||||
.withAuthor("Smith, Donald")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem2 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Test 2")
|
||||
.withIssueDate("1990-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works")
|
||||
.withSubject("TestingForMore").withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem3 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Public item 2")
|
||||
.withIssueDate("2010-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test")
|
||||
.withAuthor("test2, test2").withAuthor("Maybe, Maybe")
|
||||
.withSubject("AnotherTest").withSubject("TestingForMore")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
UUID scope = col2.getID();
|
||||
//** WHEN **
|
||||
//An anonymous user browses this endpoint to find the the objects in the system
|
||||
//With the given search filter
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "Test,notequals"))
|
||||
//** THEN **
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//The type has to be 'discover'
|
||||
.andExpect(jsonPath("$.type", is("discover")))
|
||||
//The page object needs to look like this
|
||||
.andExpect(jsonPath("$._embedded.searchResult.page", is(
|
||||
PageMatcher.pageEntry(0, 20)
|
||||
)))
|
||||
//The search results have to contain the items that match the searchFilter
|
||||
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItems(
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Test 2"),
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Public item 2")
|
||||
)))
|
||||
//These facets have to show up in the embedded.facets section as well with the given hasMore property
|
||||
// because we don't exceed their default limit for a hasMore true (the default is 10)
|
||||
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
|
||||
FacetEntryMatcher.authorFacet(false),
|
||||
FacetEntryMatcher.entityTypeFacet(false),
|
||||
FacetEntryMatcher.subjectFacet(false),
|
||||
FacetEntryMatcher.dateIssuedFacet(false),
|
||||
FacetEntryMatcher.hasContentInOriginalBundleFacet(false)
|
||||
)))
|
||||
//There always needs to be a self link available
|
||||
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotAuthority_query() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -2895,6 +3197,108 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithQueryOperatorNotAuthority() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
//** GIVEN **
|
||||
//1. A community-collection structure with one parent community with sub-community and two collections.
|
||||
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||
.withName("Parent Community")
|
||||
.build();
|
||||
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
|
||||
.withName("Sub Community")
|
||||
.build();
|
||||
Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build();
|
||||
Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build();
|
||||
//2. Three public items that are readable by Anonymous with different subjects
|
||||
Item publicItem1 = ItemBuilder.createItem(context, col1)
|
||||
.withTitle("Test")
|
||||
.withIssueDate("2010-10-17")
|
||||
.withAuthor("Smith, Donald")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem2 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Test 2")
|
||||
.withIssueDate("1990-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works")
|
||||
.withSubject("TestingForMore").withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
Item publicItem3 = ItemBuilder.createItem(context, col2)
|
||||
.withTitle("Public item 2")
|
||||
.withIssueDate("2010-02-13")
|
||||
.withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test")
|
||||
.withAuthor("test2, test2").withAuthor("Maybe, Maybe")
|
||||
.withSubject("AnotherTest").withSubject("TestingForMore")
|
||||
.withSubject("ExtraEntry")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
UUID scope = col2.getID();
|
||||
//** WHEN **
|
||||
//An anonymous user browses this endpoint to find the the objects in the system
|
||||
//With the given search filter
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "test,notauthority"))
|
||||
//** THEN **
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//The type has to be 'discover'
|
||||
.andExpect(jsonPath("$.type", is("discover")))
|
||||
//The page object needs to look like this
|
||||
.andExpect(jsonPath("$._embedded.searchResult.page", is(
|
||||
PageMatcher.pageEntry(0, 20)
|
||||
)))
|
||||
//The search results have to contain the items that match the searchFilter
|
||||
.andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItem(
|
||||
SearchResultMatcher.matchOnItemName("item", "items", "Public item 2")
|
||||
)))
|
||||
//These facets have to show up in the embedded.facets section as well with the given hasMore property
|
||||
// because we don't exceed their default limit for a hasMore true (the default is 10)
|
||||
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
|
||||
FacetEntryMatcher.authorFacet(false),
|
||||
FacetEntryMatcher.entityTypeFacet(false),
|
||||
FacetEntryMatcher.subjectFacet(false),
|
||||
FacetEntryMatcher.dateIssuedFacet(false),
|
||||
FacetEntryMatcher.hasContentInOriginalBundleFacet(false)
|
||||
)))
|
||||
//There always needs to be a self link available
|
||||
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithMissingQueryOperator() throws Exception {
|
||||
//** WHEN **
|
||||
// An anonymous user browses this endpoint to find the the objects in the system
|
||||
// With the given search filter where there is the filter operator missing in the value (must be of form
|
||||
// <:filter-value>,<:filter-operator>)
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "test"))
|
||||
//** THEN **
|
||||
//Will result in 422 status because of missing filter operator
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsWithNotValidQueryOperator() throws Exception {
|
||||
//** WHEN **
|
||||
// An anonymous user browses this endpoint to find the the objects in the system
|
||||
// With the given search filter where there is a non-valid filter operator given (must be of form
|
||||
// <:filter-value>,<:filter-operator> where the filter operator is one of: “contains”, “notcontains”, "equals"
|
||||
// “notequals”, “authority”, “notauthority”, "query”); see enum RestSearchOperator
|
||||
getClient().perform(get("/api/discover/search/objects")
|
||||
.param("f.title", "test,operator"))
|
||||
//** THEN **
|
||||
//Will result in 422 status because of non-valid filter operator
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discoverSearchObjectsTestWithDateIssuedQueryTest() throws Exception {
|
||||
//We turn off the authorization system in order to create the structure as defined below
|
||||
@@ -4478,7 +4882,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
.perform(get("/api/discover/search/objects")
|
||||
.param("configuration", "administrativeView")
|
||||
.param("query", "Test")
|
||||
.param("f.withdrawn", "true")
|
||||
.param("f.withdrawn", "true,contains")
|
||||
)
|
||||
|
||||
.andExpect(status().isOk())
|
||||
@@ -4497,7 +4901,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
.perform(get("/api/discover/search/objects")
|
||||
.param("configuration", "administrativeView")
|
||||
.param("query", "Test")
|
||||
.param("f.withdrawn", "false")
|
||||
.param("f.withdrawn", "false,contains")
|
||||
)
|
||||
|
||||
.andExpect(status().isOk())
|
||||
@@ -4517,7 +4921,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
.perform(get("/api/discover/search/objects")
|
||||
.param("configuration", "administrativeView")
|
||||
.param("query", "Test")
|
||||
.param("f.discoverable", "true")
|
||||
.param("f.discoverable", "true,contains")
|
||||
)
|
||||
|
||||
.andExpect(status().isOk())
|
||||
@@ -4537,7 +4941,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
|
||||
.perform(get("/api/discover/search/objects")
|
||||
.param("configuration", "administrativeView")
|
||||
.param("query", "Test")
|
||||
.param("f.discoverable", "false")
|
||||
.param("f.discoverable", "false,contains")
|
||||
)
|
||||
|
||||
.andExpect(status().isOk())
|
||||
|
@@ -17,6 +17,7 @@ import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
@@ -31,15 +32,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.rest.exception.EPersonNameNotProvidedException;
|
||||
import org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException;
|
||||
import org.dspace.app.rest.jackson.IgnoreJacksonWriteOnlyAccess;
|
||||
import org.dspace.app.rest.matcher.EPersonMatcher;
|
||||
import org.dspace.app.rest.matcher.GroupMatcher;
|
||||
@@ -61,6 +66,7 @@ import org.dspace.builder.GroupBuilder;
|
||||
import org.dspace.builder.WorkflowItemBuilder;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.PasswordHash;
|
||||
@@ -69,6 +75,7 @@ import org.dspace.eperson.service.AccountService;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.RegistrationDataService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.workflow.WorkflowService;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -85,6 +92,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
@Autowired
|
||||
private EPersonService ePersonService;
|
||||
|
||||
@Autowired
|
||||
private WorkflowService workflowService;
|
||||
|
||||
@Autowired
|
||||
private RegistrationDataDAO registrationDataDAO;
|
||||
@Autowired
|
||||
@@ -832,13 +842,62 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
// 422 error when trying to DELETE the eperson=submitter
|
||||
getClient(token).perform(delete("/api/eperson/epersons/" + ePerson.getID()))
|
||||
.andExpect(status().is(422));
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
|
||||
// Verify the eperson is still here
|
||||
getClient(token).perform(get("/api/eperson/epersons/" + ePerson.getID()))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteLastPersonInWorkflowGroup() throws Exception {
|
||||
// set up workflow group with ePerson as only member
|
||||
context.turnOffAuthorisationSystem();
|
||||
EPerson ePerson = EPersonBuilder
|
||||
.createEPerson(context)
|
||||
.withEmail("eperson@example.com")
|
||||
.withNameInMetadata("Sample", "EPerson")
|
||||
.build();
|
||||
Community community = CommunityBuilder
|
||||
.createCommunity(context)
|
||||
.build();
|
||||
Collection collection = CollectionBuilder
|
||||
.createCollection(context, community)
|
||||
.withWorkflowGroup(1, ePerson)
|
||||
.build();
|
||||
Group workflowGroup = collection.getWorkflowStep1(context);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
// enable Polish locale
|
||||
configurationService.setProperty("webui.supported.locales", "en, pl");
|
||||
|
||||
// generate expectations
|
||||
String key = RESTEmptyWorkflowGroupException.MESSAGE_KEY;
|
||||
String[] values = {
|
||||
ePerson.getID().toString(),
|
||||
workflowGroup.getID().toString(),
|
||||
};
|
||||
MessageFormat defaultFmt = new MessageFormat(I18nUtil.getMessage(key));
|
||||
MessageFormat plFmt = new MessageFormat(I18nUtil.getMessage(key, new Locale("pl")));
|
||||
|
||||
// make request using Polish locale
|
||||
getClient(getAuthToken(admin.getEmail(), password))
|
||||
.perform(
|
||||
delete("/api/eperson/epersons/" + ePerson.getID())
|
||||
.header("Accept-Language", "pl") // request Polish response
|
||||
)
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(plFmt.format(values))))
|
||||
.andExpect(status().reason(startsWith("[PL]"))); // verify it did not fall back to default locale
|
||||
|
||||
// make request using default locale
|
||||
getClient(getAuthToken(admin.getEmail(), password))
|
||||
.perform(delete("/api/eperson/epersons/" + ePerson.getID()))
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(defaultFmt.format(values))))
|
||||
.andExpect(status().reason(not(startsWith("[PL]"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patchByForbiddenUser() throws Exception {
|
||||
|
||||
@@ -2528,12 +2587,33 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess());
|
||||
|
||||
// enable Polish locale
|
||||
configurationService.setProperty("webui.supported.locales", "en, pl");
|
||||
|
||||
try {
|
||||
// make request using Polish locale
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
.header("Accept-Language", "pl") // request Polish response
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(
|
||||
// find message in dspace-server-webapp/src/test/resources/Messages_pl.properties
|
||||
I18nUtil.getMessage(EPersonNameNotProvidedException.MESSAGE_KEY, new Locale("pl"))
|
||||
)))
|
||||
.andExpect(status().reason(startsWith("[PL]"))); // verify default locale was NOT used
|
||||
|
||||
// make request using default locale
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(
|
||||
I18nUtil.getMessage(EPersonNameNotProvidedException.MESSAGE_KEY)
|
||||
)))
|
||||
.andExpect(status().reason(not(startsWith("[PL]"))));
|
||||
|
||||
EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail);
|
||||
assertNull(createdEPerson);
|
||||
@@ -2574,12 +2654,34 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess());
|
||||
|
||||
// enable Polish locale
|
||||
configurationService.setProperty("webui.supported.locales", "en, pl");
|
||||
|
||||
try {
|
||||
// make request using Polish locale
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
.header("Accept-Language", "pl") // request Polish response
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(
|
||||
// find message in dspace-server-webapp/src/test/resources/Messages_pl.properties
|
||||
I18nUtil.getMessage(EPersonNameNotProvidedException.MESSAGE_KEY, new Locale("pl"))
|
||||
)))
|
||||
.andExpect(status().reason(startsWith("[PL]"))); // verify default locale was NOT used
|
||||
|
||||
// make request using default locale
|
||||
getClient().perform(post("/api/eperson/epersons")
|
||||
.param("token", newRegisterToken)
|
||||
.content(mapper.writeValueAsBytes(ePersonRest))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(
|
||||
// find message in dspace-server-webapp/src/test/resources/Messages_pl.properties
|
||||
I18nUtil.getMessage(EPersonNameNotProvidedException.MESSAGE_KEY)
|
||||
)))
|
||||
.andExpect(status().reason(not(startsWith("[PL]"))));
|
||||
|
||||
EPerson createdEPerson = ePersonService.findByEmail(context, newRegisterEmail);
|
||||
assertNull(createdEPerson);
|
||||
|
@@ -9,8 +9,11 @@ package org.dspace.app.rest;
|
||||
|
||||
import static com.jayway.jsonpath.JsonPath.read;
|
||||
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -27,11 +30,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dspace.app.rest.exception.GroupNameNotProvidedException;
|
||||
import org.dspace.app.rest.matcher.EPersonMatcher;
|
||||
import org.dspace.app.rest.matcher.GroupMatcher;
|
||||
import org.dspace.app.rest.matcher.HalMatcher;
|
||||
@@ -54,6 +59,7 @@ import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
import org.dspace.content.service.CommunityService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
@@ -194,10 +200,49 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
String authToken = getAuthToken(admin.getEmail(), password);
|
||||
getClient(authToken)
|
||||
.perform(post("/api/eperson/groups")
|
||||
.content(mapper.writeValueAsBytes(groupRest))
|
||||
.contentType(contentType))
|
||||
.andExpect(status().isUnprocessableEntity());
|
||||
.perform(
|
||||
post("/api/eperson/groups").content("").contentType(contentType)
|
||||
)
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(containsString("Unprocessable")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWithoutNameTest() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
GroupRest groupRest = new GroupRest(); // no name set
|
||||
|
||||
String authToken = getAuthToken(admin.getEmail(), password);
|
||||
|
||||
// enable Polish locale
|
||||
configurationService.setProperty("webui.supported.locales", "en, pl");
|
||||
|
||||
// make request using Polish locale
|
||||
getClient(authToken)
|
||||
.perform(
|
||||
post("/api/eperson/groups")
|
||||
.header("Accept-Language", "pl") // request Polish response
|
||||
.content(mapper.writeValueAsBytes(groupRest))
|
||||
.contentType(contentType)
|
||||
)
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(
|
||||
I18nUtil.getMessage(GroupNameNotProvidedException.MESSAGE_KEY, new Locale("pl"))
|
||||
)))
|
||||
.andExpect(status().reason(startsWith("[PL]"))); // verify it did not fall back to default locale
|
||||
|
||||
// make request using default locale
|
||||
getClient(authToken)
|
||||
.perform(
|
||||
post("/api/eperson/groups")
|
||||
.content(mapper.writeValueAsBytes(groupRest))
|
||||
.contentType(contentType)
|
||||
)
|
||||
.andExpect(status().isUnprocessableEntity())
|
||||
.andExpect(status().reason(is(
|
||||
I18nUtil.getMessage(GroupNameNotProvidedException.MESSAGE_KEY)
|
||||
)))
|
||||
.andExpect(status().reason(not(startsWith("[PL]"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -9,6 +9,8 @@ package org.dspace.app.rest;
|
||||
|
||||
import static com.jayway.jsonpath.JsonPath.read;
|
||||
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
@@ -18,6 +20,7 @@ import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
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.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
@@ -33,6 +36,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.solr.client.solrj.SolrQuery;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.dspace.app.rest.matcher.PageMatcher;
|
||||
import org.dspace.app.rest.matcher.RelationshipMatcher;
|
||||
import org.dspace.app.rest.model.RelationshipRest;
|
||||
@@ -41,13 +46,16 @@ import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.builder.CollectionBuilder;
|
||||
import org.dspace.builder.CommunityBuilder;
|
||||
import org.dspace.builder.EPersonBuilder;
|
||||
import org.dspace.builder.EntityTypeBuilder;
|
||||
import org.dspace.builder.ItemBuilder;
|
||||
import org.dspace.builder.MetadataFieldBuilder;
|
||||
import org.dspace.builder.RelationshipBuilder;
|
||||
import org.dspace.builder.RelationshipTypeBuilder;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.EntityType;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.Relationship;
|
||||
@@ -59,6 +67,7 @@ import org.dspace.content.service.MetadataSchemaService;
|
||||
import org.dspace.content.service.RelationshipTypeService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.discovery.MockSolrSearchCore;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -86,6 +95,8 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest
|
||||
@Autowired
|
||||
private MetadataSchemaService metadataSchemaService;
|
||||
|
||||
@Autowired
|
||||
MockSolrSearchCore mockSolrSearchCore;
|
||||
private Community parentCommunity;
|
||||
private Community child1;
|
||||
|
||||
@@ -2673,4 +2684,149 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest
|
||||
RelationshipBuilder.deleteRelationship(idRef.get());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVirtualMdInRESTAndSolrDoc() throws Exception {
|
||||
context.turnOffAuthorisationSystem();
|
||||
// Create entity types if needed
|
||||
EntityType journalEntityType = entityTypeService.findByEntityType(context, "Journal");
|
||||
if (journalEntityType == null) {
|
||||
journalEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Journal").build();
|
||||
}
|
||||
EntityType journalVolumeEntityType = entityTypeService.findByEntityType(context, "JournalVolume");
|
||||
if (journalVolumeEntityType == null) {
|
||||
journalVolumeEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "JournalVolume").build();
|
||||
}
|
||||
EntityType journalIssueEntityType = entityTypeService.findByEntityType(context, "JournalIssue");
|
||||
if (journalIssueEntityType == null) {
|
||||
journalIssueEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "JournalIssue").build();
|
||||
}
|
||||
EntityType publicationEntityType = entityTypeService.findByEntityType(context, "Publication");
|
||||
if (publicationEntityType == null) {
|
||||
publicationEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build();
|
||||
}
|
||||
|
||||
// Create relationship types if needed
|
||||
RelationshipType isPublicationOfJournalIssue = relationshipTypeService
|
||||
.findbyTypesAndTypeName(context, journalIssueEntityType, publicationEntityType,
|
||||
"isPublicationOfJournalIssue", "isJournalIssueOfPublication");
|
||||
if (isPublicationOfJournalIssue == null) {
|
||||
isPublicationOfJournalIssue = RelationshipTypeBuilder.createRelationshipTypeBuilder(context,
|
||||
journalIssueEntityType, publicationEntityType, "isPublicationOfJournalIssue",
|
||||
"isJournalIssueOfPublication", null, null, null, null).build();
|
||||
}
|
||||
RelationshipType isIssueOfJournalVolume = relationshipTypeService
|
||||
.findbyTypesAndTypeName(context, journalVolumeEntityType, journalIssueEntityType,
|
||||
"isIssueOfJournalVolume", "isJournalVolumeOfIssue");
|
||||
if (isIssueOfJournalVolume == null) {
|
||||
isIssueOfJournalVolume = RelationshipTypeBuilder.createRelationshipTypeBuilder(context,
|
||||
journalVolumeEntityType, journalIssueEntityType, "isIssueOfJournalVolume",
|
||||
"isJournalVolumeOfIssue", null, null, null, null).build();
|
||||
} else {
|
||||
// Otherwise error in destroy methods when removing Journal Issue-Journal Volume relationship
|
||||
// since the rightMinCardinality constraint would be violated upon deletion
|
||||
isIssueOfJournalVolume.setRightMinCardinality(0);
|
||||
}
|
||||
RelationshipType isVolumeOfJournal = relationshipTypeService
|
||||
.findbyTypesAndTypeName(context, journalEntityType, journalVolumeEntityType,
|
||||
"isVolumeOfJournal", "isJournalOfVolume");
|
||||
if (isVolumeOfJournal == null) {
|
||||
isVolumeOfJournal = RelationshipTypeBuilder.createRelationshipTypeBuilder(context,
|
||||
journalEntityType, journalVolumeEntityType, "isVolumeOfJournal", "isJournalOfVolume",
|
||||
null, null, null, null).build();
|
||||
} else {
|
||||
// Otherwise error in destroy methods when removing Journal Volume - Journal relationship
|
||||
// since the rightMinCardinality constraint would be violated upon deletion
|
||||
isVolumeOfJournal.setRightMinCardinality(0);
|
||||
}
|
||||
|
||||
// Create virtual metadata fields if needed
|
||||
MetadataSchema journalSchema = metadataSchemaService.find(context, "journal");
|
||||
if (journalSchema == null) {
|
||||
journalSchema = metadataSchemaService.create(context, "journal", "journal");
|
||||
}
|
||||
String journalTitleVirtualMdField = "journal.title";
|
||||
MetadataField journalTitleField = metadataFieldService.findByString(context, journalTitleVirtualMdField, '.');
|
||||
if (journalTitleField == null) {
|
||||
metadataFieldService.create(context, journalSchema, "title", null, "Journal Title");
|
||||
}
|
||||
|
||||
String journalTitle = "Journal Title Test";
|
||||
|
||||
// Create entity items
|
||||
Item journal =
|
||||
ItemBuilder.createItem(context, col1).withRelationshipType("Journal").withTitle(journalTitle).build();
|
||||
Item journalVolume =
|
||||
ItemBuilder.createItem(context, col1).withRelationshipType("JournalVolume").withTitle("JournalVolume")
|
||||
.build();
|
||||
Item journalIssue =
|
||||
ItemBuilder.createItem(context, col1).withRelationshipType("JournalIssue").withTitle("JournalIssue")
|
||||
.build();
|
||||
Item publication =
|
||||
ItemBuilder.createItem(context, col1).withRelationshipType("Publication").withTitle("Publication").build();
|
||||
|
||||
// Link Publication-Journal Issue
|
||||
RelationshipBuilder.createRelationshipBuilder(context, journalIssue, publication, isPublicationOfJournalIssue)
|
||||
.build();
|
||||
// Link Journal Issue-Journal Volume
|
||||
RelationshipBuilder.createRelationshipBuilder(context, journalVolume, journalIssue, isIssueOfJournalVolume)
|
||||
.build();
|
||||
mockSolrSearchCore.getSolr().commit(false, false);
|
||||
|
||||
// Verify Publication item via REST does not contain virtual md journal.title
|
||||
getClient().perform(get("/api/core/items/" + publication.getID()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.metadata." + journalTitleVirtualMdField).doesNotExist());
|
||||
|
||||
// Verify Publication item via Solr does not contain virtual md journal.title
|
||||
SolrQuery solrQuery = new SolrQuery();
|
||||
solrQuery.setQuery("search.resourceid:" + publication.getID());
|
||||
QueryResponse queryResponse = mockSolrSearchCore.getSolr().query(solrQuery);
|
||||
assertThat(queryResponse.getResults().size(), equalTo(1));
|
||||
assertNull(queryResponse.getResults().get(0).getFieldValues(journalTitleVirtualMdField));
|
||||
|
||||
// Link Journal Volume - Journal
|
||||
RelationshipBuilder.createRelationshipBuilder(context, journal, journalVolume, isVolumeOfJournal).build();
|
||||
mockSolrSearchCore.getSolr().commit(false, false);
|
||||
|
||||
// Verify Publication item via REST does contain virtual md journal.title
|
||||
getClient().perform(get("/api/core/items/" + publication.getID()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.metadata", allOf(
|
||||
matchMetadata(journalTitleVirtualMdField, journalTitle))));
|
||||
|
||||
// Verify Publication item via Solr contains virtual md journal.title
|
||||
queryResponse = mockSolrSearchCore.getSolr().query(solrQuery);
|
||||
assertThat(queryResponse.getResults().size(), equalTo(1));
|
||||
assertEquals(journalTitle,
|
||||
((List) queryResponse.getResults().get(0).getFieldValues(journalTitleVirtualMdField)).get(0));
|
||||
|
||||
// Verify Journal Volume item via REST also contains virtual md journal.title
|
||||
getClient().perform(get("/api/core/items/" + journalVolume.getID()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.metadata", allOf(
|
||||
matchMetadata(journalTitleVirtualMdField, journalTitle))));
|
||||
|
||||
// Verify Journal Volume item via Solr also contains virtual md journal.title
|
||||
solrQuery.setQuery("search.resourceid:" + journalVolume.getID());
|
||||
queryResponse = mockSolrSearchCore.getSolr().query(solrQuery);
|
||||
assertThat(queryResponse.getResults().size(), equalTo(1));
|
||||
assertEquals(journalTitle,
|
||||
((List) queryResponse.getResults().get(0).getFieldValues(journalTitleVirtualMdField)).get(0));
|
||||
|
||||
// Verify Journal Issue item via REST also contains virtual md journal.title
|
||||
getClient().perform(get("/api/core/items/" + journalIssue.getID()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.metadata", allOf(
|
||||
matchMetadata(journalTitleVirtualMdField, journalTitle))));
|
||||
|
||||
// Verify Journal Issue item via Solr also contains virtual md journal.title
|
||||
solrQuery.setQuery("search.resourceid:" + journalIssue.getID());
|
||||
queryResponse = mockSolrSearchCore.getSolr().query(solrQuery);
|
||||
assertThat(queryResponse.getResults().size(), equalTo(1));
|
||||
assertEquals(journalTitle,
|
||||
((List) queryResponse.getResults().get(0).getFieldValues(journalTitleVirtualMdField)).get(0));
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
@@ -591,6 +592,47 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
|
||||
resetLocalesConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleExternalSourcesTest() throws Exception {
|
||||
String token = getAuthToken(admin.getEmail(), password);
|
||||
|
||||
getClient(token).perform(get("/api/config/submissionforms/traditionalpageone"))
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//We expect the content type to be "application/hal+json;charset=UTF-8"
|
||||
.andExpect(content().contentType(contentType))
|
||||
//Check that the JSON root matches the expected "traditionalpageone" input forms
|
||||
.andExpect(jsonPath("$.id", is("traditionalpageone")))
|
||||
.andExpect(jsonPath("$.name", is("traditionalpageone")))
|
||||
.andExpect(jsonPath("$.type", is("submissionform")))
|
||||
.andExpect(jsonPath("$._links.self.href", Matchers
|
||||
.startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone")))
|
||||
// check the external sources of the first field in the first row
|
||||
.andExpect(jsonPath("$.rows[0].fields[0].selectableRelationship.externalSources",
|
||||
contains(is("orcid"), is("my_staff_db"))))
|
||||
;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noExternalSourcesTest() throws Exception {
|
||||
String token = getAuthToken(admin.getEmail(), password);
|
||||
|
||||
getClient(token).perform(get("/api/config/submissionforms/journalVolumeStep"))
|
||||
//The status has to be 200 OK
|
||||
.andExpect(status().isOk())
|
||||
//We expect the content type to be "application/hal+json;charset=UTF-8"
|
||||
.andExpect(content().contentType(contentType))
|
||||
//Check that the JSON root matches the expected "journalVolumeStep" input forms
|
||||
.andExpect(jsonPath("$.id", is("journalVolumeStep")))
|
||||
.andExpect(jsonPath("$.name", is("journalVolumeStep")))
|
||||
.andExpect(jsonPath("$.type", is("submissionform")))
|
||||
.andExpect(jsonPath("$._links.self.href", Matchers
|
||||
.startsWith(REST_SERVER_URL + "config/submissionforms/journalVolumeStep")))
|
||||
// check the external sources of the first field in the first row
|
||||
.andExpect(jsonPath("$.rows[0].fields[0].selectableRelationship.externalSources", nullValue()))
|
||||
;
|
||||
}
|
||||
|
||||
private void resetLocalesConfiguration() throws DCInputsReaderException {
|
||||
configurationService.setProperty("default.locale","en");
|
||||
configurationService.setProperty("webui.supported.locales",null);
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.utils;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author mwood
|
||||
*/
|
||||
public class URLUtilsTest {
|
||||
|
||||
public URLUtilsTest() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of decode method, of class URLUtils.
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void testDecode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of encode method, of class URLUtils.
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void testEncode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of urlIsPrefixOf method, of class URLUtils.
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@SuppressWarnings("UnusedAssignment")
|
||||
public void testUrlIsPrefixOf() {
|
||||
boolean isPrefix;
|
||||
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/path", "http://example.com/path");
|
||||
assertTrue("Should match if all is equal", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com:80/test", "http://example.com:80/test/1");
|
||||
assertTrue("Should match if pattern path is longer", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com:80/test", "http://example.com/test");
|
||||
assertTrue("Should match if missing port matches default", isPrefix);
|
||||
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/path", "https://example.com/path");
|
||||
assertFalse("Should not match if protocols don't match", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/", "http://oops.example.com/");
|
||||
assertFalse("Should not match if hosts don't match", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com:80/", "http://example.com:8080/");
|
||||
assertFalse("Should not match if ports don't match", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/path1/a", "http://example.com/path2/a");
|
||||
assertFalse("Should not match if paths don't match", isPrefix);
|
||||
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/path", "http://example.com/path/");
|
||||
assertTrue("Should match with, without trailing slash", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/path1", "http://example.com/path2");
|
||||
assertFalse("Should not match if paths don't match", isPrefix);
|
||||
isPrefix = URLUtils.urlIsPrefixOf("http://example.com/path", "http://example.com/path2/sub");
|
||||
assertFalse("Should not match if interior path elements don't match", isPrefix);
|
||||
|
||||
// Check if a malformed URL raises an exception
|
||||
isPrefix = URLUtils.urlIsPrefixOf(null, "http://example.com/");
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# 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/
|
||||
#
|
||||
|
||||
# User exposed error messages
|
||||
org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException.message = [PL] Refused to delete user {0} because it is the only member of the \
|
||||
workflow group {1}. Delete the tasks and group first if you want to remove this user.
|
||||
org.dspace.app.rest.exception.EPersonNameNotProvidedException.message = [PL] The eperson.firstname and eperson.lastname values need to be filled in
|
||||
org.dspace.app.rest.exception.GroupNameNotProvidedException.message = [PL] Cannot create group, no group name is provided
|
16
dspace/config/modules/relationship.cfg
Normal file
16
dspace/config/modules/relationship.cfg
Normal file
@@ -0,0 +1,16 @@
|
||||
#---------------------------------------------------------------#
|
||||
#-----------------RELATIONSHIP CONFIGURATIONS-------------------#
|
||||
#---------------------------------------------------------------#
|
||||
# Configuration properties used by the RelationshipService #
|
||||
#---------------------------------------------------------------#
|
||||
|
||||
# The maximum number of items to be updated when adjusting a relationship.
|
||||
# This includes the relationship’s left and right item.
|
||||
# If the max is below 2, the relationship’s left and right item will still be processed. Defaults to 20
|
||||
# relationship.update.relateditems.max = 20
|
||||
|
||||
# The maximum depth of relationships to traverse.
|
||||
# A value of 5 means that a maximum of 5 levels (relationships) deep will be scanned for updates on both the left side
|
||||
# and the right side. Indirectly related items requiring more than 5 items will be skipped. Defaults to 5
|
||||
# relationship.update.relateditems.maxdepth = 5
|
||||
|
@@ -7,19 +7,19 @@
|
||||
<!ELEMENT form (row)+ >
|
||||
<!ELEMENT row (field|relation-field)+ >
|
||||
<!ATTLIST form name NMTOKEN #REQUIRED>
|
||||
|
||||
|
||||
<!ELEMENT field (dc-schema, dc-element, dc-qualifier?, language?, repeatable?, label, style?, type-bind?, input-type, hint, required?, regex?, vocabulary?, visibility?, readonly?) >
|
||||
<!ELEMENT dc-schema (#PCDATA) >
|
||||
<!ELEMENT dc-element (#PCDATA) >
|
||||
<!ELEMENT dc-qualifier (#PCDATA) >
|
||||
<!ELEMENT language (#PCDATA)>
|
||||
<!ELEMENT type-bind (#PCDATA) >
|
||||
|
||||
|
||||
<!ELEMENT repeatable (#PCDATA) >
|
||||
<!ELEMENT label (#PCDATA) >
|
||||
<!ELEMENT style (#PCDATA) >
|
||||
<!ELEMENT input-type (#PCDATA)>
|
||||
|
||||
|
||||
<!ELEMENT hint (#PCDATA) >
|
||||
<!ELEMENT required (#PCDATA)>
|
||||
<!ELEMENT name-variants (#PCDATA)>
|
||||
@@ -40,7 +40,7 @@
|
||||
<!ATTLIST value-pairs value-pairs-name CDATA #REQUIRED
|
||||
dc-term CDATA #REQUIRED
|
||||
>
|
||||
|
||||
|
||||
<!ELEMENT pair (displayed-value,stored-value) >
|
||||
<!ELEMENT displayed-value (#PCDATA)>
|
||||
<!ELEMENT stored-value (#PCDATA)>
|
||||
@@ -49,16 +49,17 @@
|
||||
|
||||
<!ELEMENT vocabulary (#PCDATA) >
|
||||
|
||||
<!ATTLIST vocabulary closed (true|false) "false">
|
||||
<!ATTLIST vocabulary closed (true|false) "false">
|
||||
|
||||
<!ELEMENT visibility (#PCDATA) >
|
||||
|
||||
<!ATTLIST language value-pairs-name CDATA #IMPLIED>
|
||||
<!ATTLIST language value-pairs-name CDATA #IMPLIED>
|
||||
|
||||
<!ELEMENT readonly (#PCDATA) >
|
||||
|
||||
<!ELEMENT relationship-type (#PCDATA) >
|
||||
<!ELEMENT search-configuration (#PCDATA) >
|
||||
<!ELEMENT filter (#PCDATA) >
|
||||
<!ELEMENT relation-field (relationship-type, search-configuration, filter?, repeatable?, name-variants?, label, type-bind?, hint, linked-metadata-field?, required?, visibility?)>
|
||||
<!ELEMENT externalsources (#PCDATA) >
|
||||
<!ELEMENT relation-field (relationship-type, search-configuration, filter?, repeatable?, name-variants?, label, type-bind?, hint, linked-metadata-field?, externalsources?, required?, regex?, visibility?)>
|
||||
<!ELEMENT linked-metadata-field (dc-schema, dc-element, dc-qualifier?, input-type)>
|
||||
|
@@ -31,7 +31,7 @@
|
||||
<label>Title</label>
|
||||
<input-type>onebox</input-type>
|
||||
<hint>Enter the name of the file.</hint>
|
||||
<required>You must enter a main title for this item.</required>
|
||||
<required>You must enter a name for this file</required>
|
||||
</field>
|
||||
</row>
|
||||
<row>
|
||||
@@ -54,14 +54,19 @@
|
||||
<search-configuration>person</search-configuration>
|
||||
<repeatable>true</repeatable>
|
||||
<label>Author</label>
|
||||
<hint>Add an author</hint>
|
||||
<hint>Enter the author's name (Family name, Given names).</hint>
|
||||
<linked-metadata-field>
|
||||
<dc-schema>dc</dc-schema>
|
||||
<dc-element>contributor</dc-element>
|
||||
<dc-qualifier>author</dc-qualifier>
|
||||
<input-type>name</input-type>
|
||||
<input-type>onebox</input-type>
|
||||
</linked-metadata-field>
|
||||
<required>At least one author (plain text or relationship) is required</required>
|
||||
<externalsources>orcid</externalsources>
|
||||
<required></required>
|
||||
<!-- You may choose to validate author names via a Regular Expression if it's appropriate for
|
||||
your institution. The below regex requires a comma to be present in the author field.
|
||||
However, this is disabled by default to support organizations as authors, etc. -->
|
||||
<!--<regex>\w+(,)+\w+</regex>-->
|
||||
</relation-field>
|
||||
</row>
|
||||
<row>
|
||||
@@ -74,7 +79,6 @@
|
||||
<input-type>onebox</input-type>
|
||||
<hint>Enter the main title of the item.</hint>
|
||||
<required>You must enter a main title for this item.</required>
|
||||
<!-- <language value-pairs-name="common_iso_languages">true</language> -->
|
||||
</field>
|
||||
</row>
|
||||
<row>
|
||||
@@ -99,8 +103,7 @@
|
||||
<style>col-sm-4</style>
|
||||
<input-type>date</input-type>
|
||||
<hint>Please give the date of previous publication or public distribution.
|
||||
You can leave out the day and/or month if they aren't
|
||||
applicable.
|
||||
You can leave out the day and/or month if they aren't applicable.
|
||||
</hint>
|
||||
<required>You must enter at least the year.</required>
|
||||
</field>
|
||||
@@ -241,6 +244,15 @@
|
||||
</form>
|
||||
|
||||
<form name="peopleStep">
|
||||
<row>
|
||||
<relation-field>
|
||||
<relationship-type>isPublicationOfAuthor</relationship-type>
|
||||
<search-configuration>publication</search-configuration>
|
||||
<label>Publication</label>
|
||||
<hint>import a publicaton</hint>
|
||||
<externalsources>pubmed</externalsources>
|
||||
</relation-field>
|
||||
</row>
|
||||
<row>
|
||||
<field>
|
||||
<dc-schema>person</dc-schema>
|
||||
@@ -494,6 +506,7 @@
|
||||
<filter>creativework.publisher:somepublishername</filter>
|
||||
<label>Journal</label>
|
||||
<hint>Select the journal related to this volume.</hint>
|
||||
<externalsources>sherpaJournal</externalsources>
|
||||
</relation-field>
|
||||
</row>
|
||||
<row>
|
||||
@@ -625,6 +638,7 @@
|
||||
<dc-qualifier>author</dc-qualifier>
|
||||
<input-type>name</input-type>
|
||||
</linked-metadata-field>
|
||||
<externalsources>orcid</externalsources>
|
||||
<required>At least one author (plain text or relationship) is required</required>
|
||||
</relation-field>
|
||||
</row>
|
||||
@@ -852,7 +866,7 @@
|
||||
</row>
|
||||
|
||||
<!-- Additional Subject with specific taxonomy (like mesh, fos, ...) -->
|
||||
|
||||
|
||||
<!-- example for FOS -->
|
||||
<!-- <row>
|
||||
<field>
|
||||
@@ -893,6 +907,7 @@
|
||||
<dc-element>relation</dc-element>
|
||||
<input-type>onebox</input-type>
|
||||
</linked-metadata-field>
|
||||
<externalsources></externalsources>
|
||||
<required></required>
|
||||
</relation-field>
|
||||
</row>
|
||||
@@ -1043,6 +1058,7 @@
|
||||
<dc-qualifier>name</dc-qualifier>
|
||||
<input-type>onebox</input-type>
|
||||
</linked-metadata-field>
|
||||
<externalsources></externalsources>
|
||||
<required>One funding agency is required</required>
|
||||
</relation-field>
|
||||
</row>
|
||||
@@ -1053,14 +1069,14 @@
|
||||
<label>Name of the Funding Stream</label>
|
||||
<input-type>onebox</input-type>
|
||||
<hint>Enter the name of the funding stream of the project</hint>
|
||||
</field>
|
||||
</field>
|
||||
<field>
|
||||
<dc-schema>dc</dc-schema>
|
||||
<dc-element>identifier</dc-element>
|
||||
<label>Award number</label>
|
||||
<input-type>onebox</input-type>
|
||||
<hint>Enter the identifier of the project</hint>
|
||||
</field>
|
||||
</field>
|
||||
</row>
|
||||
</form>
|
||||
|
||||
@@ -1341,9 +1357,9 @@
|
||||
|
||||
</value-pairs>
|
||||
|
||||
<!-- OpenAIRE document types
|
||||
https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_publicationtype.html
|
||||
Based on COAR Vocabularies -> Controlled Vocabulary for Resource Type Genres (Version 2.0):
|
||||
<!-- OpenAIRE document types
|
||||
https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_publicationtype.html
|
||||
Based on COAR Vocabularies -> Controlled Vocabulary for Resource Type Genres (Version 2.0):
|
||||
http://vocabularies.coar-repositories.org/documentation/resource_types/
|
||||
-->
|
||||
<value-pairs value-pairs-name="openaire_types" dc-term="type">
|
||||
|
Reference in New Issue
Block a user