mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-17 06:53:09 +00:00
Content reports ported from DSpace 6.x (#8598)
* content Reports * Fixed CheckStyle errors * Fixed CheckStyle errors * Fixed CheckStyle errors * First batch of fixes: mainly Javadoc, and a bit of code re-engineering * Fixed CheckStyle errors * Fixed CheckStyle errors in dspace-server-webapp * Applied requested changes for DSpace code conventions compliance * Added GET endpoint to Filtered Items report * Updated to latest version from main branch * Fixed missing imports * Fixed CheckStyle errors * Fixed H2 database initialization * Fixed unit tests and an integration test * Fixed CheckStyle errors * Fixed CheckStyle errors * Fixed ItemServiceIT test * Test without collection criterion * Fixed max result count in integration test * Disable findByMetadataQuery test to diagnose errors in other tests * Disabled ContentReportRestRepositoryIT test to validate existing tests * Re-enable test in dspace-api * Re-enabled ContentReportRestRepositoryIT tests to diagnose failures * Fixed item matching in the second test * Fixed JSON path error * Use projections to trigger embedding the owning collection in ItemRest only for the Filtered Items report * Fixed usage of allowEmbedding() through non-null arguments * Exclude owning collection from ItemRest when null/empty * Trying an alternate way to discriminate report-based Item conversions * Fixed embedded owning collection management * Replaced ItemConverter with correct version * Fixed Filtered Collections test in ContentReportRestRepositoryIT * Fixed test * Transferred owning collection to a separate class FilteredItemRest * Rollback to DSpace repo version * Fixed matcher for Filtered Collections summary * Fixed matcher for Filtered Collections summary (take 2) * Add printing mock request results to diagnose remaining problems * Try logging output through System.err * Cancelled attempt to print JSON results (does nothing) * Attempt to fix ContentReportRestRepositoryIT tests * Removed predefined UUIDs and handles * Fixed import formatting * Fixed expected results in ContentReportRestRepositoryIT * Switched to a custom matcher for the Filtered Item report test * Fixed import format * Fixed JSON collection matching in Filtered Items test * Fixed Filtered Items matcher * Fixed expected result * Fixed the test for now... * Fixed test again * Disabled non-working test * Fixed a few typos * Moved Filtered Collections report business logic to dspace-api * Fixed outdated controller * Fixed import and lost @Ignore annotation * Retrieved a lost test correction * Fixed Filtered Collections test * Reverted to the last working version (except for 2nd test, which remains disabled) * Moved Filtered Items report business logic to dspace-api * Fixed import style * Added switch to enable/disable Content Reports * Fixed an out-of-date class * Removed unused imports * Fixed activation configuration for Content Reports * Added missing @Test annotation * A forgotten Hibernate dialect configuration. I also removed obsolete Oracle settings configuration. * Switched to GET requests for Content Reports * Switched to GET requests for Content Reports * Fixed styling in imports * Fixed imports * Cleaned deprecated code * Simplified regex since trim() method is invoked on each token thereafter * Added Javadoc in the interface. * Relocated Content Reports configuration into a new file * Added "unauthorized" tests and cleaned up code repetitions * Fixed parameter according to Javadoc * Fixed Filtered Items test * Use of @ConditionalOnProperty annotation * Rolled back to manual parameter managing * Second try on @ConditionalOnProperty, with proper test configuration * Rolled back (again) to manual service activation checking (needed for proper behaviour depending on activation and authorization) * Eliminated inheritance between FilteredItemRest and ItemRest * Re-established the type property in FilteredItemRest (and in FilterCollectionRest for uniformity). --------- Co-authored-by: Jean-François Morin <jean-francois.morin@bibl.ulaval.ca>
This commit is contained in:
@@ -49,6 +49,7 @@ import org.dspace.content.service.MetadataSchemaService;
|
|||||||
import org.dspace.content.service.RelationshipService;
|
import org.dspace.content.service.RelationshipService;
|
||||||
import org.dspace.content.service.WorkspaceItemService;
|
import org.dspace.content.service.WorkspaceItemService;
|
||||||
import org.dspace.content.virtual.VirtualMetadataPopulator;
|
import org.dspace.content.virtual.VirtualMetadataPopulator;
|
||||||
|
import org.dspace.contentreport.QueryPredicate;
|
||||||
import org.dspace.core.Constants;
|
import org.dspace.core.Constants;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
import org.dspace.core.LogHelper;
|
import org.dspace.core.LogHelper;
|
||||||
@@ -175,7 +176,6 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
|||||||
private QAEventsDAO qaEventsDao;
|
private QAEventsDAO qaEventsDao;
|
||||||
|
|
||||||
protected ItemServiceImpl() {
|
protected ItemServiceImpl() {
|
||||||
super();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -275,9 +275,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
|||||||
+ template.getID()));
|
+ template.getID()));
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
} else {
|
|
||||||
return collection.getTemplateItem();
|
|
||||||
}
|
}
|
||||||
|
return collection.getTemplateItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1190,9 +1189,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
|||||||
if (item.getOwningCollection() == null) {
|
if (item.getOwningCollection() == null) {
|
||||||
if (!isInProgressSubmission(context, item)) {
|
if (!isInProgressSubmission(context, item)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return collectionService.canEditBoolean(context, item.getOwningCollection(), false);
|
return collectionService.canEditBoolean(context, item.getOwningCollection(), false);
|
||||||
@@ -1284,8 +1282,8 @@ prevent the generation of resource policy entry values with null dspace_object a
|
|||||||
if (!authorizeService
|
if (!authorizeService
|
||||||
.isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
|
.isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
|
||||||
defaultPolicy.getID()) &&
|
defaultPolicy.getID()) &&
|
||||||
(!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
|
(!appendMode && isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
|
||||||
appendMode && this.shouldBeAppended(context, dso, defaultPolicy))) {
|
appendMode && shouldBeAppended(context, dso, defaultPolicy))) {
|
||||||
ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
|
ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
|
||||||
newPolicy.setdSpaceObject(dso);
|
newPolicy.setdSpaceObject(dso);
|
||||||
newPolicy.setAction(Constants.READ);
|
newPolicy.setAction(Constants.READ);
|
||||||
@@ -1384,9 +1382,8 @@ prevent the generation of resource policy entry values with null dspace_object a
|
|||||||
|
|
||||||
if (Item.ANY.equals(value)) {
|
if (Item.ANY.equals(value)) {
|
||||||
return itemDAO.findByMetadataField(context, mdf, null, true);
|
return itemDAO.findByMetadataField(context, mdf, null, true);
|
||||||
} else {
|
|
||||||
return itemDAO.findByMetadataField(context, mdf, value, true);
|
|
||||||
}
|
}
|
||||||
|
return itemDAO.findByMetadataField(context, mdf, value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1430,9 +1427,24 @@ prevent the generation of resource policy entry values with null dspace_object a
|
|||||||
|
|
||||||
if (Item.ANY.equals(value)) {
|
if (Item.ANY.equals(value)) {
|
||||||
return itemDAO.findByMetadataField(context, mdf, null, true);
|
return itemDAO.findByMetadataField(context, mdf, null, true);
|
||||||
} else {
|
|
||||||
return itemDAO.findByMetadataField(context, mdf, value, true);
|
|
||||||
}
|
}
|
||||||
|
return itemDAO.findByMetadataField(context, mdf, value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Item> findByMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, long offset, int limit)
|
||||||
|
throws SQLException {
|
||||||
|
return itemDAO.findByMetadataQuery(context, queryPredicates, collectionUuids, "text_value ~ ?",
|
||||||
|
offset, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long countForMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids)
|
||||||
|
throws SQLException {
|
||||||
|
return itemDAO.countForMetadataQuery(context, queryPredicates, collectionUuids, "text_value ~ ?");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1498,20 +1510,19 @@ prevent the generation of resource policy entry values with null dspace_object a
|
|||||||
Collection ownCollection = item.getOwningCollection();
|
Collection ownCollection = item.getOwningCollection();
|
||||||
if (ownCollection != null) {
|
if (ownCollection != null) {
|
||||||
return ownCollection;
|
return ownCollection;
|
||||||
} else {
|
|
||||||
InProgressSubmission inprogress = ContentServiceFactory.getInstance().getWorkspaceItemService()
|
|
||||||
.findByItem(context,
|
|
||||||
item);
|
|
||||||
if (inprogress == null) {
|
|
||||||
inprogress = WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(context, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inprogress != null) {
|
|
||||||
return inprogress.getCollection();
|
|
||||||
}
|
|
||||||
// is a template item?
|
|
||||||
return item.getTemplateItemOf();
|
|
||||||
}
|
}
|
||||||
|
InProgressSubmission inprogress = ContentServiceFactory.getInstance().getWorkspaceItemService()
|
||||||
|
.findByItem(context,
|
||||||
|
item);
|
||||||
|
if (inprogress == null) {
|
||||||
|
inprogress = WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(context, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inprogress != null) {
|
||||||
|
return inprogress.getCollection();
|
||||||
|
}
|
||||||
|
// is a template item?
|
||||||
|
return item.getTemplateItemOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1611,9 +1622,8 @@ prevent the generation of resource policy entry values with null dspace_object a
|
|||||||
try {
|
try {
|
||||||
if (StringUtils.isNumeric(id)) {
|
if (StringUtils.isNumeric(id)) {
|
||||||
return findByLegacyId(context, Integer.parseInt(id));
|
return findByLegacyId(context, Integer.parseInt(id));
|
||||||
} else {
|
|
||||||
return find(context, UUID.fromString(id));
|
|
||||||
}
|
}
|
||||||
|
return find(context, UUID.fromString(id));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// Not a valid legacy ID or valid UUID
|
// Not a valid legacy ID or valid UUID
|
||||||
return null;
|
return null;
|
||||||
|
@@ -11,11 +11,13 @@ import java.sql.SQLException;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.dspace.content.Collection;
|
import org.dspace.content.Collection;
|
||||||
import org.dspace.content.Community;
|
import org.dspace.content.Community;
|
||||||
import org.dspace.content.Item;
|
import org.dspace.content.Item;
|
||||||
import org.dspace.content.MetadataField;
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.contentreport.QueryPredicate;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
import org.dspace.eperson.EPerson;
|
import org.dspace.eperson.EPerson;
|
||||||
|
|
||||||
@@ -27,12 +29,11 @@ import org.dspace.eperson.EPerson;
|
|||||||
* @author kevinvandevelde at atmire.com
|
* @author kevinvandevelde at atmire.com
|
||||||
*/
|
*/
|
||||||
public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
||||||
public Iterator<Item> findAll(Context context, boolean archived) throws SQLException;
|
Iterator<Item> findAll(Context context, boolean archived) throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findAll(Context context, boolean archived, int limit, int offset) throws SQLException;
|
Iterator<Item> findAll(Context context, boolean archived, int limit, int offset) throws SQLException;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated Iterator<Item> findAll(Context context, boolean archived, boolean withdrawn) throws SQLException;
|
||||||
public Iterator<Item> findAll(Context context, boolean archived, boolean withdrawn) throws SQLException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all items that are:
|
* Find all items that are:
|
||||||
@@ -45,7 +46,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return iterator over all regular items.
|
* @return iterator over all regular items.
|
||||||
* @throws SQLException if database error.
|
* @throws SQLException if database error.
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAllRegularItems(Context context) throws SQLException;
|
Iterator<Item> findAllRegularItems(Context context) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all Items modified since a Date.
|
* Find all Items modified since a Date.
|
||||||
@@ -55,10 +56,10 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return iterator over items
|
* @return iterator over items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByLastModifiedSince(Context context, Date since)
|
Iterator<Item> findByLastModifiedSince(Context context, Date since)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson) throws SQLException;
|
Iterator<Item> findBySubmitter(Context context, EPerson eperson) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all the items by a given submitter. The order is
|
* Find all the items by a given submitter. The order is
|
||||||
@@ -70,19 +71,40 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return an iterator over the items submitted by eperson
|
* @return an iterator over the items submitted by eperson
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
|
Iterator<Item> findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findByMetadataField(Context context, MetadataField metadataField, String value,
|
Iterator<Item> findByMetadataField(Context context, MetadataField metadataField, String value,
|
||||||
boolean inArchive) throws SQLException;
|
boolean inArchive) throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findByAuthorityValue(Context context, MetadataField metadataField, String authority,
|
/**
|
||||||
|
* Returns all the Items that belong to the specified aollections (if any)
|
||||||
|
* and match the provided predicates.
|
||||||
|
* @param context The relevant DSpace context
|
||||||
|
* @param queryPredicates List of predicates that returned items are required to match
|
||||||
|
* @param collectionUuids UUIDs of the collections to search.
|
||||||
|
* If none are provided, the entire repository will be searched.
|
||||||
|
* @param regexClause Syntactic expression used to query the database using a regular expression
|
||||||
|
* (e.g.: "text_value ~ ?")
|
||||||
|
* @param offset The offset for the query
|
||||||
|
* @param limit Maximum number of items to return
|
||||||
|
* @return A list containing the items that match the provided criteria
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
List<Item> findByMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, String regexClause,
|
||||||
|
long offset, int limit) throws SQLException;
|
||||||
|
|
||||||
|
long countForMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, String regexClause) throws SQLException;
|
||||||
|
|
||||||
|
Iterator<Item> findByAuthorityValue(Context context, MetadataField metadataField, String authority,
|
||||||
boolean inArchive) throws SQLException;
|
boolean inArchive) throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findArchivedByCollection(Context context, Collection collection, Integer limit,
|
Iterator<Item> findArchivedByCollection(Context context, Collection collection, Integer limit,
|
||||||
Integer offset) throws SQLException;
|
Integer offset) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +117,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return An iterator containing the items for which the constraints hold true
|
* @return An iterator containing the items for which the constraints hold true
|
||||||
* @throws SQLException If something goes wrong
|
* @throws SQLException If something goes wrong
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit,
|
Iterator<Item> findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit,
|
||||||
Integer offset) throws SQLException;
|
Integer offset) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,11 +128,11 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return The total amount of items that fit the constraints
|
* @return The total amount of items that fit the constraints
|
||||||
* @throws SQLException If something goes wrong
|
* @throws SQLException If something goes wrong
|
||||||
*/
|
*/
|
||||||
public int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException;
|
int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findAllByCollection(Context context, Collection collection) throws SQLException;
|
Iterator<Item> findAllByCollection(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
public Iterator<Item> findAllByCollection(Context context, Collection collection, Integer limit, Integer offset)
|
Iterator<Item> findAllByCollection(Context context, Collection collection, Integer limit, Integer offset)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +145,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return item count
|
* @return item count
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn)
|
int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,7 +161,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return item count
|
* @return item count
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countItems(Context context, List<Collection> collections, boolean includeArchived,
|
int countItems(Context context, List<Collection> collections, boolean includeArchived,
|
||||||
boolean includeWithdrawn) throws SQLException;
|
boolean includeWithdrawn) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,7 +175,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return iterator over items
|
* @return iterator over items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAll(Context context, boolean archived,
|
Iterator<Item> findAll(Context context, boolean archived,
|
||||||
boolean withdrawn, boolean discoverable, Date lastModified)
|
boolean withdrawn, boolean discoverable, Date lastModified)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
@@ -187,7 +209,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO<Item> {
|
|||||||
* @return count of items
|
* @return count of items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn)
|
int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,25 +8,36 @@
|
|||||||
package org.dspace.content.dao.impl;
|
package org.dspace.content.dao.impl;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import javax.persistence.TemporalType;
|
import javax.persistence.TemporalType;
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder.In;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
import javax.persistence.criteria.Subquery;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.dspace.content.Collection;
|
import org.dspace.content.Collection;
|
||||||
|
import org.dspace.content.DSpaceObject_;
|
||||||
import org.dspace.content.Item;
|
import org.dspace.content.Item;
|
||||||
import org.dspace.content.Item_;
|
import org.dspace.content.Item_;
|
||||||
import org.dspace.content.MetadataField;
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.content.MetadataValue;
|
||||||
|
import org.dspace.content.MetadataValue_;
|
||||||
import org.dspace.content.dao.ItemDAO;
|
import org.dspace.content.dao.ItemDAO;
|
||||||
|
import org.dspace.contentreport.QueryOperator;
|
||||||
|
import org.dspace.contentreport.QueryPredicate;
|
||||||
import org.dspace.core.AbstractHibernateDSODAO;
|
import org.dspace.core.AbstractHibernateDSODAO;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
import org.dspace.eperson.EPerson;
|
import org.dspace.eperson.EPerson;
|
||||||
|
import org.dspace.util.JpaCriteriaBuilderKit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hibernate implementation of the Database Access Object interface class for the Item object.
|
* Hibernate implementation of the Database Access Object interface class for the Item object.
|
||||||
@@ -39,7 +50,6 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO<Item> implements ItemDA
|
|||||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemDAOImpl.class);
|
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemDAOImpl.class);
|
||||||
|
|
||||||
protected ItemDAOImpl() {
|
protected ItemDAOImpl() {
|
||||||
super();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -163,6 +173,105 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO<Item> implements ItemDA
|
|||||||
return iterate(query);
|
return iterate(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Item> findByMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, String regexClause, long offset, int limit) throws SQLException {
|
||||||
|
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||||
|
CriteriaQuery<Item> criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class);
|
||||||
|
Root<Item> itemRoot = criteriaQuery.from(Item.class);
|
||||||
|
criteriaQuery.select(itemRoot);
|
||||||
|
List<Predicate> predicates = toPredicates(criteriaBuilder, criteriaQuery, itemRoot,
|
||||||
|
queryPredicates, collectionUuids, regexClause);
|
||||||
|
criteriaQuery.where(criteriaBuilder.and(predicates.stream().toArray(Predicate[]::new)));
|
||||||
|
criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(DSpaceObject_.id)));
|
||||||
|
criteriaQuery.groupBy(itemRoot.get(DSpaceObject_.id));
|
||||||
|
try {
|
||||||
|
return list(context, criteriaQuery, false, Item.class, limit, (int) offset);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long countForMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, String regexClause) throws SQLException {
|
||||||
|
// Build the query infrastructure
|
||||||
|
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||||
|
CriteriaQuery<Item> criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class);
|
||||||
|
// Select
|
||||||
|
Root<Item> itemRoot = criteriaQuery.from(Item.class);
|
||||||
|
// Apply the selected predicates
|
||||||
|
List<Predicate> predicates = toPredicates(criteriaBuilder, criteriaQuery, itemRoot,
|
||||||
|
queryPredicates, collectionUuids, regexClause);
|
||||||
|
criteriaQuery.where(criteriaBuilder.and(predicates.stream().toArray(Predicate[]::new)));
|
||||||
|
// Execute the query
|
||||||
|
return countLong(context, criteriaQuery, criteriaBuilder, itemRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<Predicate> toPredicates(CriteriaBuilder criteriaBuilder, CriteriaQuery<T> query,
|
||||||
|
Root<Item> root, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, String regexClause) {
|
||||||
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!collectionUuids.isEmpty()) {
|
||||||
|
Subquery<Collection> scollQuery = query.subquery(Collection.class);
|
||||||
|
Root<Collection> collRoot = scollQuery.from(Collection.class);
|
||||||
|
In<UUID> inColls = criteriaBuilder.in(collRoot.get(DSpaceObject_.ID));
|
||||||
|
collectionUuids.forEach(inColls::value);
|
||||||
|
scollQuery.select(collRoot.get(DSpaceObject_.ID))
|
||||||
|
.where(criteriaBuilder.and(
|
||||||
|
criteriaBuilder.equal(collRoot.get(DSpaceObject_.ID),
|
||||||
|
root.get(Item_.OWNING_COLLECTION).get(DSpaceObject_.ID)),
|
||||||
|
collRoot.get(DSpaceObject_.ID).in(collectionUuids)
|
||||||
|
));
|
||||||
|
predicates.add(criteriaBuilder.exists(scollQuery));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < queryPredicates.size(); i++) {
|
||||||
|
QueryPredicate predicate = queryPredicates.get(i);
|
||||||
|
QueryOperator op = predicate.getOperator();
|
||||||
|
if (op == null) {
|
||||||
|
log.warn("Skipping Invalid Operator: null");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.getUsesRegex()) {
|
||||||
|
if (regexClause.isEmpty()) {
|
||||||
|
log.warn("Skipping Unsupported Regex Operator: " + op);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Predicate> mvPredicates = new ArrayList<>();
|
||||||
|
Subquery<MetadataValue> mvQuery = query.subquery(MetadataValue.class);
|
||||||
|
Root<MetadataValue> mvRoot = mvQuery.from(MetadataValue.class);
|
||||||
|
mvPredicates.add(criteriaBuilder.equal(
|
||||||
|
mvRoot.get(MetadataValue_.D_SPACE_OBJECT), root.get(DSpaceObject_.ID)));
|
||||||
|
|
||||||
|
if (!predicate.getFields().isEmpty()) {
|
||||||
|
In<MetadataField> inFields = criteriaBuilder.in(mvRoot.get(MetadataValue_.METADATA_FIELD));
|
||||||
|
predicate.getFields().forEach(inFields::value);
|
||||||
|
mvPredicates.add(inFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
JpaCriteriaBuilderKit<MetadataValue> jpaKit = new JpaCriteriaBuilderKit<>(criteriaBuilder, mvQuery, mvRoot);
|
||||||
|
mvPredicates.add(op.buildJpaPredicate(predicate.getValue(), regexClause, jpaKit));
|
||||||
|
|
||||||
|
mvQuery.select(mvRoot.get(MetadataValue_.D_SPACE_OBJECT))
|
||||||
|
.where(mvPredicates.stream().toArray(Predicate[]::new));
|
||||||
|
|
||||||
|
if (op.getNegate()) {
|
||||||
|
predicates.add(criteriaBuilder.not(criteriaBuilder.exists(mvQuery)));
|
||||||
|
} else {
|
||||||
|
predicates.add(criteriaBuilder.exists(mvQuery));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(String.format("Running custom query with %d filters", queryPredicates.size()));
|
||||||
|
return predicates;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Item> findByAuthorityValue(Context context, MetadataField metadataField, String authority,
|
public Iterator<Item> findByAuthorityValue(Context context, MetadataField metadataField, String authority,
|
||||||
boolean inArchive) throws SQLException {
|
boolean inArchive) throws SQLException {
|
||||||
@@ -197,22 +306,22 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO<Item> implements ItemDA
|
|||||||
public Iterator<Item> findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit,
|
public Iterator<Item> findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit,
|
||||||
Integer offset) throws SQLException {
|
Integer offset) throws SQLException {
|
||||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class);
|
CriteriaQuery<Item> criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class);
|
||||||
Root<Item> itemRoot = criteriaQuery.from(Item.class);
|
Root<Item> itemRoot = criteriaQuery.from(Item.class);
|
||||||
criteriaQuery.select(itemRoot);
|
criteriaQuery.select(itemRoot);
|
||||||
criteriaQuery.where(criteriaBuilder.and(
|
criteriaQuery.where(criteriaBuilder.and(
|
||||||
criteriaBuilder.notEqual(itemRoot.get(Item_.owningCollection), collection),
|
criteriaBuilder.notEqual(itemRoot.get(Item_.owningCollection), collection),
|
||||||
criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)),
|
criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)),
|
||||||
criteriaBuilder.isTrue(itemRoot.get(Item_.inArchive))));
|
criteriaBuilder.isTrue(itemRoot.get(Item_.inArchive))));
|
||||||
criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(Item_.id)));
|
criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(DSpaceObject_.id)));
|
||||||
criteriaQuery.groupBy(itemRoot.get(Item_.id));
|
criteriaQuery.groupBy(itemRoot.get(DSpaceObject_.id));
|
||||||
return list(context, criteriaQuery, false, Item.class, limit, offset).iterator();
|
return list(context, criteriaQuery, false, Item.class, limit, offset).iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException {
|
public int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException {
|
||||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class);
|
CriteriaQuery<Item> criteriaQuery = getCriteriaQuery(criteriaBuilder, Item.class);
|
||||||
Root<Item> itemRoot = criteriaQuery.from(Item.class);
|
Root<Item> itemRoot = criteriaQuery.from(Item.class);
|
||||||
criteriaQuery.select(itemRoot);
|
criteriaQuery.select(itemRoot);
|
||||||
criteriaQuery.where(criteriaBuilder.and(
|
criteriaQuery.where(criteriaBuilder.and(
|
||||||
|
@@ -26,6 +26,7 @@ import org.dspace.content.Item;
|
|||||||
import org.dspace.content.MetadataValue;
|
import org.dspace.content.MetadataValue;
|
||||||
import org.dspace.content.Thumbnail;
|
import org.dspace.content.Thumbnail;
|
||||||
import org.dspace.content.WorkspaceItem;
|
import org.dspace.content.WorkspaceItem;
|
||||||
|
import org.dspace.contentreport.QueryPredicate;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
import org.dspace.discovery.SearchServiceException;
|
import org.dspace.discovery.SearchServiceException;
|
||||||
import org.dspace.eperson.EPerson;
|
import org.dspace.eperson.EPerson;
|
||||||
@@ -41,7 +42,7 @@ import org.dspace.eperson.Group;
|
|||||||
public interface ItemService
|
public interface ItemService
|
||||||
extends DSpaceObjectService<Item>, DSpaceObjectLegacySupportService<Item> {
|
extends DSpaceObjectService<Item>, DSpaceObjectLegacySupportService<Item> {
|
||||||
|
|
||||||
public Thumbnail getThumbnail(Context context, Item item, boolean requireOriginal) throws SQLException;
|
Thumbnail getThumbnail(Context context, Item item, boolean requireOriginal) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new item, with a new internal ID. Authorization is done
|
* Create a new item, with a new internal ID. Authorization is done
|
||||||
@@ -53,7 +54,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException;
|
Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new item, with a provided ID. Authorisation is done
|
* Create a new item, with a provided ID. Authorisation is done
|
||||||
@@ -66,7 +67,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException;
|
Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an empty template item for this collection. If one already exists,
|
* Create an empty template item for this collection. If one already exists,
|
||||||
@@ -80,7 +81,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public Item createTemplateItem(Context context, Collection collection) throws SQLException, AuthorizeException;
|
Item createTemplateItem(Context context, Collection collection) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the items in the archive. Only items with the "in archive" flag
|
* Get all the items in the archive. Only items with the "in archive" flag
|
||||||
@@ -90,7 +91,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the archive.
|
* @return an iterator over the items in the archive.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAll(Context context) throws SQLException;
|
Iterator<Item> findAll(Context context) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the items in the archive. Only items with the "in archive" flag
|
* Get all the items in the archive. Only items with the "in archive" flag
|
||||||
@@ -102,7 +103,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the archive.
|
* @return an iterator over the items in the archive.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAll(Context context, Integer limit, Integer offset) throws SQLException;
|
Iterator<Item> findAll(Context context, Integer limit, Integer offset) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all "final" items in the archive, both archived ("in archive" flag) or
|
* Get all "final" items in the archive, both archived ("in archive" flag) or
|
||||||
@@ -112,8 +113,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the archive.
|
* @return an iterator over the items in the archive.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated Iterator<Item> findAllUnfiltered(Context context) throws SQLException;
|
||||||
public Iterator<Item> findAllUnfiltered(Context context) throws SQLException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all items that are:
|
* Find all items that are:
|
||||||
@@ -126,7 +126,7 @@ public interface ItemService
|
|||||||
* @return iterator over all regular items.
|
* @return iterator over all regular items.
|
||||||
* @throws SQLException if database error.
|
* @throws SQLException if database error.
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAllRegularItems(Context context) throws SQLException;
|
Iterator<Item> findAllRegularItems(Context context) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all the items in the archive by a given submitter. The order is
|
* Find all the items in the archive by a given submitter. The order is
|
||||||
@@ -137,7 +137,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items submitted by eperson
|
* @return an iterator over the items submitted by eperson
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson)
|
Iterator<Item> findBySubmitter(Context context, EPerson eperson)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +152,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items submitted by eperson
|
* @return an iterator over the items submitted by eperson
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
Iterator<Item> findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +164,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items submitted by eperson
|
* @return an iterator over the items submitted by eperson
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit)
|
Iterator<Item> findBySubmitterDateSorted(Context context, EPerson eperson, Integer limit)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,7 +175,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByCollection(Context context, Collection collection) throws SQLException;
|
Iterator<Item> findByCollection(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the archived items in this collection. The order is indeterminate.
|
* Get all the archived items in this collection. The order is indeterminate.
|
||||||
@@ -187,7 +187,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByCollection(Context context, Collection collection, Integer limit, Integer offset)
|
Iterator<Item> findByCollection(Context context, Collection collection, Integer limit, Integer offset)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,7 +200,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByCollectionMapping(Context context, Collection collection, Integer limit, Integer offset)
|
Iterator<Item> findByCollectionMapping(Context context, Collection collection, Integer limit, Integer offset)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,7 +211,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countByCollectionMapping(Context context, Collection collection) throws SQLException;
|
int countByCollectionMapping(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the items (including private and withdrawn) in this collection. The order is indeterminate.
|
* Get all the items (including private and withdrawn) in this collection. The order is indeterminate.
|
||||||
@@ -223,7 +223,7 @@ public interface ItemService
|
|||||||
* @param offset offset value
|
* @param offset offset value
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAllByCollection(Context context, Collection collection, Integer limit, Integer offset)
|
Iterator<Item> findAllByCollection(Context context, Collection collection, Integer limit, Integer offset)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,7 +234,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findInArchiveOrWithdrawnDiscoverableModifiedSince(Context context, Date since)
|
Iterator<Item> findInArchiveOrWithdrawnDiscoverableModifiedSince(Context context, Date since)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,7 +244,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Context context, Date since)
|
Iterator<Item> findInArchiveOrWithdrawnNonDiscoverableModifiedSince(Context context, Date since)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,7 +255,7 @@ public interface ItemService
|
|||||||
* @return an iterator over the items in the collection.
|
* @return an iterator over the items in the collection.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findAllByCollection(Context context, Collection collection) throws SQLException;
|
Iterator<Item> findAllByCollection(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See whether this Item is contained by a given Collection.
|
* See whether this Item is contained by a given Collection.
|
||||||
@@ -265,7 +265,7 @@ public interface ItemService
|
|||||||
* @return true if {@code collection} contains this Item.
|
* @return true if {@code collection} contains this Item.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public boolean isIn(Item item, Collection collection) throws SQLException;
|
boolean isIn(Item item, Collection collection) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the communities this item is in. Returns an unordered array of the
|
* Get the communities this item is in. Returns an unordered array of the
|
||||||
@@ -277,7 +277,7 @@ public interface ItemService
|
|||||||
* @return the communities this item is in.
|
* @return the communities this item is in.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public List<Community> getCommunities(Context context, Item item) throws SQLException;
|
List<Community> getCommunities(Context context, Item item) throws SQLException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -288,7 +288,7 @@ public interface ItemService
|
|||||||
* @return the bundles in an unordered array
|
* @return the bundles in an unordered array
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public List<Bundle> getBundles(Item item, String name) throws SQLException;
|
List<Bundle> getBundles(Item item, String name) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an existing bundle to this item. This has immediate effect.
|
* Add an existing bundle to this item. This has immediate effect.
|
||||||
@@ -299,7 +299,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void addBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException;
|
void addBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a bundle. This may result in the bundle being deleted, if the
|
* Remove a bundle. This may result in the bundle being deleted, if the
|
||||||
@@ -312,7 +312,7 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public void removeBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException,
|
void removeBundle(Context context, Item item, Bundle bundle) throws SQLException, AuthorizeException,
|
||||||
IOException;
|
IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,7 +325,7 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public void removeAllBundles(Context context, Item item) throws AuthorizeException, SQLException, IOException;
|
void removeAllBundles(Context context, Item item) throws AuthorizeException, SQLException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a single bitstream in a new bundle. Provided as a convenience
|
* Create a single bitstream in a new bundle. Provided as a convenience
|
||||||
@@ -340,7 +340,7 @@ public interface ItemService
|
|||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Bitstream createSingleBitstream(Context context, InputStream is, Item item, String name)
|
Bitstream createSingleBitstream(Context context, InputStream is, Item item, String name)
|
||||||
throws AuthorizeException, IOException, SQLException;
|
throws AuthorizeException, IOException, SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,7 +354,7 @@ public interface ItemService
|
|||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Bitstream createSingleBitstream(Context context, InputStream is, Item item)
|
Bitstream createSingleBitstream(Context context, InputStream is, Item item)
|
||||||
throws AuthorizeException, IOException, SQLException;
|
throws AuthorizeException, IOException, SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -367,7 +367,7 @@ public interface ItemService
|
|||||||
* @return non-internal bitstreams.
|
* @return non-internal bitstreams.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public List<Bitstream> getNonInternalBitstreams(Context context, Item item) throws SQLException;
|
List<Bitstream> getNonInternalBitstreams(Context context, Item item) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove just the DSpace license from an item This is useful to update the
|
* Remove just the DSpace license from an item This is useful to update the
|
||||||
@@ -382,7 +382,7 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public void removeDSpaceLicense(Context context, Item item) throws SQLException, AuthorizeException,
|
void removeDSpaceLicense(Context context, Item item) throws SQLException, AuthorizeException,
|
||||||
IOException;
|
IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -394,7 +394,7 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public void removeLicenses(Context context, Item item) throws SQLException, AuthorizeException, IOException;
|
void removeLicenses(Context context, Item item) throws SQLException, AuthorizeException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Withdraw the item from the archive. It is kept in place, and the content
|
* Withdraw the item from the archive. It is kept in place, and the content
|
||||||
@@ -405,7 +405,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void withdraw(Context context, Item item) throws SQLException, AuthorizeException;
|
void withdraw(Context context, Item item) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -416,7 +416,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void reinstate(Context context, Item item) throws SQLException, AuthorizeException;
|
void reinstate(Context context, Item item) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if this Collection 'owns' this item
|
* Return true if this Collection 'owns' this item
|
||||||
@@ -425,7 +425,7 @@ public interface ItemService
|
|||||||
* @param collection Collection
|
* @param collection Collection
|
||||||
* @return true if this Collection owns this item
|
* @return true if this Collection owns this item
|
||||||
*/
|
*/
|
||||||
public boolean isOwningCollection(Item item, Collection collection);
|
boolean isOwningCollection(Item item, Collection collection);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remove all of the policies for item and replace them with a new list of
|
* remove all of the policies for item and replace them with a new list of
|
||||||
@@ -439,7 +439,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void replaceAllItemPolicies(Context context, Item item, List<ResourcePolicy> newpolicies)
|
void replaceAllItemPolicies(Context context, Item item, List<ResourcePolicy> newpolicies)
|
||||||
throws SQLException,
|
throws SQLException,
|
||||||
AuthorizeException;
|
AuthorizeException;
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void replaceAllBitstreamPolicies(Context context, Item item, List<ResourcePolicy> newpolicies)
|
void replaceAllBitstreamPolicies(Context context, Item item, List<ResourcePolicy> newpolicies)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
|
|
||||||
@@ -469,7 +469,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException;
|
void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all policies on an item and its contents, and replace them with
|
* Remove all policies on an item and its contents, and replace them with
|
||||||
@@ -484,7 +484,7 @@ public interface ItemService
|
|||||||
* draconian, but default policies must be enforced.
|
* draconian, but default policies must be enforced.
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection)
|
void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection)
|
||||||
throws java.sql.SQLException, AuthorizeException;
|
throws java.sql.SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -503,7 +503,7 @@ public interface ItemService
|
|||||||
* draconian, but default policies must be enforced.
|
* draconian, but default policies must be enforced.
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection,
|
void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection,
|
||||||
boolean overrideItemReadPolicies)
|
boolean overrideItemReadPolicies)
|
||||||
throws java.sql.SQLException, AuthorizeException;
|
throws java.sql.SQLException, AuthorizeException;
|
||||||
|
|
||||||
@@ -516,14 +516,14 @@ public interface ItemService
|
|||||||
* already applied to the bundle/bitstream. Collection's policies are inherited
|
* already applied to the bundle/bitstream. Collection's policies are inherited
|
||||||
* if there are no other policies defined or if the append mode is defined by
|
* if there are no other policies defined or if the append mode is defined by
|
||||||
* the configuration via the core.authorization.installitem.inheritance-read.append-mode property
|
* the configuration via the core.authorization.installitem.inheritance-read.append-mode property
|
||||||
*
|
*
|
||||||
* @param context DSpace context object
|
* @param context DSpace context object
|
||||||
* @param item Item to adjust policies on
|
* @param item Item to adjust policies on
|
||||||
* @param collection Collection
|
* @param collection Collection
|
||||||
* @throws SQLException If database error
|
* @throws SQLException If database error
|
||||||
* @throws AuthorizeException If authorization error
|
* @throws AuthorizeException If authorization error
|
||||||
*/
|
*/
|
||||||
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection)
|
void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -544,7 +544,7 @@ public interface ItemService
|
|||||||
* @throws SQLException If database error
|
* @throws SQLException If database error
|
||||||
* @throws AuthorizeException If authorization error
|
* @throws AuthorizeException If authorization error
|
||||||
*/
|
*/
|
||||||
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection,
|
void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection,
|
||||||
boolean replaceReadRPWithCollectionRP)
|
boolean replaceReadRPWithCollectionRP)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
@@ -565,7 +565,7 @@ public interface ItemService
|
|||||||
* @throws SQLException If database error
|
* @throws SQLException If database error
|
||||||
* @throws AuthorizeException If authorization error
|
* @throws AuthorizeException If authorization error
|
||||||
*/
|
*/
|
||||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream)
|
void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -587,7 +587,7 @@ public interface ItemService
|
|||||||
* @throws SQLException If database error
|
* @throws SQLException If database error
|
||||||
* @throws AuthorizeException If authorization error
|
* @throws AuthorizeException If authorization error
|
||||||
*/
|
*/
|
||||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream,
|
void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream,
|
||||||
boolean replaceReadRPWithCollectionRP)
|
boolean replaceReadRPWithCollectionRP)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
@@ -599,14 +599,14 @@ public interface ItemService
|
|||||||
* inherited as appropriate. Collection's policies are inherited if there are no
|
* inherited as appropriate. Collection's policies are inherited if there are no
|
||||||
* other policies defined or if the append mode is defined by the configuration
|
* other policies defined or if the append mode is defined by the configuration
|
||||||
* via the core.authorization.installitem.inheritance-read.append-mode property
|
* via the core.authorization.installitem.inheritance-read.append-mode property
|
||||||
*
|
*
|
||||||
* @param context DSpace context object
|
* @param context DSpace context object
|
||||||
* @param item Item to adjust policies on
|
* @param item Item to adjust policies on
|
||||||
* @param collection Collection
|
* @param collection Collection
|
||||||
* @throws SQLException If database error
|
* @throws SQLException If database error
|
||||||
* @throws AuthorizeException If authorization error
|
* @throws AuthorizeException If authorization error
|
||||||
*/
|
*/
|
||||||
public void adjustItemPolicies(Context context, Item item, Collection collection)
|
void adjustItemPolicies(Context context, Item item, Collection collection)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -625,7 +625,7 @@ public interface ItemService
|
|||||||
* @throws SQLException If database error
|
* @throws SQLException If database error
|
||||||
* @throws AuthorizeException If authorization error
|
* @throws AuthorizeException If authorization error
|
||||||
*/
|
*/
|
||||||
public void adjustItemPolicies(Context context, Item item, Collection collection,
|
void adjustItemPolicies(Context context, Item item, Collection collection,
|
||||||
boolean replaceReadRPWithCollectionRP)
|
boolean replaceReadRPWithCollectionRP)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
@@ -640,7 +640,7 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public void move(Context context, Item item, Collection from, Collection to)
|
void move(Context context, Item item, Collection from, Collection to)
|
||||||
throws SQLException, AuthorizeException, IOException;
|
throws SQLException, AuthorizeException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -655,7 +655,7 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public void move(Context context, Item item, Collection from, Collection to, boolean inheritDefaultPolicies)
|
void move(Context context, Item item, Collection from, Collection to, boolean inheritDefaultPolicies)
|
||||||
throws SQLException, AuthorizeException, IOException;
|
throws SQLException, AuthorizeException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -666,7 +666,7 @@ public interface ItemService
|
|||||||
* bitstreams inside
|
* bitstreams inside
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public boolean hasUploadedFiles(Item item) throws SQLException;
|
boolean hasUploadedFiles(Item item) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the collections this item is not in.
|
* Get the collections this item is not in.
|
||||||
@@ -676,7 +676,7 @@ public interface ItemService
|
|||||||
* @return the collections this item is not in, if any.
|
* @return the collections this item is not in, if any.
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public List<Collection> getCollectionsNotLinked(Context context, Item item) throws SQLException;
|
List<Collection> getCollectionsNotLinked(Context context, Item item) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return TRUE if context's user can edit item, false otherwise
|
* return TRUE if context's user can edit item, false otherwise
|
||||||
@@ -686,7 +686,7 @@ public interface ItemService
|
|||||||
* @return boolean true = current user can edit item
|
* @return boolean true = current user can edit item
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public boolean canEdit(Context context, Item item) throws java.sql.SQLException;
|
boolean canEdit(Context context, Item item) throws java.sql.SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return TRUE if context's user can create new version of the item, false
|
* return TRUE if context's user can create new version of the item, false
|
||||||
@@ -697,7 +697,7 @@ public interface ItemService
|
|||||||
* @return boolean true = current user can create new version of the item
|
* @return boolean true = current user can create new version of the item
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public boolean canCreateNewVersion(Context context, Item item) throws SQLException;
|
boolean canCreateNewVersion(Context context, Item item) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an iterator of in archive items possessing the passed metadata field, or only
|
* Returns an iterator of in archive items possessing the passed metadata field, or only
|
||||||
@@ -712,7 +712,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findArchivedByMetadataField(Context context, String schema,
|
Iterator<Item> findArchivedByMetadataField(Context context, String schema,
|
||||||
String element, String qualifier,
|
String element, String qualifier,
|
||||||
String value) throws SQLException, AuthorizeException;
|
String value) throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
@@ -727,7 +727,7 @@ public interface ItemService
|
|||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findArchivedByMetadataField(Context context, String metadataField, String value)
|
Iterator<Item> findArchivedByMetadataField(Context context, String metadataField, String value)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -744,10 +744,42 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByMetadataField(Context context,
|
Iterator<Item> findByMetadataField(Context context,
|
||||||
String schema, String element, String qualifier, String value)
|
String schema, String element, String qualifier, String value)
|
||||||
throws SQLException, AuthorizeException, IOException;
|
throws SQLException, AuthorizeException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of items that match the given predicates, within the
|
||||||
|
* specified collections, if any. This querying method is used by the
|
||||||
|
* Filtered Items report functionality.
|
||||||
|
* @param context DSpace context object
|
||||||
|
* @param queryPredicates metadata field predicates
|
||||||
|
* @param collectionUuids UUIDs of the collections to search
|
||||||
|
* @param offset position in the list to start returning items
|
||||||
|
* @param limit maximum number of items to return
|
||||||
|
* @return a list of matching items in the specified collections,
|
||||||
|
* or in any collection if no collection UUIDs are provided
|
||||||
|
* @throws SQLException if a database error occurs
|
||||||
|
*/
|
||||||
|
List<Item> findByMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids, long offset, int limit)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of items that match the given predicates, within the
|
||||||
|
* specified collections, if any. This querying method is used for pagination by the
|
||||||
|
* Filtered Items report functionality.
|
||||||
|
* @param context DSpace context object
|
||||||
|
* @param queryPredicates metadata field predicates
|
||||||
|
* @param collectionUuids UUIDs of the collections to search
|
||||||
|
* @return the total number of matching items in the specified collections,
|
||||||
|
* or in any collection if no collection UUIDs are provided
|
||||||
|
* @throws SQLException if a database error occurs
|
||||||
|
*/
|
||||||
|
long countForMetadataQuery(Context context, List<QueryPredicate> queryPredicates,
|
||||||
|
List<UUID> collectionUuids)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all the items in the archive with a given authority key value
|
* Find all the items in the archive with a given authority key value
|
||||||
* in the indicated metadata field.
|
* in the indicated metadata field.
|
||||||
@@ -762,12 +794,12 @@ public interface ItemService
|
|||||||
* @throws AuthorizeException if authorization error
|
* @throws AuthorizeException if authorization error
|
||||||
* @throws IOException if IO error
|
* @throws IOException if IO error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByAuthorityValue(Context context,
|
Iterator<Item> findByAuthorityValue(Context context,
|
||||||
String schema, String element, String qualifier, String value)
|
String schema, String element, String qualifier, String value)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
|
|
||||||
public Iterator<Item> findByMetadataFieldAuthority(Context context, String mdString, String authority)
|
Iterator<Item> findByMetadataFieldAuthority(Context context, String mdString, String authority)
|
||||||
throws SQLException, AuthorizeException;
|
throws SQLException, AuthorizeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -779,7 +811,7 @@ public interface ItemService
|
|||||||
* @param item item
|
* @param item item
|
||||||
* @return true or false
|
* @return true or false
|
||||||
*/
|
*/
|
||||||
public boolean isItemListedForUser(Context context, Item item);
|
boolean isItemListedForUser(Context context, Item item);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* counts items in the given collection
|
* counts items in the given collection
|
||||||
@@ -789,7 +821,7 @@ public interface ItemService
|
|||||||
* @return total items
|
* @return total items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countItems(Context context, Collection collection) throws SQLException;
|
int countItems(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* counts all items in the given collection including withdrawn items
|
* counts all items in the given collection including withdrawn items
|
||||||
@@ -799,7 +831,7 @@ public interface ItemService
|
|||||||
* @return total items
|
* @return total items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countAllItems(Context context, Collection collection) throws SQLException;
|
int countAllItems(Context context, Collection collection) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all Items modified since a Date.
|
* Find all Items modified since a Date.
|
||||||
@@ -809,7 +841,7 @@ public interface ItemService
|
|||||||
* @return iterator over items
|
* @return iterator over items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public Iterator<Item> findByLastModifiedSince(Context context, Date last)
|
Iterator<Item> findByLastModifiedSince(Context context, Date last)
|
||||||
throws SQLException;
|
throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -820,7 +852,7 @@ public interface ItemService
|
|||||||
* @return total items
|
* @return total items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countItems(Context context, Community community) throws SQLException;
|
int countItems(Context context, Community community) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* counts all items in the given community including withdrawn
|
* counts all items in the given community including withdrawn
|
||||||
@@ -830,7 +862,7 @@ public interface ItemService
|
|||||||
* @return total items
|
* @return total items
|
||||||
* @throws SQLException if database error
|
* @throws SQLException if database error
|
||||||
*/
|
*/
|
||||||
public int countAllItems(Context context, Community community) throws SQLException;
|
int countAllItems(Context context, Community community) throws SQLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* counts all items
|
* counts all items
|
||||||
@@ -877,7 +909,7 @@ public interface ItemService
|
|||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
* @throws SearchServiceException
|
* @throws SearchServiceException
|
||||||
*/
|
*/
|
||||||
public List<Item> findItemsWithEdit(Context context, int offset, int limit)
|
List<Item> findItemsWithEdit(Context context, int offset, int limit)
|
||||||
throws SQLException, SearchServiceException;
|
throws SQLException, SearchServiceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -887,7 +919,7 @@ public interface ItemService
|
|||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
* @throws SearchServiceException
|
* @throws SearchServiceException
|
||||||
*/
|
*/
|
||||||
public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException;
|
int countItemsWithEdit(Context context) throws SQLException, SearchServiceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the supplied item is an inprogress submission
|
* Check if the supplied item is an inprogress submission
|
||||||
@@ -947,7 +979,7 @@ public interface ItemService
|
|||||||
* relationships.
|
* relationships.
|
||||||
* @return metadata fields that match the parameters
|
* @return metadata fields that match the parameters
|
||||||
*/
|
*/
|
||||||
public List<MetadataValue> getMetadata(Item item, String schema, String element, String qualifier,
|
List<MetadataValue> getMetadata(Item item, String schema, String element, String qualifier,
|
||||||
String lang, boolean enableVirtualMetadata);
|
String lang, boolean enableVirtualMetadata);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -955,7 +987,7 @@ public interface ItemService
|
|||||||
* @param item the item.
|
* @param item the item.
|
||||||
* @return the label of the entity type, taken from the item metadata, or null if not found.
|
* @return the label of the entity type, taken from the item metadata, or null if not found.
|
||||||
*/
|
*/
|
||||||
public String getEntityTypeLabel(Item item);
|
String getEntityTypeLabel(Item item);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the entity type of the given item.
|
* Retrieve the entity type of the given item.
|
||||||
@@ -963,6 +995,6 @@ public interface ItemService
|
|||||||
* @param item the item.
|
* @param item the item.
|
||||||
* @return the entity type of the given item, or null if not found.
|
* @return the entity type of the given item, or null if not found.
|
||||||
*/
|
*/
|
||||||
public EntityType getEntityType(Context context, Item item) throws SQLException;
|
EntityType getEntityType(Context context, Item item) throws SQLException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.content.Collection;
|
||||||
|
import org.dspace.content.Community;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.content.service.CollectionService;
|
||||||
|
import org.dspace.content.service.ItemService;
|
||||||
|
import org.dspace.content.service.MetadataFieldService;
|
||||||
|
import org.dspace.contentreport.service.ContentReportService;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
public class ContentReportServiceImpl implements ContentReportService {
|
||||||
|
|
||||||
|
private static final Logger log = org.apache.logging.log4j.LogManager
|
||||||
|
.getLogger(ContentReportServiceImpl.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected ConfigurationService configurationService;
|
||||||
|
@Autowired
|
||||||
|
private CollectionService collectionService;
|
||||||
|
@Autowired
|
||||||
|
private ItemService itemService;
|
||||||
|
@Autowired
|
||||||
|
private MetadataFieldService metadataFieldService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true<</code> if Content Reports are enabled.
|
||||||
|
* @return <code>true<</code> if Content Reports are enabled
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return configurationService.getBooleanProperty("contentreport.enable");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves item statistics per collection according to a set of Boolean filters.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param filters Set of filters
|
||||||
|
* @return a list of collections with the requested statistics for each of them
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<FilteredCollection> findFilteredCollections(Context context, java.util.Collection<Filter> filters) {
|
||||||
|
List<FilteredCollection> colls = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
List<Collection> collections = collectionService.findAll(context);
|
||||||
|
for (Collection collection : collections) {
|
||||||
|
FilteredCollection coll = new FilteredCollection();
|
||||||
|
coll.setHandle(collection.getHandle());
|
||||||
|
coll.setLabel(collection.getName());
|
||||||
|
Community community = collection.getCommunities().stream()
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (community != null) {
|
||||||
|
coll.setCommunityLabel(community.getName());
|
||||||
|
coll.setCommunityHandle(community.getHandle());
|
||||||
|
}
|
||||||
|
colls.add(coll);
|
||||||
|
|
||||||
|
Iterator<Item> items = itemService.findAllByCollection(context, collection);
|
||||||
|
int nbTotalItems = 0;
|
||||||
|
while (items.hasNext()) {
|
||||||
|
Item item = items.next();
|
||||||
|
nbTotalItems++;
|
||||||
|
boolean matchesAllFilters = true;
|
||||||
|
for (Filter filter : filters) {
|
||||||
|
if (filter.testItem(context, item)) {
|
||||||
|
coll.addValue(filter, 1);
|
||||||
|
} else {
|
||||||
|
// This ensures the requested filter is present in the collection record
|
||||||
|
// even when there are no matching items.
|
||||||
|
coll.addValue(filter, 0);
|
||||||
|
matchesAllFilters = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchesAllFilters) {
|
||||||
|
coll.addAllFiltersValue(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coll.setTotalItems(nbTotalItems);
|
||||||
|
coll.seal();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("SQLException trying to receive filtered collections statistics", e);
|
||||||
|
}
|
||||||
|
return colls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of items according to a set of criteria.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param query structured query to find items against
|
||||||
|
* @return a list of items filtered according to the provided query
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public FilteredItems findFilteredItems(Context context, FilteredItemsQuery query) {
|
||||||
|
FilteredItems report = new FilteredItems();
|
||||||
|
|
||||||
|
List<QueryPredicate> predicates = query.getQueryPredicates();
|
||||||
|
List<UUID> collectionUuids = getUuidsFromStrings(query.getCollections());
|
||||||
|
Set<Filter> filters = query.getFilters();
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Item> items = itemService.findByMetadataQuery(context, predicates, collectionUuids,
|
||||||
|
query.getOffset(), query.getPageLimit());
|
||||||
|
items.stream()
|
||||||
|
.filter(item -> filters.stream().allMatch(f -> f.testItem(context, item)))
|
||||||
|
.forEach(report::addItem);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
long count = itemService.countForMetadataQuery(context, predicates, collectionUuids);
|
||||||
|
report.setItemCount(count);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a metadata field name to a list of {@link MetadataField} instances
|
||||||
|
* (one if no wildcards are used, possibly more otherwise).
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param metadataField field to search for
|
||||||
|
* @return a corresponding list of {@link MetadataField} entries
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<MetadataField> getMetadataFields(org.dspace.core.Context context, String metadataField)
|
||||||
|
throws SQLException {
|
||||||
|
List<MetadataField> fields = new ArrayList<>();
|
||||||
|
if ("*".equals(metadataField)) {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
String schema = "";
|
||||||
|
String element = "";
|
||||||
|
String qualifier = null;
|
||||||
|
String[] parts = metadataField.split("\\.");
|
||||||
|
if (parts.length > 0) {
|
||||||
|
schema = parts[0];
|
||||||
|
}
|
||||||
|
if (parts.length > 1) {
|
||||||
|
element = parts[1];
|
||||||
|
}
|
||||||
|
if (parts.length > 2) {
|
||||||
|
qualifier = parts[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Item.ANY.equals(qualifier)) {
|
||||||
|
fields.addAll(metadataFieldService.findFieldsByElementNameUnqualified(context, schema, element));
|
||||||
|
} else {
|
||||||
|
MetadataField mf = metadataFieldService.findByElement(context, schema, element, qualifier);
|
||||||
|
if (mf != null) {
|
||||||
|
fields.add(mf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<UUID> getUuidsFromStrings(List<String> collSel) {
|
||||||
|
List<UUID> uuids = new ArrayList<>();
|
||||||
|
for (String s: collSel) {
|
||||||
|
try {
|
||||||
|
uuids.add(UUID.fromString(s));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("Invalid collection UUID: " + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
399
dspace-api/src/main/java/org/dspace/contentreport/Filter.java
Normal file
399
dspace-api/src/main/java/org/dspace/contentreport/Filter.java
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.authorize.factory.AuthorizeServiceFactory;
|
||||||
|
import org.dspace.authorize.service.AuthorizeService;
|
||||||
|
import org.dspace.content.Bundle;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.contentreport.ItemFilterUtil.BundleName;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available filters for the Filtered Collections and Filtered Items reports.
|
||||||
|
* In this enum, each item corresponds to a separate property, not values of
|
||||||
|
* a single property, hence the @JsonProperty applied to each of them.
|
||||||
|
* For each item, the annotation value is read through reflection and copied into
|
||||||
|
* the id property, which eliminates repetitions, hence reducing the risk or errors.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public enum Filter {
|
||||||
|
|
||||||
|
@JsonProperty("is_item")
|
||||||
|
IS_ITEM(FilterCategory.PROPERTY, (context, item) -> true),
|
||||||
|
@JsonProperty("is_withdrawn")
|
||||||
|
IS_WITHDRAWN(FilterCategory.PROPERTY, (context, item) -> item.isWithdrawn()),
|
||||||
|
@JsonProperty("is_not_withdrawn")
|
||||||
|
IS_NOT_WITHDRAWN(FilterCategory.PROPERTY, (context, item) -> !item.isWithdrawn()),
|
||||||
|
@JsonProperty("is_discoverable")
|
||||||
|
IS_DISCOVERABLE(FilterCategory.PROPERTY, (context, item) -> item.isDiscoverable()),
|
||||||
|
@JsonProperty("is_not_discoverable")
|
||||||
|
IS_NOT_DISCOVERABLE(FilterCategory.PROPERTY, (context, item) -> !item.isDiscoverable()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches items having multiple original bitstreams.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_multiple_originals")
|
||||||
|
HAS_MULTIPLE_ORIGINALS(FilterCategory.BITSTREAM, (context, item) ->
|
||||||
|
ItemFilterUtil.countOriginalBitstream(item) > 1),
|
||||||
|
/**
|
||||||
|
* Matches items having no original bitstreams.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_no_originals")
|
||||||
|
HAS_NO_ORIGINALS(FilterCategory.BITSTREAM, (context, item) -> ItemFilterUtil.countOriginalBitstream(item) == 0),
|
||||||
|
/**
|
||||||
|
* Matches items having exactly one original bitstream.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_one_original")
|
||||||
|
HAS_ONE_ORIGINAL(FilterCategory.BITSTREAM, (context, item) -> ItemFilterUtil.countOriginalBitstream(item) == 1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches items having bitstreams with a MIME type that matches one defined in the "rest.report-mime-document"
|
||||||
|
* configuration property.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_doc_original")
|
||||||
|
HAS_DOC_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) ->
|
||||||
|
ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()) > 0),
|
||||||
|
/**
|
||||||
|
* Matches items having bitstreams with a MIME type starting with "image" (e.g., image/jpeg, image/png).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_image_original")
|
||||||
|
HAS_IMAGE_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) ->
|
||||||
|
ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image") > 0),
|
||||||
|
/**
|
||||||
|
* Matches items having bitstreams with a MIME type other than document (cf. HAS_DOCUMENT above) or image
|
||||||
|
* (cf. HAS_IMAGE_ORIGINAL above).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_unsupp_type")
|
||||||
|
HAS_UNSUPPORTED_TYPE(FilterCategory.BITSTREAM_MIME, (context, item) -> {
|
||||||
|
int bitCount = ItemFilterUtil.countOriginalBitstream(item);
|
||||||
|
if (bitCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int docCount = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes());
|
||||||
|
int imgCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image");
|
||||||
|
return (bitCount - docCount - imgCount) > 0;
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having bitstreams of multiple types (document, image, other).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_mixed_original")
|
||||||
|
HAS_MIXED_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) -> {
|
||||||
|
int countBit = ItemFilterUtil.countOriginalBitstream(item);
|
||||||
|
if (countBit <= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int countDoc = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes());
|
||||||
|
if (countDoc > 0) {
|
||||||
|
return countDoc != countBit;
|
||||||
|
}
|
||||||
|
int countImg = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image");
|
||||||
|
if (countImg > 0) {
|
||||||
|
return countImg != countBit;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
@JsonProperty("has_pdf_original")
|
||||||
|
HAS_PDF_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) ->
|
||||||
|
ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.MIMES_PDF) > 0),
|
||||||
|
@JsonProperty("has_jpg_original")
|
||||||
|
HAS_JPEG_ORIGINAL(FilterCategory.BITSTREAM_MIME, (context, item) ->
|
||||||
|
ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.MIMES_JPG) > 0),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one PDF of size less than 20 kb (configurable in rest.cfg).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_small_pdf")
|
||||||
|
HAS_SMALL_PDF(FilterCategory.BITSTREAM_MIME, (context, item) ->
|
||||||
|
ItemFilterUtil.countBitstreamSmallerThanMinSize(
|
||||||
|
context, BundleName.ORIGINAL, item, ItemFilterUtil.MIMES_PDF, "rest.report-pdf-min-size") > 0),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one PDF of size more than 25 Mb (configurable in rest.cfg).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_large_pdf")
|
||||||
|
HAS_LARGE_PDF(FilterCategory.BITSTREAM_MIME, (context, item) ->
|
||||||
|
ItemFilterUtil.countBitstreamLargerThanMaxSize(
|
||||||
|
context, BundleName.ORIGINAL, item, ItemFilterUtil.MIMES_PDF, "rest.report-pdf-max-size") > 0),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one non-text bitstream.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_doc_without_text")
|
||||||
|
HAS_DOC_WITHOUT_TEXT(FilterCategory.BITSTREAM_MIME, (context, item) -> {
|
||||||
|
int countDoc = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes());
|
||||||
|
if (countDoc == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int countText = ItemFilterUtil.countBitstream(BundleName.TEXT, item);
|
||||||
|
return countDoc > countText;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches items having at least one image, but all of supported types.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_only_supp_image_type")
|
||||||
|
HAS_ONLY_SUPPORTED_IMAGE_TYPE(FilterCategory.MIME, (context, item) -> {
|
||||||
|
int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/");
|
||||||
|
if (imageCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int suppImageCount = ItemFilterUtil.countOriginalBitstreamMime(
|
||||||
|
context, item, ItemFilterUtil.getSupportedImageMimeTypes());
|
||||||
|
return (imageCount == suppImageCount);
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one image of an unsupported type.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_unsupp_image_type")
|
||||||
|
HAS_UNSUPPORTED_IMAGE_TYPE(FilterCategory.MIME, (context, item) -> {
|
||||||
|
int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/");
|
||||||
|
if (imageCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int suppImageCount = ItemFilterUtil.countOriginalBitstreamMime(
|
||||||
|
context, item, ItemFilterUtil.getSupportedImageMimeTypes());
|
||||||
|
return (imageCount - suppImageCount) > 0;
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one document, but all of supported types.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_only_supp_doc_type")
|
||||||
|
HAS_ONLY_SUPPORTED_DOC_TYPE(FilterCategory.MIME, (context, item) -> {
|
||||||
|
int docCount = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes());
|
||||||
|
if (docCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int suppDocCount = ItemFilterUtil.countOriginalBitstreamMime(
|
||||||
|
context, item, ItemFilterUtil.getSupportedDocumentMimeTypes());
|
||||||
|
return docCount == suppDocCount;
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one document of an unsupported type.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_unsupp_doc_type")
|
||||||
|
HAS_UNSUPPORTED_DOC_TYPE(FilterCategory.MIME, (context, item) -> {
|
||||||
|
int docCount = ItemFilterUtil.countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes());
|
||||||
|
if (docCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int suppDocCount = ItemFilterUtil.countOriginalBitstreamMime(
|
||||||
|
context, item, ItemFilterUtil.getSupportedDocumentMimeTypes());
|
||||||
|
return (docCount - suppDocCount) > 0;
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches items having at least one unsupported bundle.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_unsupported_bundle")
|
||||||
|
HAS_UNSUPPORTED_BUNDLE(FilterCategory.BUNDLE, (context, item) -> {
|
||||||
|
String[] bundleList = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||||
|
.getArrayProperty("rest.report-supp-bundles");
|
||||||
|
return ItemFilterUtil.hasUnsupportedBundle(item, bundleList);
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one thumbnail of size less than 400 bytes (configurable in rest.cfg).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_small_thumbnail")
|
||||||
|
HAS_SMALL_THUMBNAIL(FilterCategory.BUNDLE, (context, item) ->
|
||||||
|
ItemFilterUtil.countBitstreamSmallerThanMinSize(
|
||||||
|
context, BundleName.THUMBNAIL, item, ItemFilterUtil.MIMES_JPG, "rest.report-thumbnail-min-size") > 0),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one original without a thumbnail.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_original_without_thumbnail")
|
||||||
|
HAS_ORIGINAL_WITHOUT_THUMBNAIL(FilterCategory.BUNDLE, (context, item) -> {
|
||||||
|
int countBit = ItemFilterUtil.countOriginalBitstream(item);
|
||||||
|
if (countBit == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item);
|
||||||
|
return countBit > countThumb;
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one non-JPEG thumbnail.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_invalid_thumbnail_name")
|
||||||
|
HAS_INVALID_THUMBNAIL_NAME(FilterCategory.BUNDLE, (context, item) -> {
|
||||||
|
List<String> originalNames = ItemFilterUtil.getBitstreamNames(BundleName.ORIGINAL, item);
|
||||||
|
List<String> thumbNames = ItemFilterUtil.getBitstreamNames(BundleName.THUMBNAIL, item);
|
||||||
|
if (thumbNames.size() != originalNames.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return originalNames.stream()
|
||||||
|
.anyMatch(name -> !thumbNames.contains(name + ".jpg") && !thumbNames.contains(name + ".jpeg"));
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one non-generated thumbnail.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_non_generated_thumb")
|
||||||
|
HAS_NON_GENERATED_THUMBNAIL(FilterCategory.BUNDLE, (context, item) -> {
|
||||||
|
String[] generatedThumbDesc = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||||
|
.getArrayProperty("rest.report-gen-thumbnail-desc");
|
||||||
|
int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item);
|
||||||
|
if (countThumb == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int countGen = ItemFilterUtil.countBitstreamByDesc(BundleName.THUMBNAIL, item, generatedThumbDesc);
|
||||||
|
return (countThumb > countGen);
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having no licence-typed bitstreams.
|
||||||
|
*/
|
||||||
|
@JsonProperty("no_license")
|
||||||
|
NO_LICENSE(FilterCategory.BUNDLE, (context, item) ->
|
||||||
|
ItemFilterUtil.countBitstream(BundleName.LICENSE, item) == 0),
|
||||||
|
/**
|
||||||
|
* Matches items having licence documentation (a licence bitstream named other than license.txt).
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_license_documentation")
|
||||||
|
HAS_LICENSE_DOCUMENTATION(FilterCategory.BUNDLE, (context, item) -> {
|
||||||
|
List<String> names = ItemFilterUtil.getBitstreamNames(BundleName.LICENSE, item);
|
||||||
|
return names.stream()
|
||||||
|
.anyMatch(name -> !name.equals("license.txt"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches items having at least one original with restricted access.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_restricted_original")
|
||||||
|
HAS_RESTRICTED_ORIGINAL(FilterCategory.PERMISSION, (context, item) -> {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(BundleName.ORIGINAL.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.anyMatch(bit -> {
|
||||||
|
try {
|
||||||
|
if (!getAuthorizeService()
|
||||||
|
.authorizeActionBoolean(getAnonymousContext(), bit, org.dspace.core.Constants.READ)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
getLog().warn("SQL Exception testing original bitstream access " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having at least one thumbnail with restricted access.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_restricted_thumbnail")
|
||||||
|
HAS_RESTRICTED_THUMBNAIL(FilterCategory.PERMISSION, (context, item) -> {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(BundleName.THUMBNAIL.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.anyMatch(bit -> {
|
||||||
|
try {
|
||||||
|
if (!getAuthorizeService()
|
||||||
|
.authorizeActionBoolean(getAnonymousContext(), bit, org.dspace.core.Constants.READ)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
getLog().warn("SQL Exception testing thumbnail bitstream access " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Matches items having metadata with restricted access.
|
||||||
|
*/
|
||||||
|
@JsonProperty("has_restricted_metadata")
|
||||||
|
HAS_RESTRICTED_METADATA(FilterCategory.PERMISSION, (context, item) -> {
|
||||||
|
try {
|
||||||
|
return !getAuthorizeService()
|
||||||
|
.authorizeActionBoolean(getAnonymousContext(), item, org.dspace.core.Constants.READ);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
getLog().warn("SQL Exception testing item metadata access " + e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private static final Logger log = LogManager.getLogger();
|
||||||
|
private static AuthorizeService authorizeService;
|
||||||
|
private static Context anonymousContext;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private FilterCategory category;
|
||||||
|
private BiPredicate<Context, Item> itemTester;
|
||||||
|
|
||||||
|
Filter(FilterCategory category, BiPredicate<Context, Item> itemTester) {
|
||||||
|
try {
|
||||||
|
JsonProperty jp = getClass().getField(name()).getAnnotation(JsonProperty.class);
|
||||||
|
id = Optional.ofNullable(jp).map(JsonProperty::value).orElse(name());
|
||||||
|
} catch (Exception e) {
|
||||||
|
id = name();
|
||||||
|
}
|
||||||
|
this.category = category;
|
||||||
|
this.itemTester = itemTester;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterCategory getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean testItem(Context context, Item item) {
|
||||||
|
return itemTester.test(context, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Logger getLog() {
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthorizeService getAuthorizeService() {
|
||||||
|
if (authorizeService == null) {
|
||||||
|
authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
|
||||||
|
}
|
||||||
|
return authorizeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Context getAnonymousContext() {
|
||||||
|
if (anonymousContext == null) {
|
||||||
|
anonymousContext = new Context();
|
||||||
|
}
|
||||||
|
return anonymousContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public static Filter get(String id) {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> Objects.equals(item.id, id))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Filter> getFilters(String filters) {
|
||||||
|
String[] ids = Optional.ofNullable(filters).orElse("").split("[^a-z_]+");
|
||||||
|
Set<Filter> set = Arrays.stream(ids)
|
||||||
|
.map(Filter::get)
|
||||||
|
.filter(f -> f != null)
|
||||||
|
.collect(Collectors.toCollection(() -> EnumSet.noneOf(Filter.class)));
|
||||||
|
if (set == null) {
|
||||||
|
set = EnumSet.noneOf(Filter.class);
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the category/section of filters defined in the {@link Filter} enum.
|
||||||
|
* This enum will be used when/if the structured filter definitions are returned to
|
||||||
|
* the Angular layer through a REST endpoint.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public enum FilterCategory {
|
||||||
|
|
||||||
|
PROPERTY("property"),
|
||||||
|
BITSTREAM("bitstream"),
|
||||||
|
BITSTREAM_MIME("bitstream_mime"),
|
||||||
|
MIME("mime"),
|
||||||
|
BUNDLE("bundle"),
|
||||||
|
PERMISSION("permission");
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private List<Filter> filters;
|
||||||
|
|
||||||
|
FilterCategory(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Filter> getFilters() {
|
||||||
|
if (filters == null) {
|
||||||
|
filters = Arrays.stream(Filter.values())
|
||||||
|
.filter(f -> f.getCategory() == this)
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents an entry in the Filtered Collections report.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredCollection implements Cloneable, Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -231735620268582719L;
|
||||||
|
|
||||||
|
/** Name of the collection */
|
||||||
|
private String label;
|
||||||
|
/** Handle of the collection, used to make it clickable from the generated report */
|
||||||
|
private String handle;
|
||||||
|
/** Name of the owning community */
|
||||||
|
private String communityLabel;
|
||||||
|
/** Handle of the owning community, used to make it clickable from the generated report */
|
||||||
|
private String communityHandle;
|
||||||
|
/** Total number of items in the collection */
|
||||||
|
private int totalItems;
|
||||||
|
/** Number of filtered items per requested filter in the collection */
|
||||||
|
private Map<Filter, Integer> values = new EnumMap<>(Filter.class);
|
||||||
|
/** Number of items in the collection that match all requested filters */
|
||||||
|
private int allFiltersValue;
|
||||||
|
/**
|
||||||
|
* Indicates whether this object is protected against further changes.
|
||||||
|
* This is used in computing summary data in the parent FilteredCollectionsRest class.
|
||||||
|
*/
|
||||||
|
private boolean sealed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredCollectionRest instance
|
||||||
|
* from its building blocks.
|
||||||
|
* @param label Name of the collection
|
||||||
|
* @param handle Handle of the collection
|
||||||
|
* @param communityLabel Name of the owning community
|
||||||
|
* @param communityHandle Handle of the owning community
|
||||||
|
* @param totalItems Total number of items in the collection
|
||||||
|
* @param allFiltersValue Number of items in the collection that match all requested filters
|
||||||
|
* @param values Number of filtered items per requested filter in the collection
|
||||||
|
* @param doSeal true if the collection must be sealed immediately
|
||||||
|
* @return a FilteredCollectionRest instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static FilteredCollection of(String label, String handle,
|
||||||
|
String communityLabel, String communityHandle,
|
||||||
|
int totalItems, int allFiltersValue, Map<Filter, Integer> values, boolean doSeal) {
|
||||||
|
var coll = new FilteredCollection();
|
||||||
|
coll.label = label;
|
||||||
|
coll.handle = handle;
|
||||||
|
coll.communityLabel = communityLabel;
|
||||||
|
coll.communityHandle = communityHandle;
|
||||||
|
coll.totalItems = totalItems;
|
||||||
|
coll.allFiltersValue = allFiltersValue;
|
||||||
|
Optional.ofNullable(values).ifPresent(vs -> vs.forEach(coll::addValue));
|
||||||
|
if (doSeal) {
|
||||||
|
coll.seal();
|
||||||
|
}
|
||||||
|
return coll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item counts per filter.
|
||||||
|
* If this object is sealed, a defensive copy will be returned.
|
||||||
|
*
|
||||||
|
* @return the item counts per filter
|
||||||
|
*/
|
||||||
|
public Map<Filter, Integer> getValues() {
|
||||||
|
if (sealed) {
|
||||||
|
return new EnumMap<>(values);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments a filtered item count for a given filter.
|
||||||
|
*
|
||||||
|
* @param filter Filter to add to the requested filters in this collection
|
||||||
|
* @param delta Number by which the filtered item count must be incremented
|
||||||
|
* for the requested filter
|
||||||
|
*/
|
||||||
|
public void addValue(Filter filter, int delta) {
|
||||||
|
checkSealed();
|
||||||
|
Integer oldValue = values.getOrDefault(filter, Integer.valueOf(0));
|
||||||
|
int newValue = oldValue.intValue() + delta;
|
||||||
|
values.put(filter, Integer.valueOf(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all filtered item counts for this collection.
|
||||||
|
* The contents are copied into this object's internal Map, which is protected against
|
||||||
|
* further tampering with the provided Map.
|
||||||
|
*
|
||||||
|
* @param values Values that replace the current ones
|
||||||
|
*/
|
||||||
|
public void setValues(Map<? extends Filter, ? extends Integer> values) {
|
||||||
|
checkSealed();
|
||||||
|
this.values.clear();
|
||||||
|
this.values.putAll(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabel(String label) {
|
||||||
|
checkSealed();
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandle(String handle) {
|
||||||
|
checkSealed();
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommunityLabel() {
|
||||||
|
return communityLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityLabel(String communityLabel) {
|
||||||
|
checkSealed();
|
||||||
|
this.communityLabel = communityLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommunityHandle() {
|
||||||
|
return communityHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommunityHandle(String communityHandle) {
|
||||||
|
checkSealed();
|
||||||
|
this.communityHandle = communityHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalItems() {
|
||||||
|
return totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalItems(int totalItems) {
|
||||||
|
checkSealed();
|
||||||
|
this.totalItems = totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAllFiltersValue() {
|
||||||
|
return allFiltersValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the count of items matching all filters.
|
||||||
|
*
|
||||||
|
* @param delta Number by which the count must be incremented
|
||||||
|
*/
|
||||||
|
public void addAllFiltersValue(int delta) {
|
||||||
|
checkSealed();
|
||||||
|
allFiltersValue++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the count of items matching all filters.
|
||||||
|
*
|
||||||
|
* @param allFiltersValue Number that replaces the current item count
|
||||||
|
*/
|
||||||
|
public void setAllFiltersValue(int allFiltersValue) {
|
||||||
|
checkSealed();
|
||||||
|
this.allFiltersValue = allFiltersValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSealed() {
|
||||||
|
return sealed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seals this filtered collection object.
|
||||||
|
* No changes to this object can be made afterwards. Any attempt will throw
|
||||||
|
* an IllegalStateException.
|
||||||
|
*/
|
||||||
|
public void seal() {
|
||||||
|
sealed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSealed() {
|
||||||
|
if (sealed) {
|
||||||
|
throw new IllegalStateException("This filtered collection record is sealed"
|
||||||
|
+ " and cannot be modified anymore. You can apply changes to a non-sealed clone.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a non-sealed clone of this filtered collection record.
|
||||||
|
*
|
||||||
|
* @return a new non-sealed FilteredCollectionRest instance containing
|
||||||
|
* all attribute values of this object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public FilteredCollection clone() {
|
||||||
|
var clone = new FilteredCollection();
|
||||||
|
clone.label = label;
|
||||||
|
clone.handle = handle;
|
||||||
|
clone.values.putAll(values);
|
||||||
|
clone.allFiltersValue = allFiltersValue;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the complete result of a Filtered Collections report query.
|
||||||
|
* In addition to the list of FilteredCollection entries, it contains the lazily computed
|
||||||
|
* summary to be included in the completed report.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredCollections implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3622651208704009095L;
|
||||||
|
|
||||||
|
/** Collections included in the report */
|
||||||
|
private List<FilteredCollection> collections = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* Summary generated by adding up data for each filter included in the report.
|
||||||
|
* It will be regenerated if any non-sealed collection item is found in
|
||||||
|
* the {@link #collections} collection attribute.
|
||||||
|
*/
|
||||||
|
private FilteredCollection summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredCollectionsRest instance
|
||||||
|
* from its building blocks.
|
||||||
|
* @param collections a list of FilteredCollectionRest instances
|
||||||
|
* @return a FilteredCollectionsRest instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static FilteredCollections of(Collection<FilteredCollection> collections) {
|
||||||
|
var colls = new FilteredCollections();
|
||||||
|
Optional.ofNullable(collections).ifPresent(cs -> cs.stream().forEach(colls::addCollection));
|
||||||
|
return colls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a defensive copy of the collections included in this report.
|
||||||
|
*
|
||||||
|
* @return the collections included in this report
|
||||||
|
*/
|
||||||
|
public List<FilteredCollection> getCollections() {
|
||||||
|
return new ArrayList<>(collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link FilteredCollectionRest} object to this report.
|
||||||
|
*
|
||||||
|
* @param coll {@link FilteredCollectionRest} to add to this report
|
||||||
|
*/
|
||||||
|
public void addCollection(FilteredCollection coll) {
|
||||||
|
summary = null;
|
||||||
|
collections.add(coll);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all collections for this report.
|
||||||
|
* The contents are copied into this object's internal list, which is protected against
|
||||||
|
* further tampering with the provided list.
|
||||||
|
*
|
||||||
|
* @param collections Values that replace the current ones
|
||||||
|
*/
|
||||||
|
public void setCollections(List<FilteredCollection> collections) {
|
||||||
|
summary = null;
|
||||||
|
this.collections.clear();
|
||||||
|
this.collections.addAll(collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the report summary.
|
||||||
|
* If the summary has not been computed yet and/or the report includes non-sealed collections,
|
||||||
|
* it will be regenerated.
|
||||||
|
*
|
||||||
|
* @return the generated report summary
|
||||||
|
*/
|
||||||
|
public FilteredCollection getSummary() {
|
||||||
|
boolean needsRefresh = summary == null || collections.stream().anyMatch(c -> !c.getSealed());
|
||||||
|
if (needsRefresh) {
|
||||||
|
summary = new FilteredCollection();
|
||||||
|
for (var coll : collections) {
|
||||||
|
coll.getValues().forEach(summary::addValue);
|
||||||
|
}
|
||||||
|
int total = collections.stream()
|
||||||
|
.mapToInt(FilteredCollection::getTotalItems)
|
||||||
|
.sum();
|
||||||
|
summary.setTotalItems(total);
|
||||||
|
int allFilters = collections.stream()
|
||||||
|
.mapToInt(FilteredCollection::getAllFiltersValue)
|
||||||
|
.sum();
|
||||||
|
summary.setAllFiltersValue(allFilters);
|
||||||
|
summary.seal();
|
||||||
|
}
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a list of items for a Filtered Items report query.
|
||||||
|
* Since the underlying list should correspond to only a page of results,
|
||||||
|
* the total number of items found through the query is included in this report.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredItems implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 7980375013177658249L;
|
||||||
|
|
||||||
|
/** Items included in the report */
|
||||||
|
private List<Item> items = new ArrayList<>();
|
||||||
|
/** Total item count (for pagination) */
|
||||||
|
private long itemCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a defensive copy of the items included in this report.
|
||||||
|
*
|
||||||
|
* @return the items included in this report
|
||||||
|
*/
|
||||||
|
public List<Item> getItems() {
|
||||||
|
return new ArrayList<>(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an {@link ItemRest} object to this report.
|
||||||
|
*
|
||||||
|
* @param item {@link ItemRest} to add to this report
|
||||||
|
*/
|
||||||
|
public void addItem(Item item) {
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all items for this report.
|
||||||
|
* The contents are copied into this object's internal list, which is protected
|
||||||
|
* against further tampering with the provided list.
|
||||||
|
*
|
||||||
|
* @param items Values that replace the current ones
|
||||||
|
*/
|
||||||
|
public void setItems(List<Item> items) {
|
||||||
|
this.items.clear();
|
||||||
|
this.items.addAll(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getItemCount() {
|
||||||
|
return itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemCount(long itemCount) {
|
||||||
|
this.itemCount = itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.contentreport;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structured query contents for the Filtered Items report
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredItemsQuery {
|
||||||
|
|
||||||
|
private List<String> collections = new ArrayList<>();
|
||||||
|
private List<QueryPredicate> queryPredicates = new ArrayList<>();
|
||||||
|
private long offset;
|
||||||
|
private int pageLimit;
|
||||||
|
private Set<Filter> filters = EnumSet.noneOf(Filter.class);
|
||||||
|
private List<String> additionalFields = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredItemsQuery instance
|
||||||
|
* from its building blocks.
|
||||||
|
* @param collectionUuids collection UUIDs to add
|
||||||
|
* @param predicates query predicates used to filter existing items
|
||||||
|
* @param pageLimit number of items per page
|
||||||
|
* @param filters filters to apply to existing items
|
||||||
|
* The filters mapping to true will be applied, others (either missing or
|
||||||
|
* mapping to false) will not.
|
||||||
|
* @param additionalFields additional fields to display in the resulting report
|
||||||
|
* @return a FilteredItemsQuery instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static FilteredItemsQuery of(Collection<String> collectionUuids,
|
||||||
|
Collection<QueryPredicate> predicates, long offset, int pageLimit,
|
||||||
|
Collection<Filter> filters, Collection<String> additionalFields) {
|
||||||
|
var query = new FilteredItemsQuery();
|
||||||
|
Optional.ofNullable(collectionUuids).ifPresent(query.collections::addAll);
|
||||||
|
Optional.ofNullable(predicates).ifPresent(query.queryPredicates::addAll);
|
||||||
|
query.offset = offset;
|
||||||
|
query.pageLimit = pageLimit;
|
||||||
|
Optional.ofNullable(filters).ifPresent(query.filters::addAll);
|
||||||
|
Optional.ofNullable(additionalFields).ifPresent(query.additionalFields::addAll);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCollections() {
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollections(List<String> collections) {
|
||||||
|
this.collections.clear();
|
||||||
|
if (collections != null) {
|
||||||
|
this.collections.addAll(collections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<QueryPredicate> getQueryPredicates() {
|
||||||
|
return queryPredicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQueryPredicates(List<QueryPredicate> queryPredicates) {
|
||||||
|
this.queryPredicates.clear();
|
||||||
|
if (queryPredicates != null) {
|
||||||
|
this.queryPredicates.addAll(queryPredicates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffset(long offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageLimit() {
|
||||||
|
return pageLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageLimit(int pageLimit) {
|
||||||
|
this.pageLimit = pageLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Filter> getFilters() {
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilters(Set<Filter> filters) {
|
||||||
|
this.filters.clear();
|
||||||
|
if (filters != null) {
|
||||||
|
this.filters.addAll(filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAdditionalFields() {
|
||||||
|
return additionalFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdditionalFields(List<String> additionalFields) {
|
||||||
|
this.additionalFields.clear();
|
||||||
|
if (additionalFields != null) {
|
||||||
|
this.additionalFields.addAll(additionalFields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,353 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import static org.dspace.content.Item.ANY;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.content.Bitstream;
|
||||||
|
import org.dspace.content.Bundle;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.content.factory.ContentServiceFactory;
|
||||||
|
import org.dspace.content.service.ItemService;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for applying some of the filters defined in the {@link Filter} enum.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval) (port to DSpace 7.x)
|
||||||
|
* @author Terry Brady, Georgetown University (original code in DSpace 6.x)
|
||||||
|
*/
|
||||||
|
public class ItemFilterUtil {
|
||||||
|
|
||||||
|
protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||||
|
private static final Logger log = LogManager.getLogger(ItemFilterUtil.class);
|
||||||
|
public static final String[] MIMES_PDF = {"application/pdf"};
|
||||||
|
public static final String[] MIMES_JPG = {"image/jpeg"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported bundle types.
|
||||||
|
* N.B.: Bundle names are used in metadata as they are named here.
|
||||||
|
* Do NOT change these names, the name() method is invoked at multiple
|
||||||
|
* locations in this class and enum Filter.
|
||||||
|
* If these names are to change, the name() invocations shall be changed
|
||||||
|
* so that they refer to these unchanged names, likely through a String property.
|
||||||
|
*/
|
||||||
|
enum BundleName {
|
||||||
|
ORIGINAL, TEXT, LICENSE, THUMBNAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemFilterUtil() {}
|
||||||
|
|
||||||
|
static String[] getDocumentMimeTypes() {
|
||||||
|
return DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||||
|
.getArrayProperty("rest.report-mime-document");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String[] getSupportedDocumentMimeTypes() {
|
||||||
|
return DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||||
|
.getArrayProperty("rest.report-mime-document-supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String[] getSupportedImageMimeTypes() {
|
||||||
|
return DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||||
|
.getArrayProperty("rest.report-mime-document-image");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the original bitstreams of a given item.
|
||||||
|
* @param item Provided item
|
||||||
|
* @return the number of original bitstreams in the item
|
||||||
|
*/
|
||||||
|
static int countOriginalBitstream(Item item) {
|
||||||
|
return countBitstream(BundleName.ORIGINAL, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the bitstreams of a given item for a specific type.
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @return the number of matching bitstreams in the item
|
||||||
|
*/
|
||||||
|
static int countBitstream(BundleName bundleName, Item item) {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.mapToInt(bundle -> bundle.getBitstreams().size())
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the bitstream names of an given item for a specific bundle type.
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @return the names of matching bitstreams in the item
|
||||||
|
*/
|
||||||
|
static List<String> getBitstreamNames(BundleName bundleName, Item item) {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.map(Bitstream::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the original bitstreams of a given item matching one of a list of specific MIME types.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param item Provided item
|
||||||
|
* @param mimeList List of MIME types to filter bitstreams
|
||||||
|
* @return number of matching original bitstreams
|
||||||
|
*/
|
||||||
|
static int countOriginalBitstreamMime(Context context, Item item, String[] mimeList) {
|
||||||
|
return countBitstreamMime(context, BundleName.ORIGINAL, item, mimeList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the bitstreams of a given item for a specific type matching one of a list of specific MIME types.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @param mimeList List of MIME types to filter bitstreams
|
||||||
|
* @return number of matching bitstreams
|
||||||
|
*/
|
||||||
|
static int countBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.mapToInt(bit -> {
|
||||||
|
int count = 0;
|
||||||
|
for (String mime : mimeList) {
|
||||||
|
try {
|
||||||
|
if (bit.getFormat(context).getMIMEType().equals(mime.trim())) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Get format error for bitstream " + bit.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the bitstreams of a given item for a specific type matching one of a list of specific descriptions.
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @param descList List of descriptions to filter bitstreams
|
||||||
|
* @return number of matching bitstreams
|
||||||
|
*/
|
||||||
|
static int countBitstreamByDesc(BundleName bundleName, Item item, String[] descList) {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.filter(bit -> bit.getDescription() != null)
|
||||||
|
.mapToInt(bit -> {
|
||||||
|
int count = 0;
|
||||||
|
for (String desc : descList) {
|
||||||
|
String bitDesc = bit.getDescription();
|
||||||
|
if (bitDesc.equals(desc.trim())) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the bitstreams of a given item smaller than a given size for a specific type
|
||||||
|
* matching one of a list of specific MIME types.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @param mimeList List of MIME types to filter bitstreams
|
||||||
|
* @param prop Configurable property providing the size to filter bitstreams
|
||||||
|
* @return number of matching bitstreams
|
||||||
|
*/
|
||||||
|
static int countBitstreamSmallerThanMinSize(
|
||||||
|
Context context, BundleName bundleName, Item item, String[] mimeList, String prop) {
|
||||||
|
long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop);
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.mapToInt(bit -> {
|
||||||
|
int count = 0;
|
||||||
|
for (String mime : mimeList) {
|
||||||
|
try {
|
||||||
|
if (bit.getFormat(context).getMIMEType().equals(mime.trim())) {
|
||||||
|
if (bit.getSizeBytes() < size) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the bitstreams of a given item larger than a given size for a specific type
|
||||||
|
* matching one of a list of specific MIME types.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @param mimeList List of MIME types to filter bitstreams
|
||||||
|
* @param prop Configurable property providing the size to filter bitstreams
|
||||||
|
* @return number of matching bitstreams
|
||||||
|
*/
|
||||||
|
static int countBitstreamLargerThanMaxSize(
|
||||||
|
Context context, BundleName bundleName, Item item, String[] mimeList, String prop) {
|
||||||
|
long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop);
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.mapToInt(bit -> {
|
||||||
|
int count = 0;
|
||||||
|
for (String mime : mimeList) {
|
||||||
|
try {
|
||||||
|
if (bit.getFormat(context).getMIMEType().equals(mime.trim())) {
|
||||||
|
if (bit.getSizeBytes() > size) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the original bitstreams of a given item whose MIME type starts with a specific prefix.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param item Provided item
|
||||||
|
* @param prefix Prefix to filter bitstreams
|
||||||
|
* @return number of matching original bitstreams
|
||||||
|
*/
|
||||||
|
static int countOriginalBitstreamMimeStartsWith(Context context, Item item, String prefix) {
|
||||||
|
return countBitstreamMimeStartsWith(context, BundleName.ORIGINAL, item, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the bitstreams of a given item for a specific type whose MIME type starts with a specific prefix.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param bundleName Type of bundle to filter bitstreams
|
||||||
|
* @param item Provided item
|
||||||
|
* @param prefix Prefix to filter bitstreams
|
||||||
|
* @return number of matching bitstreams
|
||||||
|
*/
|
||||||
|
static int countBitstreamMimeStartsWith(Context context, BundleName bundleName, Item item, String prefix) {
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.filter(bundle -> bundle.getName().equals(bundleName.name()))
|
||||||
|
.map(Bundle::getBitstreams)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.mapToInt(bit -> {
|
||||||
|
int count = 0;
|
||||||
|
try {
|
||||||
|
if (bit.getFormat(context).getMIMEType().startsWith(prefix)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a given item has a bundle not matching a specific list of bundles.
|
||||||
|
* @param item Provided item
|
||||||
|
* @param bundleList List of bundle names to filter bundles
|
||||||
|
* @return true if the item has a (non-)matching bundle
|
||||||
|
*/
|
||||||
|
static boolean hasUnsupportedBundle(Item item, String[] bundleList) {
|
||||||
|
if (bundleList == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Set<String> bundles = Arrays.stream(bundleList)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
return item.getBundles().stream()
|
||||||
|
.anyMatch(bundle -> !bundles.contains(bundle.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hasOriginalBitstreamMime(Context context, Item item, String[] mimeList) {
|
||||||
|
return hasBitstreamMime(context, BundleName.ORIGINAL, item, mimeList);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hasBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) {
|
||||||
|
return countBitstreamMime(context, bundleName, item, mimeList) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a given item has at least one field of a specific list whose value
|
||||||
|
* matches a provided regular expression.
|
||||||
|
* @param item Provided item
|
||||||
|
* @param fieldList List of fields to check
|
||||||
|
* @param regex Regular expression to check field values against
|
||||||
|
* @return true if there is at least one matching field, false otherwise
|
||||||
|
*/
|
||||||
|
static boolean hasMetadataMatch(Item item, String fieldList, Pattern regex) {
|
||||||
|
if ("*".equals(fieldList)) {
|
||||||
|
return itemService.getMetadata(item, ANY, ANY, ANY, ANY).stream()
|
||||||
|
.anyMatch(md -> regex.matcher(md.getValue()).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.stream(fieldList.split(","))
|
||||||
|
.map(field -> itemService.getMetadataByMetadataString(item, field.trim()))
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.anyMatch(md -> regex.matcher(md.getValue()).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a given item has at all fields of a specific list whose values
|
||||||
|
* match a provided regular expression.
|
||||||
|
* @param item Provided item
|
||||||
|
* @param fieldList List of fields to check
|
||||||
|
* @param regex Regular expression to check field values against
|
||||||
|
* @return true if all specified fields match, false otherwise
|
||||||
|
*/
|
||||||
|
static boolean hasOnlyMetadataMatch(Item item, String fieldList, Pattern regex) {
|
||||||
|
if ("*".equals(fieldList)) {
|
||||||
|
return itemService.getMetadata(item, ANY, ANY, ANY, ANY).stream()
|
||||||
|
.allMatch(md -> regex.matcher(md.getValue()).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.stream(fieldList.split(","))
|
||||||
|
.map(field -> itemService.getMetadataByMetadataString(item, field.trim()))
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.allMatch(md -> regex.matcher(md.getValue()).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean recentlyModified(Item item, int days) {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.add(Calendar.DATE, -days);
|
||||||
|
return cal.getTime().before(item.getLastModified());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.Expression;
|
||||||
|
import javax.persistence.criteria.Path;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.apache.commons.lang3.function.TriFunction;
|
||||||
|
import org.dspace.content.MetadataValue;
|
||||||
|
import org.dspace.content.MetadataValue_;
|
||||||
|
import org.dspace.util.DSpacePostgreSQLDialect;
|
||||||
|
import org.dspace.util.JpaCriteriaBuilderKit;
|
||||||
|
import org.hibernate.criterion.Criterion;
|
||||||
|
import org.hibernate.criterion.Property;
|
||||||
|
import org.hibernate.criterion.Restrictions;
|
||||||
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operators available for creating predicates to query the
|
||||||
|
* Filtered Items report
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public enum QueryOperator {
|
||||||
|
|
||||||
|
EXISTS("exists", true, false,
|
||||||
|
(val, regexClause) -> Property.forName("mv.value").isNotNull(),
|
||||||
|
(val, regexClause, jpaKit) -> jpaKit.criteriaBuilder().isNotNull(jpaKit.root().get(MetadataValue_.VALUE))),
|
||||||
|
DOES_NOT_EXIST("doesnt_exist", true, true,
|
||||||
|
(val, regexClause) -> EXISTS.buildPredicate(val, regexClause),
|
||||||
|
(val, regexClause, jpaKit) -> EXISTS.buildJpaPredicate(val, regexClause, jpaKit)),
|
||||||
|
EQUALS("equals", true, false,
|
||||||
|
(val, regexClause) -> Property.forName("mv.value").eq(val),
|
||||||
|
(val, regexClause, jpaKit) -> jpaKit.criteriaBuilder().equal(jpaKit.root().get(MetadataValue_.VALUE), val)),
|
||||||
|
DOES_NOT_EQUAL("not_equals", true, true,
|
||||||
|
(val, regexClause) -> EQUALS.buildPredicate(val, regexClause),
|
||||||
|
(val, regexClause, jpaKit) -> EQUALS.buildJpaPredicate(val, regexClause, jpaKit)),
|
||||||
|
LIKE("like", true, false,
|
||||||
|
(val, regexClause) -> Property.forName("mv.value").like(val),
|
||||||
|
(val, regexClause, jpaKit) -> jpaKit.criteriaBuilder().like(jpaKit.root().get(MetadataValue_.VALUE), val)),
|
||||||
|
NOT_LIKE("not_like", true, true,
|
||||||
|
(val, regexClause) -> LIKE.buildPredicate(val, regexClause),
|
||||||
|
(val, regexClause, jpaKit) -> LIKE.buildJpaPredicate(val, regexClause, jpaKit)),
|
||||||
|
CONTAINS("contains", true, false,
|
||||||
|
(val, regexClause) -> Property.forName("mv.value").like("%" + val + "%"),
|
||||||
|
(val, regexClause, jpaKit) -> LIKE.buildJpaPredicate("%" + val + "%", regexClause, jpaKit)),
|
||||||
|
DOES_NOT_CONTAIN("doesnt_contain", true, true,
|
||||||
|
(val, regexClause) -> CONTAINS.buildPredicate(val, regexClause),
|
||||||
|
(val, regexClause, jpaKit) -> CONTAINS.buildJpaPredicate(val, regexClause, jpaKit)),
|
||||||
|
MATCHES("matches", false, false,
|
||||||
|
(val, regexClause) -> Restrictions.sqlRestriction(regexClause, val, StandardBasicTypes.STRING),
|
||||||
|
(val, regexClause, jpaKit) -> regexPredicate(val, DSpacePostgreSQLDialect.REGEX_MATCHES, jpaKit)),
|
||||||
|
DOES_NOT_MATCH("doesnt_match", false, false,
|
||||||
|
(val, regexClause) -> Restrictions.not(Restrictions.sqlRestriction(
|
||||||
|
regexClause, val, StandardBasicTypes.STRING)),
|
||||||
|
(val, regexClause, jpaKit) -> regexPredicate(val, DSpacePostgreSQLDialect.REGEX_NOT_MATCHES, jpaKit));
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
/** Criteria builder for the old Hibernate API */
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
private final BiFunction<String, String, Criterion> criterionBuilder;
|
||||||
|
private final TriFunction<String, String, JpaCriteriaBuilderKit<MetadataValue>, Predicate> predicateBuilder;
|
||||||
|
private final boolean usesRegex;
|
||||||
|
private final boolean negate;
|
||||||
|
|
||||||
|
QueryOperator(String code, boolean usesRegex, boolean negate,
|
||||||
|
BiFunction<String, String, Criterion> criterionBuilder,
|
||||||
|
TriFunction<String, String, JpaCriteriaBuilderKit<MetadataValue>, Predicate> predicateBuilder) {
|
||||||
|
this.code = code;
|
||||||
|
this.usesRegex = usesRegex;
|
||||||
|
this.negate = negate;
|
||||||
|
this.criterionBuilder = criterionBuilder;
|
||||||
|
this.predicateBuilder = predicateBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getUsesRegex() {
|
||||||
|
return usesRegex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getNegate() {
|
||||||
|
return negate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Criterion buildPredicate(String val, String regexClause) {
|
||||||
|
return criterionBuilder.apply(val, regexClause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Predicate buildJpaPredicate(String val, String regexClause, JpaCriteriaBuilderKit<MetadataValue> jpaKit) {
|
||||||
|
return predicateBuilder.apply(val, regexClause, jpaKit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public static QueryOperator get(String code) {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equalsIgnoreCase(code))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BiFunction<String, String, Criterion> getCriterionBuilder() {
|
||||||
|
return criterionBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Predicate regexPredicate(String val, String regexFunction,
|
||||||
|
JpaCriteriaBuilderKit<MetadataValue> jpaKit) {
|
||||||
|
// Source: https://stackoverflow.com/questions/24995881/use-regular-expressions-in-jpa-criteriabuilder
|
||||||
|
CriteriaBuilder builder = jpaKit.criteriaBuilder();
|
||||||
|
Expression<String> patternExpression = builder.<String>literal(val);
|
||||||
|
Path<String> path = jpaKit.root().get(MetadataValue_.VALUE);
|
||||||
|
// "matches" comes from the name of the regex function
|
||||||
|
// defined in class DSpacePostgreSQLDialect
|
||||||
|
return builder.equal(builder
|
||||||
|
.function(regexFunction, Boolean.class, path, patternExpression), Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.dspace.content.MetadataField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure representing a query predicate used by the Filtered Items report
|
||||||
|
* to filter items to retrieve.
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class QueryPredicate {
|
||||||
|
|
||||||
|
private List<MetadataField> fields = new ArrayList<>();
|
||||||
|
private QueryOperator operator;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a QueryPredicate from a single field, an operator, and a value.
|
||||||
|
* @param field Predicate subject
|
||||||
|
* @param operator Predicate operator
|
||||||
|
* @param value Predicate object
|
||||||
|
* @return a QueryPredicate instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static QueryPredicate of(MetadataField field, QueryOperator operator, String value) {
|
||||||
|
var predicate = new QueryPredicate();
|
||||||
|
predicate.fields.add(field);
|
||||||
|
predicate.operator = operator;
|
||||||
|
predicate.value = value;
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a QueryPredicate from a list of fields, an operator, and a value.
|
||||||
|
* @param fields Fields that form the predicate subject
|
||||||
|
* @param operator Predicate operator
|
||||||
|
* @param value Predicate object
|
||||||
|
* @return a QueryPredicate instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static QueryPredicate of(Collection<MetadataField> fields, QueryOperator operator, String value) {
|
||||||
|
var predicate = new QueryPredicate();
|
||||||
|
predicate.fields.addAll(fields);
|
||||||
|
predicate.operator = operator;
|
||||||
|
predicate.value = value;
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MetadataField> getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryOperator getOperator() {
|
||||||
|
return operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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.contentreport.service;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
import org.dspace.contentreport.FilteredCollection;
|
||||||
|
import org.dspace.contentreport.FilteredItems;
|
||||||
|
import org.dspace.contentreport.FilteredItemsQuery;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
|
||||||
|
public interface ContentReportService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true<</code> if Content Reports are enabled.
|
||||||
|
* @return <code>true<</code> if Content Reports are enabled
|
||||||
|
*/
|
||||||
|
boolean getEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves item statistics per collection according to a set of Boolean filters.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param filters Set of filters
|
||||||
|
* @return a list of collections with the requested statistics for each of them
|
||||||
|
*/
|
||||||
|
List<FilteredCollection> findFilteredCollections(Context context, Collection<Filter> filters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of items according to a set of criteria.
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param query structured query to find items against
|
||||||
|
* @return a list of items filtered according to the provided query
|
||||||
|
*/
|
||||||
|
FilteredItems findFilteredItems(Context context, FilteredItemsQuery query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a metadata field name to a list of {@link MetadataField} instances
|
||||||
|
* (one if no wildcards are used, possibly more otherwise).
|
||||||
|
* @param context DSpace context
|
||||||
|
* @param metadataField field to search for
|
||||||
|
* @return a corresponding list of {@link MetadataField} entries
|
||||||
|
*/
|
||||||
|
List<MetadataField> getMetadataFields(org.dspace.core.Context context, String metadataField)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.PostgreSQL94Dialect;
|
||||||
|
import org.hibernate.dialect.function.SQLFunctionTemplate;
|
||||||
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL-specific dialect that adds regular expression support as a JPA function.
|
||||||
|
* @see org.dspace.contentreport.QueryOperator
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class DSpacePostgreSQLDialect extends PostgreSQL94Dialect {
|
||||||
|
|
||||||
|
public static final String REGEX_MATCHES = "matches";
|
||||||
|
public static final String REGEX_IMATCHES = "imatches";
|
||||||
|
public static final String REGEX_NOT_MATCHES = "not_matches";
|
||||||
|
public static final String REGEX_NOT_IMATCHES = "not_imatches";
|
||||||
|
|
||||||
|
public DSpacePostgreSQLDialect() {
|
||||||
|
registerFunction(REGEX_MATCHES, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 ~ ?2"));
|
||||||
|
registerFunction(REGEX_IMATCHES, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 ~* ?2"));
|
||||||
|
registerFunction(REGEX_NOT_MATCHES, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 !~ ?2"));
|
||||||
|
registerFunction(REGEX_NOT_IMATCHES, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 !~* ?2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.util;
|
||||||
|
|
||||||
|
import javax.persistence.criteria.AbstractQuery;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure containing the required objects to build criteria
|
||||||
|
* for a JPA query built using the JPA Criteria API.
|
||||||
|
* The getters match those generated by the JVM when using a record
|
||||||
|
* so that no API changes will be required when this class gets converted
|
||||||
|
* into a record when DSpace gets promoted to Java 17 or later.
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
// TODO: Convert this data structure into a record when DSpace gets promoted to Java 17 or later
|
||||||
|
public class JpaCriteriaBuilderKit<T> {
|
||||||
|
|
||||||
|
private CriteriaBuilder criteriaBuilder;
|
||||||
|
/** Can be a CriteriaQuery as well as a Subquery - both extend AbstractQuery. */
|
||||||
|
private AbstractQuery<T> query;
|
||||||
|
private Root<T> root;
|
||||||
|
|
||||||
|
public JpaCriteriaBuilderKit(CriteriaBuilder criteriaBuilder, AbstractQuery<T> query,
|
||||||
|
Root<T> root) {
|
||||||
|
this.criteriaBuilder = criteriaBuilder;
|
||||||
|
this.query = query;
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CriteriaBuilder criteriaBuilder() {
|
||||||
|
return criteriaBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractQuery<T> query() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Root<T> root() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -9,7 +9,10 @@ package org.dspace;
|
|||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -91,6 +94,14 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati
|
|||||||
try {
|
try {
|
||||||
// Update/Initialize the database to latest version (via Flyway)
|
// Update/Initialize the database to latest version (via Flyway)
|
||||||
DatabaseUtils.updateDatabase();
|
DatabaseUtils.updateDatabase();
|
||||||
|
|
||||||
|
// Register custom functions in the H2 database
|
||||||
|
DataSource dataSource = DSpaceServicesFactory.getInstance()
|
||||||
|
.getServiceManager()
|
||||||
|
.getServiceByName("dataSource", DataSource.class);
|
||||||
|
try (Connection c = dataSource.getConnection(); Statement stmt = c.createStatement()) {
|
||||||
|
stmt.execute("CREATE ALIAS IF NOT EXISTS matches FOR 'org.dspace.util.DSpaceH2Dialect.matches'");
|
||||||
|
}
|
||||||
} catch (SQLException se) {
|
} catch (SQLException se) {
|
||||||
log.error("Error initializing database", se);
|
log.error("Error initializing database", se);
|
||||||
fail("Error initializing database: " + se.getMessage()
|
fail("Error initializing database: " + se.getMessage()
|
||||||
|
@@ -11,7 +11,9 @@ import static org.hamcrest.CoreMatchers.equalTo;
|
|||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -19,6 +21,9 @@ import java.io.InputStream;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -44,11 +49,15 @@ import org.dspace.content.Collection;
|
|||||||
import org.dspace.content.Community;
|
import org.dspace.content.Community;
|
||||||
import org.dspace.content.EntityType;
|
import org.dspace.content.EntityType;
|
||||||
import org.dspace.content.Item;
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.content.MetadataSchema;
|
||||||
import org.dspace.content.MetadataValue;
|
import org.dspace.content.MetadataValue;
|
||||||
import org.dspace.content.Relationship;
|
import org.dspace.content.Relationship;
|
||||||
import org.dspace.content.RelationshipType;
|
import org.dspace.content.RelationshipType;
|
||||||
import org.dspace.content.WorkspaceItem;
|
import org.dspace.content.WorkspaceItem;
|
||||||
import org.dspace.content.factory.ContentServiceFactory;
|
import org.dspace.content.factory.ContentServiceFactory;
|
||||||
|
import org.dspace.contentreport.QueryOperator;
|
||||||
|
import org.dspace.contentreport.QueryPredicate;
|
||||||
import org.dspace.core.Constants;
|
import org.dspace.core.Constants;
|
||||||
import org.dspace.eperson.Group;
|
import org.dspace.eperson.Group;
|
||||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||||
@@ -71,6 +80,9 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||||
protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
|
protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
|
||||||
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
|
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
|
||||||
|
protected MetadataSchemaService metadataSchemaService =
|
||||||
|
ContentServiceFactory.getInstance().getMetadataSchemaService();
|
||||||
|
protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService();
|
||||||
protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
|
protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
|
||||||
protected VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService();
|
protected VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService();
|
||||||
protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
|
protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
|
||||||
@@ -78,6 +90,8 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
|
|
||||||
Community community;
|
Community community;
|
||||||
Collection collection1;
|
Collection collection1;
|
||||||
|
MetadataSchema schemaDC;
|
||||||
|
MetadataField fieldAuthor;
|
||||||
|
|
||||||
Item item;
|
Item item;
|
||||||
|
|
||||||
@@ -99,6 +113,9 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
try {
|
try {
|
||||||
context.turnOffAuthorisationSystem();
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
schemaDC = metadataSchemaService.find(context, "dc");
|
||||||
|
fieldAuthor = metadataFieldService.findByElement(context, schemaDC, "contributor", "author");
|
||||||
|
|
||||||
community = CommunityBuilder.createCommunity(context)
|
community = CommunityBuilder.createCommunity(context)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -142,7 +159,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
|
|
||||||
// check the correct order using default method `getMetadata`
|
// check the correct order using default method `getMetadata`
|
||||||
List<MetadataValue> defaultMetadata =
|
List<MetadataValue> defaultMetadata =
|
||||||
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
|
itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
|
||||||
|
|
||||||
assertThat(defaultMetadata,hasSize(3));
|
assertThat(defaultMetadata,hasSize(3));
|
||||||
|
|
||||||
@@ -158,7 +175,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
|
|
||||||
// check the correct order using the method `getMetadata` without virtual fields
|
// check the correct order using the method `getMetadata` without virtual fields
|
||||||
List<MetadataValue> nonVirtualMetadatas =
|
List<MetadataValue> nonVirtualMetadatas =
|
||||||
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
|
itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
|
||||||
|
|
||||||
// if we don't reload the item the place order is not applied correctly
|
// if we don't reload the item the place order is not applied correctly
|
||||||
// item = context.reloadEntity(item);
|
// item = context.reloadEntity(item);
|
||||||
@@ -180,19 +197,19 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
item = context.reloadEntity(item);
|
item = context.reloadEntity(item);
|
||||||
|
|
||||||
// now just add one metadata to be the last
|
// now just add one metadata to be the last
|
||||||
this.itemService.addMetadata(
|
itemService.addMetadata(
|
||||||
context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, latest", null, 0
|
context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, latest", null, 0
|
||||||
);
|
);
|
||||||
// now just remove first metadata
|
// now just remove first metadata
|
||||||
this.itemService.removeMetadataValues(context, item, List.of(placeZero));
|
itemService.removeMetadataValues(context, item, List.of(placeZero));
|
||||||
// now just add one metadata to place 0
|
// now just add one metadata to place 0
|
||||||
this.itemService.addAndShiftRightMetadata(
|
itemService.addAndShiftRightMetadata(
|
||||||
context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, new", null, 0, 0
|
context, item, dcSchema, contributorElement, authorQualifier, Item.ANY, "test, new", null, 0, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// check the metadata using method `getMetadata`
|
// check the metadata using method `getMetadata`
|
||||||
defaultMetadata =
|
defaultMetadata =
|
||||||
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
|
itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
|
||||||
|
|
||||||
// check correct places
|
// check correct places
|
||||||
assertThat(defaultMetadata,hasSize(4));
|
assertThat(defaultMetadata,hasSize(4));
|
||||||
@@ -212,7 +229,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
|
|
||||||
// check metadata using nonVirtualMethod
|
// check metadata using nonVirtualMethod
|
||||||
nonVirtualMetadatas =
|
nonVirtualMetadatas =
|
||||||
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
|
itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
|
||||||
|
|
||||||
// check correct places
|
// check correct places
|
||||||
assertThat(nonVirtualMetadatas,hasSize(4));
|
assertThat(nonVirtualMetadatas,hasSize(4));
|
||||||
@@ -244,7 +261,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
|
|
||||||
// check after commit
|
// check after commit
|
||||||
defaultMetadata =
|
defaultMetadata =
|
||||||
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
|
itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY);
|
||||||
|
|
||||||
// check correct places
|
// check correct places
|
||||||
assertThat(defaultMetadata,hasSize(4));
|
assertThat(defaultMetadata,hasSize(4));
|
||||||
@@ -264,7 +281,7 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
|
|
||||||
// check metadata using nonVirtualMethod
|
// check metadata using nonVirtualMethod
|
||||||
nonVirtualMetadatas =
|
nonVirtualMetadatas =
|
||||||
this.itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
|
itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY, false);
|
||||||
|
|
||||||
// check correct places
|
// check correct places
|
||||||
assertThat(nonVirtualMetadatas,hasSize(4));
|
assertThat(nonVirtualMetadatas,hasSize(4));
|
||||||
@@ -916,4 +933,65 @@ public class ItemServiceIT extends AbstractIntegrationTestWithDatabase {
|
|||||||
assertThat(metadataValue.getAuthority(), equalTo(authority));
|
assertThat(metadataValue.getAuthority(), equalTo(authority));
|
||||||
assertThat(metadataValue.getPlace(), equalTo(place));
|
assertThat(metadataValue.getPlace(), equalTo(place));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindByMetadataQuery() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
// Here we add an author to the item
|
||||||
|
MetadataValue mv = itemService.addMetadata(context, item, dcSchema, contributorElement,
|
||||||
|
authorQualifier, null, "test, one");
|
||||||
|
context.commit();
|
||||||
|
|
||||||
|
item = context.reloadEntity(item);
|
||||||
|
|
||||||
|
assertNotNull(mv);
|
||||||
|
MetadataField mf = mv.getMetadataField();
|
||||||
|
assertEquals(fieldAuthor, mf);
|
||||||
|
MetadataSchema ms = mf.getMetadataSchema();
|
||||||
|
assertNotNull(ms);
|
||||||
|
assertEquals(dcSchema, ms.getName());
|
||||||
|
|
||||||
|
// We check whether the author metadata was properly added.
|
||||||
|
List<MetadataValue> mvs = item.getMetadata();
|
||||||
|
MetadataValue mvAuthor1 = mvs.stream()
|
||||||
|
.filter(mv1 -> Objects.equals(mv1.getMetadataField().getElement(), "contributor"))
|
||||||
|
.filter(mv1 -> Objects.equals(mv1.getMetadataField().getQualifier(), "author"))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
assertNotNull(mvAuthor1);
|
||||||
|
assertEquals("test, one", mvAuthor1.getValue());
|
||||||
|
|
||||||
|
assertMetadataValue(
|
||||||
|
authorQualifier, contributorElement, dcSchema, "test, one", null, 0, mvAuthor1
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(collection1, item.getOwningCollection());
|
||||||
|
|
||||||
|
List<UUID> collectionUuids = List.of(collection1.getID());
|
||||||
|
|
||||||
|
// First test: we should not find anything.
|
||||||
|
QueryPredicate predicate = QueryPredicate.of(fieldAuthor, QueryOperator.MATCHES, ".*whatever.*");
|
||||||
|
List<Item> items = itemService.findByMetadataQuery(context, List.of(predicate), collectionUuids, 0, -1);
|
||||||
|
assertTrue(items.isEmpty());
|
||||||
|
|
||||||
|
// Second test: we search against the metadata value specified above.
|
||||||
|
predicate = QueryPredicate.of(fieldAuthor, QueryOperator.EQUALS, "test, one");
|
||||||
|
items = itemService.findByMetadataQuery(context, List.of(predicate), collectionUuids, 0, -1);
|
||||||
|
assertEquals(1, items.size());
|
||||||
|
|
||||||
|
Item item = items.get(0);
|
||||||
|
assertNotNull(item);
|
||||||
|
List<MetadataValue> allMetadata = item.getMetadata();
|
||||||
|
Optional<MetadataValue> mvAuthor = allMetadata.stream()
|
||||||
|
.filter(md -> Objects.equals(dcSchema, md.getMetadataField().getMetadataSchema().getName()))
|
||||||
|
.filter(md -> Objects.equals(contributorElement, md.getMetadataField().getElement()))
|
||||||
|
.filter(md -> Objects.equals(authorQualifier, md.getMetadataField().getQualifier()))
|
||||||
|
.findFirst();
|
||||||
|
assertTrue(mvAuthor.isPresent());
|
||||||
|
assertEquals("test, one", mvAuthor.get().getValue());
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.H2Dialect;
|
||||||
|
import org.hibernate.dialect.function.SQLFunctionTemplate;
|
||||||
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H2-specific dialect that adds regular expression support as a function.
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class DSpaceH2Dialect extends H2Dialect {
|
||||||
|
|
||||||
|
private static Map<String, Pattern> regexCache = new HashMap<>();
|
||||||
|
|
||||||
|
public DSpaceH2Dialect() {
|
||||||
|
registerFunction("matches", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "matches(?1, ?2)"));
|
||||||
|
|
||||||
|
// The SQL function is registered in AbstractIntegrationTestWithDatabase.initDatabase().
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matches(String regex, String value) {
|
||||||
|
Pattern pattern = regexCache.get(regex);
|
||||||
|
if (pattern == null) {
|
||||||
|
pattern = Pattern.compile(regex);
|
||||||
|
regexCache.put(regex, pattern);
|
||||||
|
}
|
||||||
|
return pattern.matcher(value).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,226 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.app.rest.converter.ConverterService;
|
||||||
|
import org.dspace.app.rest.model.ContentReportSupportRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredCollectionsQuery;
|
||||||
|
import org.dspace.app.rest.model.FilteredCollectionsRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsQueryPredicate;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsQueryRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsRest;
|
||||||
|
import org.dspace.app.rest.model.RestModel;
|
||||||
|
import org.dspace.app.rest.model.hateoas.ContentReportSupportResource;
|
||||||
|
import org.dspace.app.rest.model.hateoas.FilteredCollectionsResource;
|
||||||
|
import org.dspace.app.rest.model.hateoas.FilteredItemsResource;
|
||||||
|
import org.dspace.app.rest.repository.ContentReportRestRepository;
|
||||||
|
import org.dspace.app.rest.utils.ContextUtil;
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
import org.dspace.contentreport.service.ContentReportService;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.rest.webmvc.ControllerUtils;
|
||||||
|
import org.springframework.hateoas.Link;
|
||||||
|
import org.springframework.hateoas.RepresentationModel;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller receives and dispatches requests related to the
|
||||||
|
* contents reports ported from DSpace 6.x (Filtered Collections
|
||||||
|
* and Filtered Items).
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/" + RestModel.CONTENT_REPORT)
|
||||||
|
public class ContentReportRestController implements InitializingBean {
|
||||||
|
|
||||||
|
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DiscoverableEndpointsService discoverableEndpointsService;
|
||||||
|
@Autowired
|
||||||
|
private ConverterService converter;
|
||||||
|
@Autowired
|
||||||
|
private ContentReportRestRepository contentReportRestRepository;
|
||||||
|
@Autowired
|
||||||
|
private ContentReportService contentReportService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
discoverableEndpointsService
|
||||||
|
.register(this, List.of(Link.of("/api/" + RestModel.CONTENT_REPORT, RestModel.CONTENT_REPORT)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.GET)
|
||||||
|
public ContentReportSupportResource getContentReportSupport() {
|
||||||
|
ContentReportSupportRest contentReportSupportRest = contentReportRestRepository.getContentReportSupport();
|
||||||
|
return converter.toResource(contentReportSupportRest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET-based endpoint for the Filtered Collections contents report.
|
||||||
|
* This method also serves as a feed for the HAL Browser infrastructure.
|
||||||
|
* @param filters querying filters received as a comma-separated string
|
||||||
|
* or as a multivalued parameter
|
||||||
|
* @param request HTTP request
|
||||||
|
* @param response HTTP response
|
||||||
|
* @return the list of collections with their respective statistics
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasAuthority('ADMIN')")
|
||||||
|
@GetMapping("/filteredcollections")
|
||||||
|
public ResponseEntity<RepresentationModel<?>> getFilteredCollections(
|
||||||
|
@RequestParam(name = "filters", required = false) List<String> filters,
|
||||||
|
HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
|
if (contentReportService.getEnabled()) {
|
||||||
|
Context context = ContextUtil.obtainContext(request);
|
||||||
|
Set<Filter> filtersSet = listToStream(filters)
|
||||||
|
.map(Filter::get)
|
||||||
|
.filter(f -> f != null)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
FilteredCollectionsQuery query = FilteredCollectionsQuery.of(filtersSet);
|
||||||
|
return filteredCollectionsReport(context, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
error404(response);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ResponseEntity<RepresentationModel<?>> filteredCollectionsReport(Context context,
|
||||||
|
FilteredCollectionsQuery query) {
|
||||||
|
FilteredCollectionsRest report = contentReportRestRepository
|
||||||
|
.findFilteredCollections(context, query);
|
||||||
|
FilteredCollectionsResource result = converter.toResource(report);
|
||||||
|
return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint for the Filtered Items contents report.
|
||||||
|
* All parameters received as comma-separated lists can also be repeated
|
||||||
|
* instead (e.g., filters=a&filters=b&...).
|
||||||
|
* @param collections comma-separated list UUIDs of collections to include in the report
|
||||||
|
* @param predicates predicates to filter the requested items.
|
||||||
|
* A given predicate has the form
|
||||||
|
* field:operator:value (if value is required by the operator), or
|
||||||
|
* field:operator (if no value is required by the operator).
|
||||||
|
* The colon is used here as a separator to avoid conflicts with the
|
||||||
|
* comma, which is already used by Spring as a multi-value separator.
|
||||||
|
* Predicates are actually retrieved directly through the request to prevent comma-containing
|
||||||
|
* predicate values from being split by the Spring infrastructure.
|
||||||
|
* @param pageNumber page number (starting at 0)
|
||||||
|
* @param pageLimit maximum number of items per page
|
||||||
|
* @param filters querying filters received as a comma-separated string
|
||||||
|
* @param additionalFields comma-separated list of extra fields to add to the report
|
||||||
|
* @param request HTTP request
|
||||||
|
* @param response HTTP response
|
||||||
|
* @param pageable paging parameters
|
||||||
|
* @return the list of items with their respective statistics
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasAuthority('ADMIN')")
|
||||||
|
@GetMapping("/filtereditems")
|
||||||
|
public ResponseEntity<RepresentationModel<?>> getFilteredItems(
|
||||||
|
@RequestParam(name = "collections", required = false) List<String> collections,
|
||||||
|
@RequestParam(name = "queryPredicates", required = false) List<String> predicates,
|
||||||
|
@RequestParam(name = "pageNumber", defaultValue = "0") String pageNumber,
|
||||||
|
@RequestParam(name = "pageLimit", defaultValue = "10") String pageLimit,
|
||||||
|
@RequestParam(name = "filters", required = false) List<String> filters,
|
||||||
|
@RequestParam(name = "additionalFields", required = false) List<String> additionalFields,
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Pageable pageable) throws IOException {
|
||||||
|
if (contentReportService.getEnabled()) {
|
||||||
|
Context context = ContextUtil.obtainContext(request);
|
||||||
|
String[] realPredicates = request.getParameterValues("queryPredicates");
|
||||||
|
List<String> collUuids = Optional.ofNullable(collections).orElseGet(() -> List.of());
|
||||||
|
List<FilteredItemsQueryPredicate> preds = arrayToStream(realPredicates)
|
||||||
|
.map(FilteredItemsQueryPredicate::of)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
int pgLimit = parseInt(pageLimit, 10);
|
||||||
|
int pgNumber = parseInt(pageNumber, 0);
|
||||||
|
Pageable myPageable = pageable;
|
||||||
|
if (pageable == null || pageable.getPageNumber() != pgNumber || pageable.getPageSize() != pgLimit) {
|
||||||
|
Sort sort = Optional.ofNullable(pageable).map(Pageable::getSort).orElse(Sort.unsorted());
|
||||||
|
myPageable = PageRequest.of(pgNumber, pgLimit, sort);
|
||||||
|
}
|
||||||
|
Set<Filter> filtersMap = listToStream(filters)
|
||||||
|
.map(Filter::get)
|
||||||
|
.filter(f -> f != null)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
List<String> addFields = Optional.ofNullable(additionalFields).orElseGet(() -> List.of());
|
||||||
|
FilteredItemsQueryRest query = FilteredItemsQueryRest.of(collUuids, preds, pgLimit, filtersMap, addFields);
|
||||||
|
|
||||||
|
return filteredItemsReport(context, query, myPageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
error404(response);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<String> listToStream(Collection<String> array) {
|
||||||
|
return Optional.ofNullable(array)
|
||||||
|
.stream()
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.filter(StringUtils::isNotBlank);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<String> arrayToStream(String... array) {
|
||||||
|
return Optional.ofNullable(array)
|
||||||
|
.stream()
|
||||||
|
.flatMap(Arrays::stream)
|
||||||
|
.filter(StringUtils::isNotBlank);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseInt(String value, int defaultValue) {
|
||||||
|
return Optional.ofNullable(value)
|
||||||
|
.stream()
|
||||||
|
.mapToInt(Integer::parseInt)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<RepresentationModel<?>> filteredItemsReport(Context context,
|
||||||
|
FilteredItemsQueryRest query, Pageable pageable) {
|
||||||
|
FilteredItemsRest report = contentReportRestRepository
|
||||||
|
.findFilteredItems(context, query, pageable);
|
||||||
|
FilteredItemsResource result = converter.toResource(report);
|
||||||
|
return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void error404(HttpServletResponse response) throws IOException {
|
||||||
|
log.debug("Content Reports are disabled");
|
||||||
|
String err = "Content Reports are disabled";
|
||||||
|
response.setStatus(404);
|
||||||
|
response.setContentType("text/html");
|
||||||
|
response.setContentLength(err.length());
|
||||||
|
response.getWriter().write(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* 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.converter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemRest;
|
||||||
|
import org.dspace.app.rest.model.MetadataValueList;
|
||||||
|
import org.dspace.app.rest.projection.Projection;
|
||||||
|
import org.dspace.app.rest.utils.ContextUtil;
|
||||||
|
import org.dspace.app.util.service.MetadataExposureService;
|
||||||
|
import org.dspace.authorize.service.AuthorizeService;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.content.MetadataValue;
|
||||||
|
import org.dspace.content.service.ItemService;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the converter from/to the Item in the DSpace API data model and the
|
||||||
|
* REST data model
|
||||||
|
*
|
||||||
|
* @author Andrea Bollini (andrea.bollini at 4science.it)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class FilteredItemConverter {
|
||||||
|
|
||||||
|
// Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components
|
||||||
|
@Lazy
|
||||||
|
@Autowired
|
||||||
|
ConverterService converter;
|
||||||
|
@Autowired
|
||||||
|
private ItemService itemService;
|
||||||
|
@Autowired
|
||||||
|
private CollectionConverter collectionConverter;
|
||||||
|
@Autowired
|
||||||
|
AuthorizeService authorizeService;
|
||||||
|
@Autowired
|
||||||
|
MetadataExposureService metadataExposureService;
|
||||||
|
|
||||||
|
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredItemConverter.class);
|
||||||
|
|
||||||
|
public FilteredItemRest convert(Item obj, Projection projection) {
|
||||||
|
FilteredItemRest item = new FilteredItemRest();
|
||||||
|
|
||||||
|
item.setHandle(obj.getHandle());
|
||||||
|
if (obj.getID() != null) {
|
||||||
|
item.setUuid(obj.getID().toString());
|
||||||
|
}
|
||||||
|
item.setName(obj.getName());
|
||||||
|
|
||||||
|
MetadataValueList metadataValues = getPermissionFilteredMetadata(
|
||||||
|
ContextUtil.obtainCurrentRequestContext(), obj);
|
||||||
|
item.setMetadata(converter.toRest(metadataValues, projection));
|
||||||
|
|
||||||
|
item.setInArchive(obj.isArchived());
|
||||||
|
item.setDiscoverable(obj.isDiscoverable());
|
||||||
|
item.setWithdrawn(obj.isWithdrawn());
|
||||||
|
item.setLastModified(obj.getLastModified());
|
||||||
|
|
||||||
|
List<MetadataValue> entityTypes =
|
||||||
|
itemService.getMetadata(obj, "dspace", "entity", "type", Item.ANY, false);
|
||||||
|
if (CollectionUtils.isNotEmpty(entityTypes) && StringUtils.isNotBlank(entityTypes.get(0).getValue())) {
|
||||||
|
item.setEntityType(entityTypes.get(0).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional.ofNullable(obj.getOwningCollection())
|
||||||
|
.map(coll -> collectionConverter.convert(coll, Projection.DEFAULT))
|
||||||
|
.ifPresent(item::setOwningCollection);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the metadata list filtered according to the hidden metadata configuration
|
||||||
|
* When the context is null, it will return the metadatalist as for an anonymous user
|
||||||
|
* Overrides the parent method to include virtual metadata
|
||||||
|
* @param context The context
|
||||||
|
* @param obj The object of which the filtered metadata will be retrieved
|
||||||
|
* @return A list of object metadata (including virtual metadata) filtered based on the hidden metadata
|
||||||
|
* configuration
|
||||||
|
*/
|
||||||
|
private MetadataValueList getPermissionFilteredMetadata(Context context, Item obj) {
|
||||||
|
List<MetadataValue> fullList = itemService.getMetadata(obj, Item.ANY, Item.ANY, Item.ANY, Item.ANY, true);
|
||||||
|
List<MetadataValue> returnList = new LinkedList<>();
|
||||||
|
try {
|
||||||
|
if (obj.isWithdrawn() && (Objects.isNull(context) ||
|
||||||
|
Objects.isNull(context.getCurrentUser()) || !authorizeService.isAdmin(context))) {
|
||||||
|
return new MetadataValueList(new ArrayList<>());
|
||||||
|
}
|
||||||
|
if (context != null && (authorizeService.isAdmin(context) || itemService.canEdit(context, obj))) {
|
||||||
|
return new MetadataValueList(fullList);
|
||||||
|
}
|
||||||
|
for (MetadataValue mv : fullList) {
|
||||||
|
MetadataField metadataField = mv.getMetadataField();
|
||||||
|
if (!metadataExposureService
|
||||||
|
.isHidden(context, metadataField.getMetadataSchema().getName(),
|
||||||
|
metadataField.getElement(),
|
||||||
|
metadataField.getQualifier())) {
|
||||||
|
returnList.add(mv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Error filtering item metadata based on permissions", e);
|
||||||
|
}
|
||||||
|
return new MetadataValueList(returnList);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* 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.link.contentreport;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.ContentReportRestController;
|
||||||
|
import org.dspace.app.rest.link.HalLinkFactory;
|
||||||
|
import org.dspace.app.rest.model.hateoas.ContentReportSupportResource;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.hateoas.IanaLinkRelations;
|
||||||
|
import org.springframework.hateoas.Link;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class adds the self and report links to the ContentReportSupportResource.
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ContentReportSupportHalLinkFactory
|
||||||
|
extends HalLinkFactory<ContentReportSupportResource, ContentReportRestController> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addLinks(ContentReportSupportResource halResource, Pageable pageable, LinkedList<Link> list)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().getContentReportSupport()));
|
||||||
|
list.add(buildLink("filteredcollections", getMethodOn().getFilteredCollections(null, null, null)));
|
||||||
|
list.add(buildLink("filtereditems", getMethodOn()
|
||||||
|
.getFilteredItems(null, null, null, null, null, null, null, null, null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<ContentReportRestController> getControllerClass() {
|
||||||
|
return ContentReportRestController.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<ContentReportSupportResource> getResourceClass() {
|
||||||
|
return ContentReportSupportResource.class;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.ContentReportRestController;
|
||||||
|
|
||||||
|
public class ContentReportSupportRest extends BaseObjectRest<String> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 9137258312781361906L;
|
||||||
|
public static final String NAME = "contentreport";
|
||||||
|
public static final String CATEGORY = RestModel.CONTENT_REPORT;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getController() {
|
||||||
|
return ContentReportRestController.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypePlural() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
import org.dspace.contentreport.FilteredCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class serves as a REST representation of a single Collection in a {@link FilteredCollectionsRest}
|
||||||
|
* from the DSpace statistics. It takes its values from a @link FilteredCollection} instance.
|
||||||
|
* It must not extend BaseObjectRest<?>.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredCollectionRest {
|
||||||
|
|
||||||
|
public static final String NAME = "filtered-collection";
|
||||||
|
public static final String CATEGORY = RestModel.CONTENT_REPORT;
|
||||||
|
|
||||||
|
/** Name of the collection */
|
||||||
|
private String label;
|
||||||
|
/** Handle of the collection, used to make it clickable from the generated report */
|
||||||
|
private String handle;
|
||||||
|
/** Name of the owning community */
|
||||||
|
@JsonProperty("community_label")
|
||||||
|
private String communityLabel;
|
||||||
|
/** Handle of the owning community, used to make it clickable from the generated report */
|
||||||
|
@JsonProperty("community_handle")
|
||||||
|
private String communityHandle;
|
||||||
|
/** Total number of items in the collection */
|
||||||
|
@JsonProperty("nb_total_items")
|
||||||
|
private int totalItems;
|
||||||
|
/** Number of filtered items per requested filter in the collection */
|
||||||
|
private Map<Filter, Integer> values = new EnumMap<>(Filter.class);
|
||||||
|
/** Number of items in the collection that match all requested filters */
|
||||||
|
@JsonProperty("all_filters_value")
|
||||||
|
private int allFiltersValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a FilteredCollectionRest instance from a {@link FilteredCollection} instance.
|
||||||
|
* @param model the FilteredCollection instance that provides values to the
|
||||||
|
* FilteredCollectionRest instance to be created
|
||||||
|
* @return a FilteredCollectionRest instance built from the provided model object
|
||||||
|
*/
|
||||||
|
public static FilteredCollectionRest of(FilteredCollection model) {
|
||||||
|
Objects.requireNonNull(model);
|
||||||
|
|
||||||
|
var coll = new FilteredCollectionRest();
|
||||||
|
coll.label = model.getLabel();
|
||||||
|
coll.handle = model.getHandle();
|
||||||
|
coll.communityLabel = model.getCommunityLabel();
|
||||||
|
coll.communityHandle = model.getCommunityHandle();
|
||||||
|
coll.totalItems = model.getTotalItems();
|
||||||
|
coll.allFiltersValue = model.getAllFiltersValue();
|
||||||
|
Optional.ofNullable(model.getValues()).ifPresent(coll.values::putAll);
|
||||||
|
|
||||||
|
return coll;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
public String getType() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Filter, Integer> getValues() {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommunityLabel() {
|
||||||
|
return communityLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommunityHandle() {
|
||||||
|
return communityHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalItems() {
|
||||||
|
return totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAllFiltersValue() {
|
||||||
|
return allFiltersValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structured query contents for the Filtered Collections report
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredCollectionsQuery {
|
||||||
|
|
||||||
|
private Set<Filter> filters = EnumSet.noneOf(Filter.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredCollectionsQuery instance
|
||||||
|
* from its building blocks.
|
||||||
|
* @param filters filters to apply to existing items.
|
||||||
|
* The filters mapping to true will be applied, others (either missing or
|
||||||
|
* mapping to false) will not.
|
||||||
|
* @return a FilteredCollectionsQuery instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static FilteredCollectionsQuery of(Collection<Filter> filters) {
|
||||||
|
var query = new FilteredCollectionsQuery();
|
||||||
|
Optional.ofNullable(filters).ifPresent(query.filters::addAll);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Filter> getFilters() {
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilters(Set<Filter> filters) {
|
||||||
|
this.filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toQueryString() {
|
||||||
|
return filters.stream()
|
||||||
|
.map(f -> "filters=" + f.getId())
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.ContentReportRestController;
|
||||||
|
import org.dspace.contentreport.FilteredCollections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class serves as a REST representation of a Filtered Collections Report.
|
||||||
|
* The name must match that of the associated resource class (FilteredCollectionsResource) except for
|
||||||
|
* the suffix. This is why it is not named something like FilteredCollectionsReportRest.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredCollectionsRest extends BaseObjectRest<String> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -1109226348211060786L;
|
||||||
|
/** Type of instances of this class, used by the DSpace REST infrastructure */
|
||||||
|
public static final String NAME = "filteredcollectionsreport";
|
||||||
|
/** Category of instances of this class, used by the DSpace REST infrastructure */
|
||||||
|
public static final String CATEGORY = RestModel.CONTENT_REPORT;
|
||||||
|
|
||||||
|
/** Collections included in the report */
|
||||||
|
private List<FilteredCollectionRest> collections = new ArrayList<>();
|
||||||
|
/** Report summary */
|
||||||
|
private FilteredCollectionRest summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a FilteredCollectionsRest instance from a {@link FilteredCollections} instance.
|
||||||
|
* Each underlying FilteredCollection is converted to a FilteredCollectionRest instance.
|
||||||
|
* @param model the FilteredCollections instance that provides values to the
|
||||||
|
* FilteredCollectionsRest instance to be created
|
||||||
|
* @return a FilteredCollectionsRest instance built from the provided model object
|
||||||
|
*/
|
||||||
|
public static FilteredCollectionsRest of(FilteredCollections model) {
|
||||||
|
var colls = new FilteredCollectionsRest();
|
||||||
|
Optional.ofNullable(model.getCollections()).ifPresent(cs ->
|
||||||
|
cs.stream()
|
||||||
|
.map(FilteredCollectionRest::of)
|
||||||
|
.forEach(colls.collections::add));
|
||||||
|
colls.summary = FilteredCollectionRest.of(model.getSummary());
|
||||||
|
return colls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getController() {
|
||||||
|
return ContentReportRestController.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypePlural() {
|
||||||
|
return getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FilteredCollectionRest> getCollections() {
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilteredCollectionRest getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization of ItemRest dedicated to the Filtered Items report.
|
||||||
|
* This class adds the owning collection property required to properly
|
||||||
|
* display search results without compromising the expected behaviour
|
||||||
|
* of standard ItemRest instances, in all other contexts, especially
|
||||||
|
* when it comes to embedded contents, a criterion that is widely checked
|
||||||
|
* against in several integration tests.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (jean-francois.morin@bibl.ulaval.ca)
|
||||||
|
*/
|
||||||
|
public class FilteredItemRest {
|
||||||
|
|
||||||
|
public static final String NAME = "filtered-item";
|
||||||
|
public static final String CATEGORY = RestAddressableModel.CONTENT_REPORT;
|
||||||
|
|
||||||
|
public static final String OWNING_COLLECTION = "owningCollection";
|
||||||
|
|
||||||
|
private String uuid;
|
||||||
|
private String name;
|
||||||
|
private String handle;
|
||||||
|
MetadataRest metadata = new MetadataRest();
|
||||||
|
private boolean inArchive = false;
|
||||||
|
private boolean discoverable = false;
|
||||||
|
private boolean withdrawn = false;
|
||||||
|
private Date lastModified = new Date();
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
private String entityType = null;
|
||||||
|
private CollectionRest owningCollection;
|
||||||
|
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
public String getType() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUuid(String uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandle(String handle) {
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MetadataRest getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(MetadataRest metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getInArchive() {
|
||||||
|
return inArchive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInArchive(boolean inArchive) {
|
||||||
|
this.inArchive = inArchive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getDiscoverable() {
|
||||||
|
return discoverable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDiscoverable(boolean discoverable) {
|
||||||
|
this.discoverable = discoverable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getWithdrawn() {
|
||||||
|
return withdrawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWithdrawn(boolean withdrawn) {
|
||||||
|
this.withdrawn = withdrawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLastModified() {
|
||||||
|
return lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModified(Date lastModified) {
|
||||||
|
this.lastModified = lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntityType() {
|
||||||
|
return entityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityType(String entityType) {
|
||||||
|
this.entityType = entityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionRest getOwningCollection() {
|
||||||
|
return owningCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwningCollection(CollectionRest owningCollection) {
|
||||||
|
this.owningCollection = owningCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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.model;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dspace.contentreport.QueryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure representing a query predicate used by the Filtered Items report
|
||||||
|
* to filter items to retrieve. This version is specific to the REST layer and its
|
||||||
|
* property types are detached from the persistence layer.
|
||||||
|
* @see org.dspace.contentreport.QueryPredicate
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredItemsQueryPredicate {
|
||||||
|
|
||||||
|
private String field;
|
||||||
|
private QueryOperator operator;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredItemsQueryPredicate from a single field, an operator, and a value.
|
||||||
|
* @param field Predicate subject
|
||||||
|
* @param operator Predicate operator
|
||||||
|
* @param value Predicate object
|
||||||
|
* @return a FilteredItemsQueryPredicate instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static FilteredItemsQueryPredicate of(String field, QueryOperator operator, String value) {
|
||||||
|
var predicate = new FilteredItemsQueryPredicate();
|
||||||
|
predicate.field = field;
|
||||||
|
predicate.operator = operator;
|
||||||
|
predicate.value = value;
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredItemsQueryPredicate from a colon-separated string value.
|
||||||
|
* @param value Colon-separated string value (field:operator:object or field:operator)
|
||||||
|
* @return a FilteredItemsQueryPredicate instance built from the provided value
|
||||||
|
*/
|
||||||
|
public static FilteredItemsQueryPredicate of(String value) {
|
||||||
|
String[] tokens = value.split("\\:");
|
||||||
|
String field = tokens.length > 0 ? tokens[0].trim() : "";
|
||||||
|
QueryOperator operator = tokens.length > 1 ? QueryOperator.get(tokens[1].trim()) : null;
|
||||||
|
String object = tokens.length > 2 ? StringUtils.trimToEmpty(tokens[2]) : "";
|
||||||
|
return of(field, operator, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryOperator getOperator() {
|
||||||
|
return operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String op = Optional.ofNullable(operator).map(QueryOperator::getCode).orElse("");
|
||||||
|
return field + ":" + op + ":" + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
import org.dspace.contentreport.QueryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST-based version of structured query contents for the Filtered Items report
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredItemsQueryRest {
|
||||||
|
|
||||||
|
private List<String> collections = new ArrayList<>();
|
||||||
|
private List<FilteredItemsQueryPredicate> queryPredicates = new ArrayList<>();
|
||||||
|
private int pageLimit;
|
||||||
|
private Set<Filter> filters = EnumSet.noneOf(Filter.class);
|
||||||
|
private List<String> additionalFields = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut method that builds a FilteredItemsQueryRest instance
|
||||||
|
* from its building blocks.
|
||||||
|
* @param collectionUuids collection UUIDs to add
|
||||||
|
* @param predicates query predicates used to filter existing items
|
||||||
|
* @param pageLimit number of items per page
|
||||||
|
* @param filters filters to apply to existing items
|
||||||
|
* The filters mapping to true will be applied, others (either missing or
|
||||||
|
* mapping to false) will not.
|
||||||
|
* @param additionalFields additional fields to display in the resulting report
|
||||||
|
* @return a FilteredItemsQueryRest instance built from the provided parameters
|
||||||
|
*/
|
||||||
|
public static FilteredItemsQueryRest of(Collection<String> collectionUuids,
|
||||||
|
Collection<FilteredItemsQueryPredicate> predicates, int pageLimit,
|
||||||
|
Collection<Filter> filters, Collection<String> additionalFields) {
|
||||||
|
var query = new FilteredItemsQueryRest();
|
||||||
|
Optional.ofNullable(collectionUuids).ifPresent(query.collections::addAll);
|
||||||
|
Optional.ofNullable(predicates).ifPresent(query.queryPredicates::addAll);
|
||||||
|
query.pageLimit = pageLimit;
|
||||||
|
Optional.ofNullable(filters).ifPresent(query.filters::addAll);
|
||||||
|
Optional.ofNullable(additionalFields).ifPresent(query.additionalFields::addAll);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCollections() {
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollections(List<String> collections) {
|
||||||
|
this.collections = collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FilteredItemsQueryPredicate> getQueryPredicates() {
|
||||||
|
return queryPredicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQueryPredicates(List<FilteredItemsQueryPredicate> queryPredicates) {
|
||||||
|
this.queryPredicates = queryPredicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPredicateFields() {
|
||||||
|
if (queryPredicates == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return queryPredicates.stream()
|
||||||
|
.map(FilteredItemsQueryPredicate::getField)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<QueryOperator> getPredicateOperators() {
|
||||||
|
if (queryPredicates == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return queryPredicates.stream()
|
||||||
|
.map(FilteredItemsQueryPredicate::getOperator)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPredicateValues() {
|
||||||
|
if (queryPredicates == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return queryPredicates.stream()
|
||||||
|
.map(FilteredItemsQueryPredicate::getValue)
|
||||||
|
.map(s -> s == null ? "" : s)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageLimit() {
|
||||||
|
return pageLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageLimit(int pageLimit) {
|
||||||
|
this.pageLimit = pageLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Filter> getFilters() {
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilters(Set<Filter> filters) {
|
||||||
|
this.filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAdditionalFields() {
|
||||||
|
return additionalFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdditionalFields(List<String> additionalFields) {
|
||||||
|
this.additionalFields = additionalFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toQueryString() {
|
||||||
|
String colls = collections.stream()
|
||||||
|
.map(coll -> "collection=" + coll)
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
String preds = queryPredicates.stream()
|
||||||
|
.map(pred -> "queryPredicates=" + pred)
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
String pgLimit = "pageLimit=" + pageLimit;
|
||||||
|
String fltrs = filters.stream()
|
||||||
|
.map(e -> "filters=" + e.getId())
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
String flds = additionalFields.stream()
|
||||||
|
.map(fld -> "additionalFields=" + fld)
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
|
||||||
|
return Stream.of(colls, preds, pgLimit, fltrs, flds)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.ContentReportRestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class serves as a REST representation of a Filtered Items Report.
|
||||||
|
* The name must match that of the associated resource class (FilteredItemsResource) except for
|
||||||
|
* the suffix. This is why it is not named something like FilteredItemsReportRest.
|
||||||
|
*
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class FilteredItemsRest extends BaseObjectRest<String> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -2483812920345013458L;
|
||||||
|
/** Type of instances of this class, used by the DSpace REST infrastructure */
|
||||||
|
public static final String NAME = "filtereditemsreport";
|
||||||
|
/** Category of instances of this class, used by the DSpace REST infrastructure */
|
||||||
|
public static final String CATEGORY = RestModel.CONTENT_REPORT;
|
||||||
|
|
||||||
|
/** Items included in the report */
|
||||||
|
private List<FilteredItemRest> items = new ArrayList<>();
|
||||||
|
/** Total item count (for pagination) */
|
||||||
|
private long itemCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a FilteredItemsRest instance from a list of items and an total item count.
|
||||||
|
* To avoid adding a dependency to any Spring-managed service here, the items
|
||||||
|
* provided here are already converted to FilteredItemRest instances.
|
||||||
|
* @param items the items to add to the FilteredItemsRest instance to be created
|
||||||
|
* @param itemCount total number of items found regardless of any pagination constraint
|
||||||
|
* @return a FilteredItemsRest instance built from the provided data
|
||||||
|
*/
|
||||||
|
public static FilteredItemsRest of(List<FilteredItemRest> items, long itemCount) {
|
||||||
|
var itemsRest = new FilteredItemsRest();
|
||||||
|
itemsRest.items.addAll(items);
|
||||||
|
itemsRest.itemCount = itemCount;
|
||||||
|
return itemsRest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return controller class responsible for this Rest object
|
||||||
|
*
|
||||||
|
* @return Controller class responsible for this Rest object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Class<?> getController() {
|
||||||
|
return ContentReportRestController.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypePlural() {
|
||||||
|
return getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a defensive copy of the items included in this report.
|
||||||
|
*
|
||||||
|
* @return the items included in this report
|
||||||
|
*/
|
||||||
|
public List<FilteredItemRest> getItems() {
|
||||||
|
return new ArrayList<>(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getItemCount() {
|
||||||
|
return itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
public interface RestModel extends Serializable {
|
public interface RestModel extends Serializable {
|
||||||
|
|
||||||
public static final String ROOT = "root";
|
public static final String ROOT = "root";
|
||||||
|
public static final String CONTENT_REPORT = "contentreport";
|
||||||
public static final String CORE = "core";
|
public static final String CORE = "core";
|
||||||
public static final String EPERSON = "eperson";
|
public static final String EPERSON = "eperson";
|
||||||
public static final String DISCOVER = "discover";
|
public static final String DISCOVER = "discover";
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model.hateoas;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.model.ContentReportSupportRest;
|
||||||
|
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
|
||||||
|
|
||||||
|
@RelNameDSpaceResource(ContentReportSupportRest.NAME)
|
||||||
|
public class ContentReportSupportResource extends HALResource<ContentReportSupportRest> {
|
||||||
|
public ContentReportSupportResource(ContentReportSupportRest content) {
|
||||||
|
super(content);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model.hateoas;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.model.FilteredCollectionsRest;
|
||||||
|
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
|
||||||
|
import org.dspace.app.rest.utils.Utils;
|
||||||
|
|
||||||
|
@RelNameDSpaceResource(FilteredCollectionsRest.NAME)
|
||||||
|
public class FilteredCollectionsResource extends DSpaceResource<FilteredCollectionsRest> {
|
||||||
|
|
||||||
|
public FilteredCollectionsResource(FilteredCollectionsRest data, Utils utils) {
|
||||||
|
super(data, utils);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.model.hateoas;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsRest;
|
||||||
|
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
|
||||||
|
import org.dspace.app.rest.utils.Utils;
|
||||||
|
|
||||||
|
@RelNameDSpaceResource(FilteredItemsRest.NAME)
|
||||||
|
public class FilteredItemsResource extends DSpaceResource<FilteredItemsRest> {
|
||||||
|
|
||||||
|
public FilteredItemsResource(FilteredItemsRest data, Utils utils) {
|
||||||
|
super(data, utils);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* The contents of this file are subject to the license and copyright
|
||||||
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
* tree and available online at
|
||||||
|
*
|
||||||
|
* http://www.dspace.org/license/
|
||||||
|
*/
|
||||||
|
package org.dspace.app.rest.repository;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.converter.FilteredItemConverter;
|
||||||
|
import org.dspace.app.rest.model.ContentReportSupportRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredCollectionsQuery;
|
||||||
|
import org.dspace.app.rest.model.FilteredCollectionsRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsQueryPredicate;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsQueryRest;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsRest;
|
||||||
|
import org.dspace.app.rest.projection.Projection;
|
||||||
|
import org.dspace.content.MetadataField;
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
import org.dspace.contentreport.FilteredCollection;
|
||||||
|
import org.dspace.contentreport.FilteredCollections;
|
||||||
|
import org.dspace.contentreport.FilteredItems;
|
||||||
|
import org.dspace.contentreport.FilteredItemsQuery;
|
||||||
|
import org.dspace.contentreport.QueryPredicate;
|
||||||
|
import org.dspace.contentreport.service.ContentReportService;
|
||||||
|
import org.dspace.core.Context;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This repository serves the content reports ported from DSpace 6.x
|
||||||
|
* (Filtered Collections and Filtered Items).
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
@Component(ContentReportSupportRest.CATEGORY + "." + ContentReportSupportRest.NAME)
|
||||||
|
public class ContentReportRestRepository extends AbstractDSpaceRestRepository {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ContentReportService contentReportService;
|
||||||
|
@Autowired
|
||||||
|
private FilteredItemConverter itemConverter;
|
||||||
|
|
||||||
|
public ContentReportSupportRest getContentReportSupport() {
|
||||||
|
return new ContentReportSupportRest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilteredCollectionsRest findFilteredCollections(Context context, FilteredCollectionsQuery query) {
|
||||||
|
Set<Filter> filters = query.getFilters();
|
||||||
|
|
||||||
|
List<FilteredCollection> colls = contentReportService.findFilteredCollections(context, filters);
|
||||||
|
FilteredCollections report = FilteredCollections.of(colls);
|
||||||
|
|
||||||
|
FilteredCollectionsRest reportRest = FilteredCollectionsRest.of(report);
|
||||||
|
reportRest.setId("filteredcollections");
|
||||||
|
return reportRest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilteredItemsRest findFilteredItems(Context context, FilteredItemsQueryRest queryRest, Pageable pageable) {
|
||||||
|
List<QueryPredicate> predicates = queryRest.getQueryPredicates().stream()
|
||||||
|
.map(pred -> convertPredicate(context, pred))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
FilteredItemsQuery query = new FilteredItemsQuery();
|
||||||
|
query.setCollections(queryRest.getCollections());
|
||||||
|
query.setQueryPredicates(predicates);
|
||||||
|
query.setFilters(queryRest.getFilters());
|
||||||
|
query.setAdditionalFields(queryRest.getAdditionalFields());
|
||||||
|
query.setOffset(pageable.getOffset());
|
||||||
|
query.setPageLimit(pageable.getPageSize());
|
||||||
|
|
||||||
|
FilteredItems items = contentReportService.findFilteredItems(context, query);
|
||||||
|
|
||||||
|
List<FilteredItemRest> filteredItemsRest = items.getItems().stream()
|
||||||
|
.map(item -> itemConverter.convert(item, Projection.DEFAULT))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
FilteredItemsRest report = FilteredItemsRest.of(filteredItemsRest, items.getItemCount());
|
||||||
|
report.setId("filtereditems");
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryPredicate convertPredicate(Context context, FilteredItemsQueryPredicate predicate) {
|
||||||
|
try {
|
||||||
|
List<MetadataField> fields = contentReportService.getMetadataFields(context, predicate.getField());
|
||||||
|
return QueryPredicate.of(fields, predicate.getOperator(), predicate.getValue());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,263 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.dspace.app.rest.matcher.ContentReportMatcher;
|
||||||
|
import org.dspace.app.rest.matcher.HalMatcher;
|
||||||
|
import org.dspace.app.rest.model.FilteredCollectionsQuery;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsQueryPredicate;
|
||||||
|
import org.dspace.app.rest.model.FilteredItemsQueryRest;
|
||||||
|
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
|
||||||
|
import org.dspace.builder.CollectionBuilder;
|
||||||
|
import org.dspace.builder.CommunityBuilder;
|
||||||
|
import org.dspace.builder.ItemBuilder;
|
||||||
|
import org.dspace.content.Collection;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.contentreport.Filter;
|
||||||
|
import org.dspace.contentreport.FilteredCollection;
|
||||||
|
import org.dspace.contentreport.QueryOperator;
|
||||||
|
import org.dspace.services.ConfigurationService;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for the content reports ported from DSpace 6.x
|
||||||
|
* (Filtered Collections and Filtered Items).
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class ContentReportRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationService configurationService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilteredCollections() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
configurationService.setProperty("contentreport.enable", Boolean.TRUE);
|
||||||
|
|
||||||
|
TestKit testKit = setupCollectionsAndItems();
|
||||||
|
Collection col1 = testKit.collections.get(0);
|
||||||
|
Collection col2 = testKit.collections.get(1);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
String token = getAuthToken(admin.getEmail(), password);
|
||||||
|
|
||||||
|
Map<Filter, Integer> valuesCol1 = Map.of(Filter.IS_DISCOVERABLE, 1);
|
||||||
|
FilteredCollection fcol1 = FilteredCollection.of(col1.getName(), col1.getHandle(),
|
||||||
|
parentCommunity.getName(), parentCommunity.getHandle(),
|
||||||
|
1, 1, valuesCol1, true);
|
||||||
|
Map<Filter, Integer> valuesCol2 = Map.of(Filter.IS_DISCOVERABLE, 2);
|
||||||
|
FilteredCollection fcol2 = FilteredCollection.of(col2.getName(), col2.getHandle(),
|
||||||
|
parentCommunity.getName(), parentCommunity.getHandle(),
|
||||||
|
2, 2, valuesCol2, true);
|
||||||
|
|
||||||
|
FilteredCollectionsQuery query = FilteredCollectionsQuery.of(Set.of(Filter.IS_DISCOVERABLE));
|
||||||
|
|
||||||
|
getClient(token).perform(get("/api/contentreport/filteredcollections?" + query.toQueryString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.collections", Matchers.containsInAnyOrder(
|
||||||
|
ContentReportMatcher.matchFilteredCollectionProperties(fcol1),
|
||||||
|
ContentReportMatcher.matchFilteredCollectionProperties(fcol2)
|
||||||
|
)))
|
||||||
|
.andExpect(jsonPath("type", is("filteredcollectionsreport")))
|
||||||
|
.andExpect(jsonPath("$.summary",
|
||||||
|
ContentReportMatcher.matchFilteredCollectionSummary(3, 3)))
|
||||||
|
.andExpect(jsonPath("$._links.self.href",
|
||||||
|
Matchers.containsString("/api/contentreport/filteredcollections")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilteredCollectionsUnauthorized() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
configurationService.setProperty("contentreport.enable", Boolean.TRUE);
|
||||||
|
|
||||||
|
setupCollectionsAndItems();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
FilteredCollectionsQuery query = FilteredCollectionsQuery.of(Set.of(Filter.IS_DISCOVERABLE));
|
||||||
|
|
||||||
|
getClient().perform(get("/api/contentreport/filteredcollections?" + query.toQueryString()))
|
||||||
|
.andExpect(status().isUnauthorized());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilteredCollectionsOff() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
configurationService.setProperty("contentreport.enable", Boolean.FALSE);
|
||||||
|
|
||||||
|
setupCollectionsAndItems();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
String token = getAuthToken(admin.getEmail(), password);
|
||||||
|
|
||||||
|
FilteredCollectionsQuery query = FilteredCollectionsQuery.of(Set.of(Filter.IS_DISCOVERABLE));
|
||||||
|
|
||||||
|
getClient(token).perform(get("/api/contentreport/filteredcollections?" + query.toQueryString()))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilteredItems() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
configurationService.setProperty("contentreport.enable", Boolean.TRUE);
|
||||||
|
|
||||||
|
TestKit testKit = setupCollectionsAndItems();
|
||||||
|
Item publicItem2 = testKit.items.get(1);
|
||||||
|
Item publicItem3 = testKit.items.get(2);
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
String token = getAuthToken(admin.getEmail(), password);
|
||||||
|
|
||||||
|
FilteredItemsQueryRest query = FilteredItemsQueryRest.of(null,
|
||||||
|
List.of(FilteredItemsQueryPredicate.of("dc.contributor.author", QueryOperator.EQUALS, "Doe, Jane")),
|
||||||
|
100, null, List.of("dc.subject"));
|
||||||
|
|
||||||
|
getClient(token).perform(get("/api/contentreport/filtereditems?" + query.toQueryString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$", HalMatcher.matchNoEmbeds()))
|
||||||
|
.andExpect(jsonPath("$.itemCount", is(2)))
|
||||||
|
.andExpect(jsonPath("$.items", Matchers.containsInAnyOrder(
|
||||||
|
matchItemProperties(publicItem2),
|
||||||
|
matchItemProperties(publicItem3)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilteredItemsUnauthorized() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
configurationService.setProperty("contentreport.enable", Boolean.TRUE);
|
||||||
|
|
||||||
|
setupCollectionsAndItems();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
|
||||||
|
FilteredItemsQueryRest query = FilteredItemsQueryRest.of(null,
|
||||||
|
List.of(FilteredItemsQueryPredicate.of("dc.contributor.author", QueryOperator.EQUALS, "Doe, Jane")),
|
||||||
|
100, null, List.of("dc.subject"));
|
||||||
|
|
||||||
|
getClient().perform(get("/api/contentreport/filtereditems?" + query.toQueryString()))
|
||||||
|
.andExpect(status().isUnauthorized());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilteredItemsOff() throws Exception {
|
||||||
|
context.turnOffAuthorisationSystem();
|
||||||
|
|
||||||
|
configurationService.setProperty("contentreport.enable", Boolean.FALSE);
|
||||||
|
|
||||||
|
setupCollectionsAndItems();
|
||||||
|
|
||||||
|
context.restoreAuthSystemState();
|
||||||
|
String token = getAuthToken(admin.getEmail(), password);
|
||||||
|
|
||||||
|
FilteredItemsQueryRest query = FilteredItemsQueryRest.of(null,
|
||||||
|
List.of(FilteredItemsQueryPredicate.of("dc.contributor.author", QueryOperator.EQUALS, "Doe, Jane")),
|
||||||
|
100, null, List.of("dc.subject"));
|
||||||
|
|
||||||
|
getClient(token).perform(get("/api/contentreport/filtereditems?" + query.toQueryString()))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need for a specific filtered item type...
|
||||||
|
private static Matcher<? super Object> matchItemProperties(Item item) {
|
||||||
|
return allOf(
|
||||||
|
hasJsonPath("$.uuid", is(item.getID().toString())),
|
||||||
|
hasJsonPath("$.name", is(item.getName())),
|
||||||
|
hasJsonPath("$.handle", is(item.getHandle())),
|
||||||
|
hasJsonPath("$.inArchive", is(item.isArchived())),
|
||||||
|
hasJsonPath("$.discoverable", is(item.isDiscoverable())),
|
||||||
|
hasJsonPath("$.withdrawn", is(item.isWithdrawn())),
|
||||||
|
hasJsonPath("$.lastModified", is(notNullValue())),
|
||||||
|
hasJsonPath("$.type", is("filtered-item"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestKit setupCollectionsAndItems() {
|
||||||
|
//** GIVEN **
|
||||||
|
//1. A community with two collections.
|
||||||
|
parentCommunity = CommunityBuilder.createCommunity(context)
|
||||||
|
.withName("My Community")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build();
|
||||||
|
Collection col2 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 2").build();
|
||||||
|
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDate pastDate = today.minusDays(10);
|
||||||
|
String fmtPastDate = DateTimeFormatter.ISO_DATE.format(pastDate);
|
||||||
|
LocalDate futureDate = today.plusDays(10);
|
||||||
|
String fmtFutureDate = DateTimeFormatter.ISO_DATE.format(futureDate);
|
||||||
|
|
||||||
|
//2. Three items, two of which are available and discoverable...
|
||||||
|
Item item1 = ItemBuilder.createItem(context, col1)
|
||||||
|
.withTitle("Public item 1")
|
||||||
|
.withIssueDate(fmtPastDate)
|
||||||
|
.withAuthor("Smith, Donald").withAuthor("Doe, John")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Item item2 = ItemBuilder.createItem(context, col2)
|
||||||
|
.withTitle("Public item 2")
|
||||||
|
.withIssueDate(fmtPastDate)
|
||||||
|
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
|
||||||
|
.withSubject("TestingForMore").withSubject("ExtraEntry")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// ... and one will be available in a few days.
|
||||||
|
Item item3 = ItemBuilder.createItem(context, col2)
|
||||||
|
.withTitle("Public item 3")
|
||||||
|
.withIssueDate(fmtFutureDate)
|
||||||
|
.withAuthor("Smith, Maria").withAuthor("Doe, Jane")
|
||||||
|
.withSubject("AnotherTest").withSubject("TestingForMore")
|
||||||
|
.withSubject("ExtraEntry")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TestKit kit = new TestKit();
|
||||||
|
kit.collections.add(col1);
|
||||||
|
kit.collections.add(col2);
|
||||||
|
kit.items.add(item1);
|
||||||
|
kit.items.add(item2);
|
||||||
|
kit.items.add(item3);
|
||||||
|
return kit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure to help trace back the created collections and items to perform the tests.
|
||||||
|
* In a future version (Java 17 or later), this class could be turned into a record.
|
||||||
|
*/
|
||||||
|
private static class TestKit {
|
||||||
|
|
||||||
|
public final List<Collection> collections = new ArrayList<>();
|
||||||
|
public final List<Item> items = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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.matcher;
|
||||||
|
|
||||||
|
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.contentreport.FilteredCollection;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to construct a Matcher for a FilteredCollectionRest.
|
||||||
|
* @author Jean-François Morin (Université Laval)
|
||||||
|
*/
|
||||||
|
public class ContentReportMatcher {
|
||||||
|
|
||||||
|
private ContentReportMatcher() { }
|
||||||
|
|
||||||
|
public static Matcher<? super Object> matchFilteredCollectionProperties(FilteredCollection collection) {
|
||||||
|
return allOf(
|
||||||
|
hasJsonPath("$.label", is(collection.getLabel())),
|
||||||
|
hasJsonPath("$.community_label", is(collection.getCommunityLabel())),
|
||||||
|
hasJsonPath("$.community_handle", is(collection.getCommunityHandle())),
|
||||||
|
hasJsonPath("$.nb_total_items", is(collection.getTotalItems())),
|
||||||
|
hasJsonPath("$.all_filters_value", is(collection.getAllFiltersValue()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<? super Object> matchFilteredCollectionSummary(int nbTotalItems, int nbFilteredItems) {
|
||||||
|
return allOf(
|
||||||
|
hasJsonPath("$.nb_total_items", is(nbTotalItems)),
|
||||||
|
hasJsonPath("$.all_filters_value", is(nbFilteredItems)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<? super Object> matchFilteredItemProperties(Item item) {
|
||||||
|
return allOf(
|
||||||
|
hasJsonPath("$.name", is(item.getName())),
|
||||||
|
hasJsonPath("$.inArchive", is(item.isArchived())),
|
||||||
|
hasJsonPath("$.discoverable", is(item.isDiscoverable())),
|
||||||
|
hasJsonPath("$.withdrawn", is(item.isWithdrawn())),
|
||||||
|
hasJsonPath("$.type", is("item"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -84,7 +84,7 @@ db.url = jdbc:postgresql://localhost:5432/dspace
|
|||||||
db.driver = org.postgresql.Driver
|
db.driver = org.postgresql.Driver
|
||||||
|
|
||||||
# PostgreSQL Database Dialect (for Hibernate)
|
# PostgreSQL Database Dialect (for Hibernate)
|
||||||
db.dialect = org.hibernate.dialect.PostgreSQL94Dialect
|
db.dialect = org.dspace.util.DSpacePostgreSQLDialect
|
||||||
|
|
||||||
# Database username and password
|
# Database username and password
|
||||||
db.username = dspace
|
db.username = dspace
|
||||||
@@ -1668,3 +1668,4 @@ include = ${module_dir}/usage-statistics.cfg
|
|||||||
include = ${module_dir}/versioning.cfg
|
include = ${module_dir}/versioning.cfg
|
||||||
include = ${module_dir}/workflow.cfg
|
include = ${module_dir}/workflow.cfg
|
||||||
include = ${module_dir}/external-providers.cfg
|
include = ${module_dir}/external-providers.cfg
|
||||||
|
include = ${module_dir}/contentreport.cfg
|
||||||
|
@@ -76,20 +76,10 @@ dspace.name = DSpace at My University
|
|||||||
# URL for connecting to database
|
# URL for connecting to database
|
||||||
db.url = jdbc:postgresql://localhost:5432/dspace
|
db.url = jdbc:postgresql://localhost:5432/dspace
|
||||||
|
|
||||||
# JDBC Driver for PostgreSQL
|
|
||||||
db.driver = org.postgresql.Driver
|
|
||||||
|
|
||||||
# PostgreSQL Database Dialect (for Hibernate)
|
|
||||||
db.dialect = org.hibernate.dialect.PostgreSQL94Dialect
|
|
||||||
|
|
||||||
# Database username and password
|
# Database username and password
|
||||||
db.username = dspace
|
db.username = dspace
|
||||||
db.password = dspace
|
db.password = dspace
|
||||||
|
|
||||||
# Database Schema name
|
|
||||||
# For PostgreSQL, this is often "public" (default schema)
|
|
||||||
db.schema = public
|
|
||||||
|
|
||||||
## Connection pool parameters
|
## Connection pool parameters
|
||||||
|
|
||||||
# Maximum number of DB connections in pool (default = 30)
|
# Maximum number of DB connections in pool (default = 30)
|
||||||
@@ -240,4 +230,4 @@ db.schema = public
|
|||||||
#spring.servlet.multipart.max-file-size = 512MB
|
#spring.servlet.multipart.max-file-size = 512MB
|
||||||
|
|
||||||
# Maximum size of a multipart request (i.e. max total size of all files in one request)
|
# Maximum size of a multipart request (i.e. max total size of all files in one request)
|
||||||
#spring.servlet.multipart.max-request-size = 512MB
|
#spring.servlet.multipart.max-request-size = 512MB
|
||||||
|
10
dspace/config/modules/contentreport.cfg
Normal file
10
dspace/config/modules/contentreport.cfg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#---------------------------------------------------------------#
|
||||||
|
#--------------CONTENT REPORTS CONFIGURATIONS-------------------#
|
||||||
|
#---------------------------------------------------------------#
|
||||||
|
# Used by dspace-server-webapp and the Angular UI #
|
||||||
|
# NOTE: This is currently a beta feature. #
|
||||||
|
#---------------------------------------------------------------#
|
||||||
|
|
||||||
|
# Configuration setting to trigger showing/hiding the Content Reports
|
||||||
|
# (REST endpoints on the REST side and menu section on the Angular side)
|
||||||
|
#contentreport.enable=true
|
@@ -54,4 +54,5 @@ rest.properties.exposed = google.recaptcha.mode
|
|||||||
rest.properties.exposed = cc.license.jurisdiction
|
rest.properties.exposed = cc.license.jurisdiction
|
||||||
rest.properties.exposed = identifiers.item-status.register-doi
|
rest.properties.exposed = identifiers.item-status.register-doi
|
||||||
rest.properties.exposed = authentication-password.domain.valid
|
rest.properties.exposed = authentication-password.domain.valid
|
||||||
rest.properties.exposed = handle.canonical.prefix
|
rest.properties.exposed = handle.canonical.prefix
|
||||||
|
rest.properties.exposed = contentreport.enable
|
||||||
|
@@ -77,6 +77,8 @@
|
|||||||
<constructor-arg name='configPrefix' value='solr'/>
|
<constructor-arg name='configPrefix' value='solr'/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean class="org.dspace.contentreport.ContentReportServiceImpl"/>
|
||||||
|
|
||||||
<!-- Ensure PluginService is initialized properly via init() method -->
|
<!-- Ensure PluginService is initialized properly via init() method -->
|
||||||
<bean class="org.dspace.core.LegacyPluginServiceImpl" init-method="init"/>
|
<bean class="org.dspace.core.LegacyPluginServiceImpl" init-method="init"/>
|
||||||
<bean class="org.dspace.core.LicenseServiceImpl"/>
|
<bean class="org.dspace.core.LicenseServiceImpl"/>
|
||||||
|
Reference in New Issue
Block a user