diff --git a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java index bea53c22ff..8e306efae0 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java @@ -20,11 +20,13 @@ import com.sun.syndication.feed.module.opensearch.OpenSearchModule; import com.sun.syndication.feed.module.opensearch.entity.OSQuery; import com.sun.syndication.feed.module.opensearch.impl.OpenSearchModuleImpl; import com.sun.syndication.io.FeedException; + import org.apache.logging.log4j.Logger; import org.dspace.app.util.service.OpenSearchService; import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -118,7 +120,7 @@ public class OpenSearchServiceImpl implements OpenSearchService { @Override public String getResultsString(Context context, String format, String query, int totalResults, int start, int pageSize, - DSpaceObject scope, List results, + IndexableObject scope, List results, Map labels) throws IOException { try { return getResults(context, format, query, totalResults, start, pageSize, scope, results, labels) @@ -132,7 +134,7 @@ public class OpenSearchServiceImpl implements OpenSearchService { @Override public Document getResultsDoc(Context context, String format, String query, int totalResults, int start, int pageSize, - DSpaceObject scope, List results, Map labels) + IndexableObject scope, List results, Map labels) throws IOException { try { return getResults(context, format, query, totalResults, start, pageSize, scope, results, labels) @@ -144,8 +146,8 @@ public class OpenSearchServiceImpl implements OpenSearchService { } protected SyndicationFeed getResults(Context context, String format, String query, int totalResults, int start, - int pageSize, - DSpaceObject scope, List results, Map labels) { + int pageSize, IndexableObject scope, + List results, Map labels) { // Encode results in requested format if ("rss".equals(format)) { format = "rss_2.0"; diff --git a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java index 3e0caee543..4bb89a1739 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java @@ -52,6 +52,7 @@ import org.dspace.content.service.ItemService; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -179,12 +180,12 @@ public class SyndicationFeed { * * @param request request * @param context context - * @param dso DSpaceObject + * @param dso the scope * @param items array of objects * @param labels label map */ - public void populate(HttpServletRequest request, Context context, DSpaceObject dso, - List items, Map labels) { + public void populate(HttpServletRequest request, Context context, IndexableObject dso, + List items, Map labels) { String logoURL = null; String objectURL = null; String defaultTitle = null; @@ -208,6 +209,7 @@ public class SyndicationFeed { if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) { podcastFeed = true; } + objectURL = resolveURL(request, col); } else if (dso.getType() == Constants.COMMUNITY) { Community comm = (Community) dso; defaultTitle = comm.getName(); @@ -217,8 +219,9 @@ public class SyndicationFeed { if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) { podcastFeed = true; } + objectURL = resolveURL(request, comm); } - objectURL = resolveURL(request, dso); + if (logo != null) { logoURL = urlOfBitstream(request, logo); } @@ -247,11 +250,11 @@ public class SyndicationFeed { // add entries for items if (items != null) { List entries = new ArrayList(); - for (DSpaceObject itemDSO : items) { - if (itemDSO.getType() != Constants.ITEM) { + for (IndexableObject idxObj : items) { + if (idxObj.getType() != Constants.ITEM) { continue; } - Item item = (Item) itemDSO; + Item item = (Item) idxObj; boolean hasDate = false; SyndEntry entry = new SyndEntryImpl(); entries.add(entry); diff --git a/dspace-api/src/main/java/org/dspace/app/util/Util.java b/dspace-api/src/main/java/org/dspace/app/util/Util.java index 7d4a5e8182..564300358c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/Util.java +++ b/dspace-api/src/main/java/org/dspace/app/util/Util.java @@ -473,6 +473,26 @@ public class Util { return toReturn; } + /** + * Split a list in an array of i sub-lists uniformly sized + * + * @param idsList the list to split + * @param i the number of sublists to return + * + * @return an array of sub-lists of fixed size + */ + public static List[] splitList(List idsList, int i) { + int setmin = idsList.size() / i; + List[] result = new List[i]; + int offset = 0; + for (int idx = 0; idx < i - 1; idx++) { + result[idx] = idsList.subList(offset, offset + setmin); + offset += setmin; + } + result[i - 1] = idsList.subList(offset, idsList.size()); + return result; + } + public static List differenceInSubmissionFields(Collection fromCollection, Collection toCollection) throws DCInputsReaderException { DCInputsReader reader = new DCInputsReader(); diff --git a/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java b/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java index 45259de8c0..03f41e535c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java +++ b/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java @@ -14,6 +14,7 @@ import java.util.Map; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.w3c.dom.Document; /** @@ -83,7 +84,7 @@ public interface OpenSearchService { * @param totalResults - the hit count * @param start - start result index * @param pageSize - page size - * @param scope - search scope, null or community/collection handle + * @param scope - search scope, null or the community/collection * @param results the retreived DSpace objects satisfying search * @param labels labels to apply - format specific * @return formatted search results @@ -91,7 +92,7 @@ public interface OpenSearchService { */ public String getResultsString(Context context, String format, String query, int totalResults, int start, int pageSize, - DSpaceObject scope, List results, + IndexableObject scope, List results, Map labels) throws IOException; /** @@ -103,7 +104,7 @@ public interface OpenSearchService { * @param totalResults - the hit count * @param start - start result index * @param pageSize - page size - * @param scope - search scope, null or community/collection handle + * @param scope - search scope, null or the community/collection * @param results the retreived DSpace objects satisfying search * @param labels labels to apply - format specific * @return formatted search results @@ -111,7 +112,7 @@ public interface OpenSearchService { */ public Document getResultsDoc(Context context, String format, String query, int totalResults, int start, int pageSize, - DSpaceObject scope, List results, Map labels) + IndexableObject scope, List results, Map labels) throws IOException; public DSpaceObject resolveScope(Context context, String scope) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 936a7d6492..2384a260da 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -767,4 +767,10 @@ public class AuthorizeServiceImpl implements AuthorizeService { return policy; } + @Override + public List getPoliciesActionFilterExceptRpType(Context c, DSpaceObject o, int actionID, + String rpType) throws SQLException { + return resourcePolicyService.findExceptRpType(c, o, actionID, rpType); + } + } diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index 4693599c31..a7a8a25bfc 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -307,4 +307,10 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { context.restoreAuthSystemState(); } } + + @Override + public List findExceptRpType(Context c, DSpaceObject o, int actionID, String rpType) + throws SQLException { + return resourcePolicyDAO.findByDSoAndActionExceptRpType(c, o, actionID, rpType); + } } diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java index ecaeef18e7..0ab7e0dd9b 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java @@ -66,4 +66,17 @@ public interface ResourcePolicyDAO extends GenericDAO { public void deleteByDsoEPersonPolicies(Context context, DSpaceObject dso, EPerson ePerson) throws SQLException; public void deleteByDsoAndTypeNotEqualsTo(Context c, DSpaceObject o, String type) throws SQLException; + + /** + * Return a list of policies for an object that match the action except the record labeled with the rpType + * + * @param c context + * @param o DSpaceObject policies relate to + * @param actionID action (defined in class Constants) + * @param rpType the resource policy type + * @return list of resource policies + * @throws SQLException if there's a database problem + */ + public List findByDSoAndActionExceptRpType(Context c, DSpaceObject o, int actionID, + String rpType) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java index a0f69dc526..0c254cbd3d 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java @@ -131,8 +131,7 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.actionId), action), criteriaBuilder .or(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.eperson), e), - criteriaBuilder - .in(resourcePolicyRoot.get(ResourcePolicy_.epersonGroup).in(groups))) + (resourcePolicyRoot.get(ResourcePolicy_.epersonGroup).in(groups))) ) ); return list(context, criteriaQuery, false, ResourcePolicy.class, 1, -1); @@ -201,4 +200,35 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO query.setParameter("rptype", type); query.executeUpdate(); } + + @Override + public List findByDSoAndActionExceptRpType(Context context, DSpaceObject dso, int action, + String rpType) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, ResourcePolicy.class); + + Root resourcePolicyRoot = criteriaQuery.from(ResourcePolicy.class); + criteriaQuery.select(resourcePolicyRoot); + if (rpType != null) { + criteriaQuery.where( + criteriaBuilder.and(criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.dSpaceObject), dso), + criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.actionId), action), + criteriaBuilder.or( + criteriaBuilder.notEqual(resourcePolicyRoot.get(ResourcePolicy_.rptype), + rpType), + criteriaBuilder.isNull(resourcePolicyRoot.get(ResourcePolicy_.rptype)) + ) + ) + ); + } else { + criteriaQuery.where( + criteriaBuilder.and( + criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.dSpaceObject), dso), + criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.actionId), action), + criteriaBuilder.isNotNull(resourcePolicyRoot.get(ResourcePolicy_.rptype)) + ) + ); + } + return list(context, criteriaQuery, false, ResourcePolicy.class, 1, -1); + } } diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index 092460f10f..9e739e2585 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -312,6 +312,18 @@ public interface AuthorizeService { */ public List getPoliciesActionFilter(Context c, DSpaceObject o, int actionID) throws SQLException; + /** + * Return a list of policies for an object that match the action except the record labeled with the rpType + * + * @param c context + * @param o DSpaceObject policies relate to + * @param actionID action (defined in class Constants) + * @param rpType the resource policy type + * @return list of resource policies + * @throws SQLException if there's a database problem + */ + public List getPoliciesActionFilterExceptRpType(Context c, DSpaceObject o, int actionID, + String rpType) throws SQLException; /** * Add policies to an object to match those from a previous object * diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java index 689898daa2..3da8beb130 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java @@ -76,4 +76,16 @@ public interface ResourcePolicyService extends DSpaceCRUDService public void removeDsoAndTypeNotEqualsToPolicies(Context c, DSpaceObject o, String type) throws SQLException, AuthorizeException; + /** + * Return a list of policies for an object that match the action except the record labeled with the rpType + * + * @param c context + * @param o DSpaceObject policies relate to + * @param actionID action (defined in class Constants) + * @param rpType the resource policy type + * @return list of resource policies + * @throws SQLException if there's a database problem + */ + public List findExceptRpType(Context c, DSpaceObject o, int actionID, String rpType) + throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowserScope.java b/dspace-api/src/main/java/org/dspace/browse/BrowserScope.java index aecfacd594..10cb23f8e9 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowserScope.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowserScope.java @@ -11,6 +11,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.sort.SortException; import org.dspace.sort.SortOption; @@ -114,6 +115,8 @@ public class BrowserScope { private String authority = null; + private String userLocale = null; + /** * Construct a new BrowserScope using the given Context * @@ -131,7 +134,7 @@ public class BrowserScope { * @param dso the container object; a Community or Collection * @throws BrowseException if browse error */ - public void setBrowseContainer(DSpaceObject dso) + public void setBrowseContainer(IndexableObject dso) throws BrowseException { if (dso instanceof Collection) { this.collection = (Collection) dso; @@ -582,4 +585,12 @@ public class BrowserScope { public void setAuthorityValue(String value) { authority = value; } + + public void setUserLocale(String userLocale) { + this.userLocale = userLocale; + } + + public String getUserLocale() { + return userLocale; + } } diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 98d5b8bbcb..ae491ad935 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -19,7 +19,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -29,6 +28,7 @@ import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.DiscoverResult.FacetResult; import org.dspace.discovery.DiscoverResult.SearchDocument; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; @@ -308,7 +308,7 @@ public class SolrBrowseDAO implements BrowseDAO { DiscoverResult resp = getSolrResponse(); List bitems = new ArrayList<>(); - for (DSpaceObject solrDoc : resp.getDspaceObjects()) { + for (IndexableObject solrDoc : resp.getIndexableObjects()) { // FIXME introduce project, don't retrieve Item immediately when // processing the query... Item item = (Item) solrDoc; @@ -332,7 +332,7 @@ public class SolrBrowseDAO implements BrowseDAO { } if (resp.getTotalSearchResults() > 0) { SearchDocument doc = resp.getSearchDocument( - resp.getDspaceObjects().get(0)).get(0); + resp.getIndexableObjects().get(0)).get(0); return (String) doc.getSearchFieldValues(column).get(0); } return null; diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index aed149b284..0d6a662b29 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.persistence.Cacheable; import javax.persistence.CascadeType; @@ -31,6 +32,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.Group; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.proxy.HibernateProxyHelper; @@ -53,7 +55,7 @@ import org.hibernate.proxy.HibernateProxyHelper; @Table(name = "collection") @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") -public class Collection extends DSpaceObject implements DSpaceObjectLegacySupport { +public class Collection extends DSpaceObject implements DSpaceObjectLegacySupport, IndexableObject { @Column(name = "collection_id", insertable = false, updatable = false) private Integer legacyId; diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 0539ae7908..acb44a31a5 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -277,6 +277,15 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i return collectionDAO.findByID(context, Collection.class, id); } + @Override + /** + * This method is an alias of the find method needed to avoid ambiguity between the IndexableObjectService interface + * and the DSpaceObjectService interface + */ + public Collection findIndexableObject(Context context, UUID id) throws SQLException { + return collectionDAO.findByID(context, Collection.class, id); + } + @Override public void setMetadata(Context context, Collection collection, String field, String value) throws MissingResourceException, SQLException { @@ -783,6 +792,15 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i return Constants.COLLECTION; } + @Override + /** + * This method is an alias of the getSupportsTypeConstant method needed to avoid ambiguity between the + * IndexableObjectService interface and the DSpaceObjectService interface + */ + public int getSupportsIndexableObjectTypeConstant() { + return getSupportsTypeConstant(); + } + @Override public List findAuthorized(Context context, Community community, int actionID) throws SQLException { List myResults = new ArrayList<>(); diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index b6dfefca12..aecd03fcd6 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.persistence.Cacheable; import javax.persistence.CascadeType; @@ -31,6 +32,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.Group; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.proxy.HibernateProxyHelper; @@ -49,7 +51,7 @@ import org.hibernate.proxy.HibernateProxyHelper; @Table(name = "community") @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") -public class Community extends DSpaceObject implements DSpaceObjectLegacySupport { +public class Community extends DSpaceObject implements DSpaceObjectLegacySupport, IndexableObject { /** * log4j category */ diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 8169375f15..242a5ff389 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -137,6 +137,15 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp return communityDAO.findByID(context, Community.class, id); } + @Override + /** + * This method is an alias of the find method needed to avoid ambiguity between the IndexableObjectService interface + * and the DSpaceObjectService interface + */ + public Community findIndexableObject(Context context, UUID id) throws SQLException { + return find(context, id); + } + @Override public List findAll(Context context) throws SQLException { MetadataField sortField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), @@ -511,6 +520,14 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp return Constants.COMMUNITY; } + @Override + /** + * This method is an alias of the getSupportsTypeConstant method needed to avoid ambiguity between the + * IndexableObjectService interface and the DSpaceObjectService interface + */ + public int getSupportsIndexableObjectTypeConstant() { + return getSupportsTypeConstant(); + } /** * Internal method to remove the community and all its children from the diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java index 738da8c171..896925fb1e 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -11,6 +11,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.UUID; + import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -26,7 +27,6 @@ import javax.persistence.Transient; import org.apache.commons.collections4.CollectionUtils; import org.dspace.authorize.ResourcePolicy; -import org.dspace.browse.BrowsableObject; import org.dspace.core.ReloadableEntity; import org.dspace.handle.Handle; import org.hibernate.annotations.GenericGenerator; @@ -37,8 +37,7 @@ import org.hibernate.annotations.GenericGenerator; @Entity @Inheritance(strategy = InheritanceType.JOINED) @Table(name = "dspaceobject") -public abstract class DSpaceObject implements Serializable, ReloadableEntity, - BrowsableObject { +public abstract class DSpaceObject implements Serializable, ReloadableEntity { @Id @GeneratedValue(generator = "system-uuid") @GenericGenerator(name = "system-uuid", strategy = "uuid2") diff --git a/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java b/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java index a0ba4c4769..df1d9c1623 100644 --- a/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java +++ b/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java @@ -10,7 +10,7 @@ package org.dspace.content; import java.io.Serializable; import java.sql.SQLException; -import org.dspace.browse.BrowsableObject; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.EPerson; /** @@ -20,7 +20,7 @@ import org.dspace.eperson.EPerson; * @author Robert Tansley * @version $Revision$ */ -public interface InProgressSubmission extends BrowsableObject { +public interface InProgressSubmission extends IndexableObject { /** * Get the internal ID of this submission * diff --git a/dspace-api/src/main/java/org/dspace/content/Item.java b/dspace-api/src/main/java/org/dspace/content/Item.java index 4546cf2bd2..adb2eaaacd 100644 --- a/dspace-api/src/main/java/org/dspace/content/Item.java +++ b/dspace-api/src/main/java/org/dspace/content/Item.java @@ -13,6 +13,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -34,6 +35,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.EPerson; import org.hibernate.proxy.HibernateProxyHelper; @@ -53,7 +55,7 @@ import org.hibernate.proxy.HibernateProxyHelper; */ @Entity @Table(name = "item") -public class Item extends DSpaceObject implements DSpaceObjectLegacySupport { +public class Item extends DSpaceObject implements DSpaceObjectLegacySupport, IndexableObject { /** * log4j logger diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 78e5eddcd1..d3021e95e9 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -172,6 +172,15 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It return item; } + @Override + /** + * This method is an alias of the find method needed to avoid ambiguity between the IndexableObjectService interface + * and the DSpaceObjectService interface + */ + public Item findIndexableObject(Context context, UUID id) throws SQLException { + return find(context, id); + } + @Override public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException { if (workspaceItem.getItem() != null) { @@ -658,6 +667,15 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It return Constants.ITEM; } + @Override + /** + * This method is an alias of the getSupportsTypeConstant method needed to avoid ambiguity between the + * IndexableObjectService interface and the DSpaceObjectService interface + */ + public int getSupportsIndexableObjectTypeConstant() { + return getSupportsTypeConstant(); + } + protected void rawDelete(Context context, Item item) throws AuthorizeException, SQLException, IOException { authorizeService.authorizeAction(context, item, Constants.REMOVE); diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java index c9db50454c..0a61f4d93a 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -27,10 +27,10 @@ import javax.persistence.SequenceGenerator; import javax.persistence.Table; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.dspace.browse.BrowsableObject; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.workflow.WorkflowItem; @@ -45,7 +45,7 @@ import org.hibernate.proxy.HibernateProxyHelper; @Entity @Table(name = "workspaceitem") public class WorkspaceItem - implements InProgressSubmission, Serializable, ReloadableEntity, BrowsableObject { + implements InProgressSubmission, Serializable, ReloadableEntity, IndexableObject { @Id @Column(name = "workspace_item_id", unique = true, nullable = false) diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index ace8bab0ab..42ff757446 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -61,12 +61,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { } @Override - public int getSupportsTypeConstant() { + public int getSupportsIndexableObjectTypeConstant() { return Constants.WORKSPACEITEM; } @Override - public WorkspaceItem find(Context context, Integer id) throws SQLException { + public WorkspaceItem find(Context context, int id) throws SQLException { WorkspaceItem workspaceItem = workspaceItemDAO.findByID(context, WorkspaceItem.class, id); if (workspaceItem == null) { @@ -83,6 +83,14 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { return workspaceItem; } + @Override + public WorkspaceItem findIndexableObject(Context context, Integer id) throws SQLException { + if (id != null) { + return find(context, id); + } + return null; + } + @Override public WorkspaceItem create(Context context, Collection collection, boolean template) throws AuthorizeException, SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index b574aef4a0..d6ba49f285 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -10,13 +10,11 @@ package org.dspace.content.factory; import java.io.Serializable; import java.util.List; -import org.dspace.browse.BrowsableObject; import org.dspace.content.DSpaceObject; import org.dspace.content.InProgressSubmission; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BrowsableObjectService; import org.dspace.content.service.BundleService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -25,6 +23,7 @@ import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.EntityService; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InProgressSubmissionService; +import org.dspace.content.service.IndexableObjectService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; @@ -35,6 +34,7 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.discovery.IndexableObject; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -46,7 +46,12 @@ import org.dspace.workflow.factory.WorkflowServiceFactory; */ public abstract class ContentServiceFactory { - public abstract List getBrowsableDSpaceObjectServices(); + /** + * Return the list of all the available implementations of the IndexableObjectService interface + * + * @return the list of IndexableObjectService + */ + public abstract List getIndexableObjectServices(); public abstract List> getDSpaceObjectServices(); @@ -115,15 +120,15 @@ public abstract class ContentServiceFactory { } @SuppressWarnings("unchecked") - public , PK extends Serializable> BrowsableObjectService - getBrowsableDSpaceObjectService(int type) { - for (int i = 0; i < getBrowsableDSpaceObjectServices().size(); i++) { - BrowsableObjectService objectService = getBrowsableDSpaceObjectServices().get(i); - if (objectService.getSupportsTypeConstant() == type) { - return (BrowsableObjectService) objectService; + public , PK extends Serializable> IndexableObjectService + getIndexableObjectService(int type) { + for (int i = 0; i < getIndexableObjectServices().size(); i++) { + IndexableObjectService objectService = getIndexableObjectServices().get(i); + if (objectService.getSupportsIndexableObjectTypeConstant() == type) { + return (IndexableObjectService) objectService; } } - throw new UnsupportedOperationException("Unknown Browsable DSpace type: " + type); + throw new UnsupportedOperationException("Unknown Findable Object type: " + type); } public DSpaceObjectLegacySupportService getDSpaceLegacyObjectService(int type) { diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java index 990a36ac26..e55b7bfe42 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java @@ -12,7 +12,6 @@ import java.util.List; import org.dspace.content.DSpaceObject; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BrowsableObjectService; import org.dspace.content.service.BundleService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -20,6 +19,7 @@ import org.dspace.content.service.DSpaceObjectLegacySupportService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.EntityService; import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.IndexableObjectService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; @@ -83,8 +83,8 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { private EntityService entityService; @Override - public List getBrowsableDSpaceObjectServices() { - return new DSpace().getServiceManager().getServicesByType(BrowsableObjectService.class); + public List getIndexableObjectServices() { + return new DSpace().getServiceManager().getServicesByType(IndexableObjectService.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/service/BrowsableObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/BrowsableObjectService.java deleted file mode 100644 index 578efed548..0000000000 --- a/dspace-api/src/main/java/org/dspace/content/service/BrowsableObjectService.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content.service; - -import java.io.Serializable; -import java.sql.SQLException; - -import org.dspace.browse.BrowsableObject; -import org.dspace.core.Context; - -/** - * Service interface class for any BrowsableDSpaceObject. - * All BrowsableObject service classes should implement this class since it offers some basic methods which all - * BrowsableObjects are required to have. - * - * @param class type - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -public interface BrowsableObjectService, PK extends Serializable> { - - - /** - * Generic find for when the precise type of a BDSO is not known, just the - * a pair of type number and database ID. - * - * @param context - the context - * @param id - id within table of type'd objects - * @return the object found, or null if it does not exist. - * @throws SQLException only upon failure accessing the database. - */ - public T find(Context context, PK id) throws SQLException; - - /** - * Returns the Constants which this service supports - * - * @return a org.dspace.core.Constants that represents a BrowsableDSpaceObject type - */ - public int getSupportsTypeConstant(); -} diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 525ec5ca89..063f217d79 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -13,6 +13,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.MissingResourceException; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -30,7 +31,8 @@ import org.dspace.eperson.Group; * @author kevinvandevelde at atmire.com */ public interface CollectionService - extends DSpaceObjectService, DSpaceObjectLegacySupportService { + extends DSpaceObjectService, DSpaceObjectLegacySupportService, + IndexableObjectService { /** * Create a new collection with a new ID. diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index 6284b27e25..3e9b372930 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.List; import java.util.MissingResourceException; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -27,7 +28,8 @@ import org.dspace.eperson.Group; * * @author kevinvandevelde at atmire.com */ -public interface CommunityService extends DSpaceObjectService, DSpaceObjectLegacySupportService { +public interface CommunityService extends DSpaceObjectService, DSpaceObjectLegacySupportService, + IndexableObjectService { /** diff --git a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java index 96ccd3c83a..753516a373 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java @@ -28,7 +28,17 @@ import org.dspace.core.Context; * @param class type * @author kevinvandevelde at atmire.com */ -public interface DSpaceObjectService extends BrowsableObjectService { +public interface DSpaceObjectService { + + /** + * Generic find for when the precise type of an Entity is not known + * + * @param context - the context + * @param uuid - uuid within table of type'd dspace objects + * @return the dspace object found, or null if it does not exist. + * @throws SQLException only upon failure accessing the database. + */ + public T find(Context context, UUID uuid) throws SQLException; /** * Get a proper name for the object. This may return null. @@ -40,17 +50,6 @@ public interface DSpaceObjectService extends BrowsableOb */ public abstract String getName(T dso); - /** - * Generic find for when the precise type of a BDSO is not known, just the - * a pair of type number and database ID. - * - * @param context - the context - * @param id - id within table of type'd objects - * @return the object found, or null if it does not exist. - * @throws SQLException only upon failure accessing the database. - */ - public T find(Context context, UUID id) throws SQLException; - /** * Tries to lookup all Identifiers of this DSpaceObject. * @@ -371,7 +370,6 @@ public interface DSpaceObjectService extends BrowsableOb public void delete(Context context, T dso) throws SQLException, AuthorizeException, IOException; - void addAndShiftRightMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, String value, String authority, int confidence, int index) throws SQLException; @@ -380,4 +378,11 @@ public interface DSpaceObjectService extends BrowsableOb void moveMetadata(Context context, T dso, String schema, String element, String qualifier, int from, int to) throws SQLException; + + /** + * Returns the Constants which this service supports + * + * @return a org.dspace.core.Constants that represents a IndexableObject type + */ + public int getSupportsTypeConstant(); } diff --git a/dspace-api/src/main/java/org/dspace/content/service/InProgressSubmissionService.java b/dspace-api/src/main/java/org/dspace/content/service/InProgressSubmissionService.java index 4ff37fbb08..627e27bad6 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/InProgressSubmissionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/InProgressSubmissionService.java @@ -26,7 +26,7 @@ import org.dspace.core.Context; * @author kevinvandevelde at atmire.com */ public interface InProgressSubmissionService, ID extends Serializable> - extends BrowsableObjectService { + extends IndexableObjectService { /** * Deletes submission wrapper, doesn't delete item contents diff --git a/dspace-api/src/main/java/org/dspace/content/service/IndexableObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/IndexableObjectService.java new file mode 100644 index 0000000000..929f244206 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/IndexableObjectService.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.service; + +import java.io.Serializable; +import java.sql.SQLException; + +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; + +/** + * Base Service interface class for any IndexableObject. The name of the methods contains IndexableObject to avoid + * ambiguity reference as some implementation supports both this interface than the DSpaceObectService interface + * + * @param + * class type of the indexable object + * @param + * class type of the primary key + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public interface IndexableObjectService, PK extends Serializable> { + + /** + * Generic find for when the precise type of an IndexableObject is not known + * + * @param context - the context + * @param id - id within table of type'd indexable objects + * @return the indexable object found, or null if it does not exist. + * @throws SQLException only upon failure accessing the database. + */ + public T findIndexableObject(Context context, PK id) throws SQLException; + + /** + * Returns the Constants which this service supports + * + * @return a org.dspace.core.Constants that represents a IndexableObject type + */ + public int getSupportsIndexableObjectTypeConstant(); +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index ef53592573..05992764c1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -39,7 +39,8 @@ import org.dspace.eperson.Group; * * @author kevinvandevelde at atmire.com */ -public interface ItemService extends DSpaceObjectService, DSpaceObjectLegacySupportService { +public interface ItemService + extends DSpaceObjectService, DSpaceObjectLegacySupportService, IndexableObjectService { public Thumbnail getThumbnail(Context context, Item item, boolean requireOriginal) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java index 1ecddec8e4..6394f0eb17 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java @@ -29,6 +29,17 @@ import org.dspace.workflow.WorkflowItem; */ public interface WorkspaceItemService extends InProgressSubmissionService { + /** + * Get a workspace item from the database. The item, collection and + * submitter are loaded into memory. + * + * @param context DSpace context object + * @param id ID of the workspace item + * @return the workspace item, or null if the ID is invalid. + * @throws SQLException if database error + */ + public WorkspaceItem find(Context context, int id) throws SQLException; + /** * Create a new workspace item, with a new ID. An Item is also created. The * submitter is the current user in the context. diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java index 94a41f6195..a21c33cdd1 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java @@ -69,6 +69,8 @@ public class DiscoverQuery { **/ private Map> properties; + private String discoveryConfigurationName; + public DiscoverQuery() { //Initialize all our lists this.filterQueries = new ArrayList(); @@ -331,6 +333,7 @@ public class DiscoverQuery { // Example: 2001 and a gap from 10 we need the following result: 2010 - 2000 ; 2000 - 1990 hence the top // year int topYear = getTopYear(newestYear, gap); + if (gap == 1) { //We need a list of our years //We have a date range add faceting for our field @@ -376,4 +379,22 @@ public class DiscoverQuery { return (int) (Math.ceil((float) (newestYear) / gap) * gap); } + /** + * Return the name of discovery configuration used by this query + * + * @return the discovery configuration name used + */ + public String getDiscoveryConfigurationName() { + return discoveryConfigurationName; + } + + /** + * Set the name of discovery configuration to use to run this query + * + * @param discoveryConfigurationName + * the name of the discovery configuration to use to run this query + */ + public void setDiscoveryConfigurationName(String discoveryConfigurationName) { + this.discoveryConfigurationName = discoveryConfigurationName; + } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java index 9004b27f95..a6bfa0b2b5 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java @@ -15,7 +15,6 @@ import java.util.List; import java.util.Map; import org.apache.commons.collections4.ListUtils; -import org.dspace.content.DSpaceObject; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; @@ -28,32 +27,31 @@ public class DiscoverResult { private long totalSearchResults; private int start; - private List dspaceObjects; + private List indexableObjects; private Map> facetResults; + /** - * A map that contains all the documents sougth after, the key is a string representation of the DSpace object + * A map that contains all the documents sougth after, the key is a string representation of the Indexable Object */ private Map> searchDocuments; private int maxResults = -1; private int searchTime; - private Map highlightedResults; + private Map highlightedResults; private String spellCheckQuery; - public DiscoverResult() { - dspaceObjects = new ArrayList(); + indexableObjects = new ArrayList(); facetResults = new LinkedHashMap>(); searchDocuments = new LinkedHashMap>(); - highlightedResults = new HashMap(); + highlightedResults = new HashMap(); } - - public void addDSpaceObject(DSpaceObject dso) { - this.dspaceObjects.add(dso); + public void addIndexableObject(IndexableObject idxObj) { + this.indexableObjects.add(idxObj); } - public List getDspaceObjects() { - return dspaceObjects; + public List getIndexableObjects() { + return indexableObjects; } public long getTotalSearchResults() { @@ -107,19 +105,19 @@ public class DiscoverResult { public List getFacetResult(DiscoverySearchFilterFacet field) { List facetValues = getFacetResult(field.getIndexFieldName()); - //Check if we are dealing with a date, sometimes the facet values arrive as dates ! + // Check if we are dealing with a date, sometimes the facet values arrive as dates ! if (facetValues.size() == 0 && field.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { facetValues = getFacetResult(field.getIndexFieldName() + ".year"); } return ListUtils.emptyIfNull(facetValues); } - public DSpaceObjectHighlightResult getHighlightedResults(DSpaceObject dso) { - return highlightedResults.get(dso.getHandle()); + public IndexableObjectHighlightResult getHighlightedResults(IndexableObject dso) { + return highlightedResults.get(dso.getUniqueIndexID()); } - public void addHighlightedResult(DSpaceObject dso, DSpaceObjectHighlightResult highlightedResult) { - this.highlightedResults.put(dso.getHandle(), highlightedResult); + public void addHighlightedResult(IndexableObject dso, IndexableObjectHighlightResult highlightedResult) { + this.highlightedResults.put(dso.getUniqueIndexID(), highlightedResult); } public static final class FacetResult { @@ -131,7 +129,7 @@ public class DiscoverResult { private String fieldType; public FacetResult(String asFilterQuery, String displayedValue, String authorityKey, String sortValue, - long count, String fieldType) { + long count, String fieldType) { this.asFilterQuery = asFilterQuery; this.displayedValue = displayedValue; this.authorityKey = authorityKey; @@ -141,6 +139,10 @@ public class DiscoverResult { } public String getAsFilterQuery() { + if (asFilterQuery == null) { + // missing facet filter query + return "[* TO *]"; + } return asFilterQuery; } @@ -161,7 +163,7 @@ public class DiscoverResult { } public String getFilterType() { - return authorityKey != null ? "authority" : "equals"; + return authorityKey != null ? "authority" : asFilterQuery != null ? "equals" : "notequals"; } public String getFieldType() { @@ -177,30 +179,51 @@ public class DiscoverResult { this.spellCheckQuery = spellCheckQuery; } - public static final class DSpaceObjectHighlightResult { - private DSpaceObject dso; + /** + * An utility class to represent the highlighting section of a Discovery Search + * + */ + public static final class IndexableObjectHighlightResult { + private IndexableObject indexableObject; private Map> highlightResults; - public DSpaceObjectHighlightResult(DSpaceObject dso, Map> highlightResults) { - this.dso = dso; + public IndexableObjectHighlightResult(IndexableObject idxObj, Map> highlightResults) { + this.indexableObject = idxObj; this.highlightResults = highlightResults; } - public DSpaceObject getDso() { - return dso; + /** + * Return the indexable object that the highlighting snippets refer to + * + * @return the indexable object + */ + public IndexableObject getIndexableObject() { + return indexableObject; } + /** + * The matching snippets for a specific metadata ignoring any authority value + * + * @param metadataKey + * the metadata where the snippets have been found + * @return the matching snippets + */ public List getHighlightResults(String metadataKey) { return highlightResults.get(metadataKey); } + /** + * All the matching snippets in whatever metadata ignoring any authority value + * + * @return All the matching snippets + */ public Map> getHighlightResults() { return highlightResults; } } - public void addSearchDocument(DSpaceObject dso, SearchDocument searchDocument) { - String dsoString = SearchDocument.getDspaceObjectStringRepresentation(dso); + public void addSearchDocument(IndexableObject dso, SearchDocument searchDocument) { + String dsoString = SearchDocument.getIndexableObjectStringRepresentation(dso); List docs = searchDocuments.get(dsoString); if (docs == null) { docs = new ArrayList(); @@ -212,11 +235,12 @@ public class DiscoverResult { /** * Returns all the sought after search document values * - * @param dso the dspace object we want our search documents for + * @param idxObj + * the indexable object we want our search documents for * @return the search documents list */ - public List getSearchDocument(DSpaceObject dso) { - String dsoString = SearchDocument.getDspaceObjectStringRepresentation(dso); + public List getSearchDocument(IndexableObject idxObj) { + String dsoString = SearchDocument.getIndexableObjectStringRepresentation(idxObj); List result = searchDocuments.get(dsoString); if (result == null) { return new ArrayList(); @@ -256,8 +280,8 @@ public class DiscoverResult { } } - public static String getDspaceObjectStringRepresentation(DSpaceObject dso) { - return dso.getType() + ":" + dso.getID(); + public static String getIndexableObjectStringRepresentation(IndexableObject idxObj) { + return idxObj.getType() + ":" + idxObj.getID(); } } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/FacetYearRange.java b/dspace-api/src/main/java/org/dspace/discovery/FacetYearRange.java index 311d1814c5..05972baf7f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/FacetYearRange.java +++ b/dspace-api/src/main/java/org/dspace/discovery/FacetYearRange.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; @@ -46,8 +45,8 @@ public class FacetYearRange { return oldestYear != -1 && newestYear != -1; } - public void calculateRange(Context context, List filterQueries, DSpaceObject scope, - SearchService searchService) throws SearchServiceException { + public void calculateRange(Context context, List filterQueries, IndexableObject scope, + SearchService searchService, DiscoverQuery parentQuery) throws SearchServiceException { dateFacet = facet.getIndexFieldName() + ".year"; //Get a range query so we can create facet queries ranging from our first to our last date //Attempt to determine our oldest & newest year by checking for previously selected filters @@ -55,7 +54,7 @@ public class FacetYearRange { //Check if we have found a range, if not then retrieve our first & last year using Solr if (oldestYear == -1 && newestYear == -1) { - calculateNewRangeBasedOnSearchIndex(context, filterQueries, scope, searchService); + calculateNewRangeBasedOnSearchIndex(context, filterQueries, scope, searchService, parentQuery); } } @@ -93,9 +92,11 @@ public class FacetYearRange { } } - private void calculateNewRangeBasedOnSearchIndex(Context context, List filterQueries, DSpaceObject scope, - SearchService searchService) throws SearchServiceException { + private void calculateNewRangeBasedOnSearchIndex(Context context, List filterQueries, + IndexableObject scope, SearchService searchService, + DiscoverQuery parentQuery) throws SearchServiceException { DiscoverQuery yearRangeQuery = new DiscoverQuery(); + yearRangeQuery.setDiscoveryConfigurationName(parentQuery.getDiscoveryConfigurationName()); yearRangeQuery.setMaxResults(1); //Set our query to anything that has this value yearRangeQuery.addFieldPresentQueries(dateFacet); @@ -105,9 +106,9 @@ public class FacetYearRange { yearRangeQuery.addSearchField(dateFacet); DiscoverResult lastYearResult = searchService.search(context, scope, yearRangeQuery); - if (0 < lastYearResult.getDspaceObjects().size()) { + if (0 < lastYearResult.getIndexableObjects().size()) { List searchDocuments = lastYearResult - .getSearchDocument(lastYearResult.getDspaceObjects().get(0)); + .getSearchDocument(lastYearResult.getIndexableObjects().get(0)); if (0 < searchDocuments.size() && 0 < searchDocuments.get(0).getSearchFieldValues(dateFacet).size()) { oldestYear = Integer.parseInt(searchDocuments.get(0).getSearchFieldValues(dateFacet).get(0)); } @@ -115,9 +116,9 @@ public class FacetYearRange { //Now get the first year yearRangeQuery.setSortField(dateFacet + "_sort", DiscoverQuery.SORT_ORDER.desc); DiscoverResult firstYearResult = searchService.search(context, scope, yearRangeQuery); - if (0 < firstYearResult.getDspaceObjects().size()) { + if (0 < firstYearResult.getIndexableObjects().size()) { List searchDocuments = firstYearResult - .getSearchDocument(firstYearResult.getDspaceObjects().get(0)); + .getSearchDocument(firstYearResult.getIndexableObjects().get(0)); if (0 < searchDocuments.size() && 0 < searchDocuments.get(0).getSearchFieldValues(dateFacet).size()) { newestYear = Integer.parseInt(searchDocuments.get(0).getSearchFieldValues(dateFacet).get(0)); } diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 89b53d6d44..33adb009bb 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -10,6 +10,7 @@ package org.dspace.discovery; import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; +import java.util.UUID; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; @@ -19,7 +20,6 @@ import org.apache.commons.cli.PosixParser; import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.Community; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -59,7 +59,7 @@ public class IndexClient { context.turnOffAuthorisationSystem(); String usage = "org.dspace.discovery.IndexClient [-cbhf] | [-r ] | [-i ] or nothing to " + - "update/clean an existing index."; + "update/clean an existing index."; Options options = new Options(); HelpFormatter formatter = new HelpFormatter(); CommandLine line = null; @@ -72,10 +72,10 @@ public class IndexClient { .create("r")); options.addOption(OptionBuilder - .withArgName("handle to add or update") + .withArgName("handle or uuid to add or update") .hasArg(true) .withDescription( - "add or update an Item, Collection or Community based on its handle") + "add or update an Item, Collection or Community based on its handle or uuid") .create("i")); options.addOption(OptionBuilder @@ -151,17 +151,36 @@ public class IndexClient { } else if (line.hasOption('s')) { checkRebuildSpellCheck(line, indexer); } else if (line.hasOption('i')) { - final String handle = line.getOptionValue('i'); - final DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService() - .resolveToObject(context, handle); - if (dso == null) { - throw new IllegalArgumentException("Cannot resolve " + handle + " to a DSpace object"); + final String param = line.getOptionValue('i'); + UUID uuid = null; + try { + uuid = UUID.fromString(param); + } catch (Exception e) { + // nothing to do, it should be an handle } - log.info("Forcibly Indexing " + handle); + IndexableObject dso = null; + if (uuid != null) { + dso = ContentServiceFactory.getInstance().getItemService().find(context, uuid); + if (dso == null) { + // it could be a community + dso = ContentServiceFactory.getInstance().getCommunityService().find(context, uuid); + if (dso == null) { + // it could be a collection + dso = ContentServiceFactory.getInstance().getCollectionService().find(context, uuid); + } + } + } else { + dso = (IndexableObject) HandleServiceFactory.getInstance() + .getHandleService().resolveToObject(context, param); + } + if (dso == null) { + throw new IllegalArgumentException("Cannot resolve " + param + " to a DSpace object"); + } + log.info("Indexing " + param + " force " + line.hasOption("f")); final long startTimeMillis = System.currentTimeMillis(); final long count = indexAll(indexer, ContentServiceFactory.getInstance().getItemService(), context, dso); final long seconds = (System.currentTimeMillis() - startTimeMillis) / 1000; - log.info("Indexed " + count + " DSpace object" + (count > 1 ? "s" : "") + " in " + seconds + " seconds"); + log.info("Indexed " + count + " object" + (count > 1 ? "s" : "") + " in " + seconds + " seconds"); } else { log.info("Updating and Cleaning Index"); indexer.cleanIndex(line.hasOption("f")); @@ -186,7 +205,7 @@ public class IndexClient { private static long indexAll(final IndexingService indexingService, final ItemService itemService, final Context context, - final DSpaceObject dso) + final IndexableObject dso) throws IOException, SearchServiceException, SQLException { long count = 0; diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java index 9117dd7ad2..29c857fa9b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java @@ -33,10 +33,10 @@ public class IndexEventConsumer implements Consumer { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IndexEventConsumer.class); // collect Items, Collections, Communities that need indexing - private Set objectsToUpdate = null; + private Set objectsToUpdate = null; - // handles to delete since IDs are not useful by now. - private Set handlesToDelete = null; + // unique search IDs to delete + private Set uniqueIdsToDelete = null; IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName(IndexingService.class.getName(), @@ -58,8 +58,8 @@ public class IndexEventConsumer implements Consumer { public void consume(Context ctx, Event event) throws Exception { if (objectsToUpdate == null) { - objectsToUpdate = new HashSet(); - handlesToDelete = new HashSet(); + objectsToUpdate = new HashSet(); + uniqueIdsToDelete = new HashSet(); } int st = event.getSubjectType(); @@ -107,7 +107,7 @@ public class IndexEventConsumer implements Consumer { + ", perhaps it has been deleted."); } else { log.debug("consume() adding event to update queue: " + event.toString()); - objectsToUpdate.add(subject); + objectsToUpdate.add((IndexableObject)subject); } break; @@ -120,17 +120,17 @@ public class IndexEventConsumer implements Consumer { + ", perhaps it has been deleted."); } else { log.debug("consume() adding event to update queue: " + event.toString()); - objectsToUpdate.add(object); + objectsToUpdate.add((IndexableObject)object); } break; case Event.DELETE: - String detail = event.getDetail(); - if (detail == null) { - log.warn("got null detail on DELETE event, skipping it."); + if (event.getSubjectType() == -1 || event.getSubjectID() == null) { + log.warn("got null subject type and/or ID on DELETE event, skipping it."); } else { + String detail = event.getSubjectType() + "-" + event.getSubjectID().toString(); log.debug("consume() adding event to delete queue: " + event.toString()); - handlesToDelete.add(detail); + uniqueIdsToDelete.add(detail); } break; default: @@ -151,37 +151,37 @@ public class IndexEventConsumer implements Consumer { @Override public void end(Context ctx) throws Exception { - if (objectsToUpdate != null && handlesToDelete != null) { + if (objectsToUpdate != null && uniqueIdsToDelete != null) { // update the changed Items not deleted because they were on create list - for (DSpaceObject o : objectsToUpdate) { + for (IndexableObject iu : objectsToUpdate) { /* we let all types through here and * allow the search indexer to make * decisions on indexing and/or removal */ - DSpaceObject iu = ctx.reloadEntity(o); - String hdl = iu.getHandle(); - if (hdl != null && !handlesToDelete.contains(hdl)) { + iu = ctx.reloadEntity(iu); + String uniqueIndexID = iu.getUniqueIndexID(); + if (uniqueIndexID != null && !uniqueIdsToDelete.contains(uniqueIndexID)) { try { - indexer.indexContent(ctx, iu, true); + indexer.indexContent(ctx, iu, true, true); log.debug("Indexed " + Constants.typeText[iu.getType()] + ", id=" + String.valueOf(iu.getID()) - + ", handle=" + hdl); + + ", unique_id=" + uniqueIndexID); } catch (Exception e) { log.error("Failed while indexing object: ", e); } } } - for (String hdl : handlesToDelete) { + for (String uid : uniqueIdsToDelete) { try { - indexer.unIndexContent(ctx, hdl, true); + indexer.unIndexContent(ctx, uid, true); if (log.isDebugEnabled()) { - log.debug("UN-Indexed Item, handle=" + hdl); + log.debug("UN-Indexed Item, handle=" + uid); } } catch (Exception e) { - log.error("Failed while UN-indexing object: " + hdl, e); + log.error("Failed while UN-indexing object: " + uid, e); } } @@ -190,7 +190,7 @@ public class IndexEventConsumer implements Consumer { // "free" the resources objectsToUpdate = null; - handlesToDelete = null; + uniqueIdsToDelete = null; } @Override diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowsableObject.java b/dspace-api/src/main/java/org/dspace/discovery/IndexableObject.java similarity index 80% rename from dspace-api/src/main/java/org/dspace/browse/BrowsableObject.java rename to dspace-api/src/main/java/org/dspace/discovery/IndexableObject.java index 59e83858ae..fe67d9787b 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowsableObject.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexableObject.java @@ -5,21 +5,22 @@ * * http://www.dspace.org/license/ */ -package org.dspace.browse; +package org.dspace.discovery; import java.io.Serializable; import org.dspace.core.Constants; +import org.dspace.core.ReloadableEntity; /** - * This is the basic interface that a data model entity need to implement to support browsing/retrieval + * This is the basic interface that a data model entity need to implement to be indexable in Discover * * @author Andrea Bollini (andrea.bollini at 4science.it) * * @param * the Class of the primary key */ -public interface BrowsableObject { +public interface IndexableObject extends ReloadableEntity { /** * @@ -27,12 +28,6 @@ public interface BrowsableObject { */ public int getType(); - /** - * - * @return the primary key of the Entity instance - */ - public PK getID(); - /** * * @return an unique id to index diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java index 18407b8001..253008105b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java @@ -10,11 +10,10 @@ package org.dspace.discovery; import java.io.IOException; import java.sql.SQLException; -import org.dspace.content.DSpaceObject; import org.dspace.core.Context; /** - * Interface used for indexing dspaceobject into discovery + * Interface used for indexing IndexableObject into discovery * * @author Kevin Van de Velde (kevin at atmire dot com) * @author Mark Diggory (markd at atmire dot com) @@ -22,28 +21,28 @@ import org.dspace.core.Context; */ public interface IndexingService { - void indexContent(Context context, DSpaceObject dso) + void indexContent(Context context, IndexableObject dso) throws SQLException; - void indexContent(Context context, DSpaceObject dso, + void indexContent(Context context, IndexableObject dso, boolean force) throws SQLException; - void indexContent(Context context, DSpaceObject dso, + void indexContent(Context context, IndexableObject dso, boolean force, boolean commit) throws SQLException, SearchServiceException; - void unIndexContent(Context context, DSpaceObject dso) + void unIndexContent(Context context, IndexableObject dso) throws SQLException, IOException; - void unIndexContent(Context context, DSpaceObject dso, boolean commit) + void unIndexContent(Context context, IndexableObject dso, boolean commit) throws SQLException, IOException; - void unIndexContent(Context context, String handle) - throws SQLException, IOException; + void unIndexContent(Context context, String uniqueSearchID) + throws IOException; - void unIndexContent(Context context, String handle, boolean commit) - throws SQLException, IOException; + void unIndexContent(Context context, String uniqueSearchID, boolean commit) + throws IOException; - void reIndexContent(Context context, DSpaceObject dso) + void reIndexContent(Context context, IndexableObject dso) throws SQLException, IOException; void createIndex(Context context) throws SQLException, IOException; @@ -52,9 +51,14 @@ public interface IndexingService { void updateIndex(Context context, boolean force); + void updateIndex(Context context, boolean force, int type); + void cleanIndex(boolean force) throws IOException, SQLException, SearchServiceException; + void cleanIndex(boolean force, int type) throws IOException, + SQLException, SearchServiceException; + void commit() throws SearchServiceException; void optimize() throws SearchServiceException; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchService.java b/dspace-api/src/main/java/org/dspace/discovery/SearchService.java index a955234e1e..596e784bf7 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchService.java @@ -7,11 +7,9 @@ */ package org.dspace.discovery; -import java.io.InputStream; import java.sql.SQLException; import java.util.List; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.configuration.DiscoveryMoreLikeThisConfiguration; @@ -50,7 +48,7 @@ public interface SearchService { * @return discovery search result object * @throws SearchServiceException if search error */ - DiscoverResult search(Context context, DSpaceObject dso, DiscoverQuery query) + DiscoverResult search(Context context, IndexableObject dso, DiscoverQuery query) throws SearchServiceException; /** @@ -74,19 +72,11 @@ public interface SearchService { * @return discovery search result object * @throws SearchServiceException if search error */ - DiscoverResult search(Context context, DSpaceObject dso, DiscoverQuery query, boolean includeWithdrawn) + DiscoverResult search(Context context, IndexableObject dso, DiscoverQuery query, boolean includeWithdrawn) throws SearchServiceException; - - InputStream searchJSON(Context context, DiscoverQuery query, String jsonIdentifier) throws SearchServiceException; - - InputStream searchJSON(Context context, DiscoverQuery query, DSpaceObject dso, String jsonIdentifier) - throws SearchServiceException; - - - List search(Context context, String query, String orderfield, boolean ascending, int offset, int max, - String... filterquery); - + List search(Context context, String query, String orderfield, boolean ascending, int offset, + int max, String... filterquery); /** * Transforms the given string field and value into a filter query @@ -138,8 +128,9 @@ public interface SearchService { */ String escapeQueryChars(String query); - FacetYearRange getFacetYearRange(Context context, DSpaceObject scope, DiscoverySearchFilterFacet facet, - List filterQueries) throws SearchServiceException; + FacetYearRange getFacetYearRange(Context context, IndexableObject scope, DiscoverySearchFilterFacet facet, + List filterQueries, DiscoverQuery parentQuery) + throws SearchServiceException; /** * This method returns us either the highest or lowest value for the field that we give to it diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java index aa465dd1bc..7fa37e2cef 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java @@ -8,6 +8,7 @@ package org.dspace.discovery; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -16,10 +17,12 @@ import java.util.Map; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationService; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.workflow.WorkflowItem; /** * Util methods used by discovery @@ -48,13 +51,43 @@ public class SearchUtils { } public static DiscoveryConfiguration getDiscoveryConfiguration() { - return getDiscoveryConfiguration(null); + return getDiscoveryConfiguration(null, null); } public static DiscoveryConfiguration getDiscoveryConfiguration(DSpaceObject dso) { + return getDiscoveryConfiguration(null, dso); + } + + /** + * Return the discovery configuration to use in a specific scope for the king of search identified by the prefix. A + * null prefix mean the normal query, other predefined values are workspace or workflow + * + * @param prefix + * the namespace of the configuration to lookup if any + * @param dso + * the DSpaceObject + * @return the discovery configuration for the specified scope + */ + public static DiscoveryConfiguration getDiscoveryConfiguration(String prefix, DSpaceObject dso) { + if (prefix != null) { + return getDiscoveryConfigurationByName(dso != null ? prefix + "." + dso.getHandle() : prefix); + } else { + return getDiscoveryConfigurationByName(dso != null ? dso.getHandle() : null); + } + } + + /** + * Return the discovery configuration identified by the specified name + * + * @param configurationName the configuration name assigned to the bean in the + * discovery.xml + * @return the discovery configuration + */ + public static DiscoveryConfiguration getDiscoveryConfigurationByName( + String configurationName) { DiscoveryConfigurationService configurationService = getConfigurationService(); - return configurationService.getDiscoveryConfiguration(dso); + return configurationService.getDiscoveryConfiguration(configurationName); } public static DiscoveryConfigurationService getConfigurationService() { @@ -76,23 +109,57 @@ public class SearchUtils { * @throws SQLException An exception that provides information on a database access error or other errors. */ public static List getAllDiscoveryConfigurations(Item item) throws SQLException { + List collections = item.getCollections(); + return getAllDiscoveryConfigurations(null, collections, item); + } + + /** + * Return all the discovery configuration applicable to the provided workspace item + * @param witem a workspace item + * @return a list of discovery configuration + * @throws SQLException + */ + public static List getAllDiscoveryConfigurations(WorkspaceItem witem) throws SQLException { + List collections = new ArrayList(); + collections.add(witem.getCollection()); + return getAllDiscoveryConfigurations("workspace", collections, witem.getItem()); + } + + /** + * Return all the discovery configuration applicable to the provided workflow item + * @param witem a workflow item + * @return a list of discovery configuration + * @throws SQLException + */ + public static List getAllDiscoveryConfigurations(WorkflowItem witem) throws SQLException { + List collections = new ArrayList(); + collections.add(witem.getCollection()); + return getAllDiscoveryConfigurations("workflow", collections, witem.getItem()); + } + + private static List getAllDiscoveryConfigurations(String prefix, + List collections, Item item) + throws SQLException { Map result = new HashMap(); - List collections = item.getCollections(); for (Collection collection : collections) { - DiscoveryConfiguration configuration = getDiscoveryConfiguration(collection); + DiscoveryConfiguration configuration = getDiscoveryConfiguration(prefix, collection); if (!result.containsKey(configuration.getId())) { result.put(configuration.getId(), configuration); } } //Also add one for the default - DiscoveryConfiguration configuration = getDiscoveryConfiguration(null); - if (!result.containsKey(configuration.getId())) { - result.put(configuration.getId(), configuration); - } + addConfigurationIfExists(result, prefix); return Arrays.asList(result.values().toArray(new DiscoveryConfiguration[result.size()])); } + private static void addConfigurationIfExists(Map result, String confName) { + DiscoveryConfiguration configurationExtra = getDiscoveryConfigurationByName(confName); + if (!result.containsKey(configurationExtra.getId())) { + result.put(configurationExtra.getId(), configurationExtra); + } + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceContentInOriginalBundleFilterPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceContentInOriginalBundleFilterPlugin.java index c43b0e6245..d389b53538 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceContentInOriginalBundleFilterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceContentInOriginalBundleFilterPlugin.java @@ -12,7 +12,6 @@ import java.util.List; import org.apache.solr.common.SolrInputDocument; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; @@ -30,7 +29,7 @@ import org.dspace.core.Context; public class SolrServiceContentInOriginalBundleFilterPlugin implements SolrServiceIndexPlugin { @Override - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { + public void additionalIndex(Context context, IndexableObject dso, SolrInputDocument document) { if (dso instanceof Item) { Item item = (Item) dso; boolean hasOriginalBundleWithContent = hasOriginalBundleWithContent(item); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java index 979c577b2b..e14aa1dbc3 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java @@ -12,7 +12,6 @@ import java.util.List; import org.apache.solr.common.SolrInputDocument; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; @@ -41,7 +40,7 @@ public class SolrServiceFileInfoPlugin implements SolrServiceIndexPlugin { private static final String SOLR_FIELD_NAME_FOR_DESCRIPTIONS = "original_bundle_descriptions"; @Override - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { + public void additionalIndex(Context context, IndexableObject dso, SolrInputDocument document) { if (dso instanceof Item) { Item item = (Item) dso; List bundles = item.getBundles(); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 2efad0bea3..f0e26238ef 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -10,8 +10,8 @@ package org.dspace.discovery; import static org.dspace.discovery.configuration.DiscoverySortConfiguration.SCORE; import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; +import java.io.Serializable; import java.io.StringWriter; import java.sql.SQLException; import java.text.ParseException; @@ -34,6 +34,8 @@ import java.util.TimeZone; import java.util.UUID; import java.util.Vector; +import com.google.common.collect.ImmutableList; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Transformer; @@ -41,12 +43,6 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.validator.routines.UrlValidator; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; @@ -61,7 +57,6 @@ import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; -import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.FacetParams; import org.apache.solr.common.params.HighlightParams; import org.apache.solr.common.params.ModifiableSolrParams; @@ -78,6 +73,7 @@ import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; import org.dspace.content.MetadataValue; +import org.dspace.content.WorkspaceItem; import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.authority.service.MetadataAuthorityService; @@ -85,6 +81,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -102,12 +99,23 @@ import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; import org.dspace.discovery.configuration.DiscoverySortConfiguration; import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; import org.dspace.discovery.configuration.HierarchicalSidebarFacetConfiguration; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.dspace.handle.service.HandleService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.storage.rdbms.DatabaseUtils; import org.dspace.util.MultiFormatDateParser; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; +import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; +import org.dspace.xmlworkflow.storedcomponents.PoolTask; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; +import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -134,12 +142,24 @@ import org.springframework.stereotype.Service; @Service public class SolrServiceImpl implements SearchService, IndexingService { + /** + * The name of the discover configuration used to search for workflow tasks in the mydspace + */ + public static final String DISCOVER_WORKFLOW_CONFIGURATION_NAME = "workflow"; + + /** + * The name of the discover configuration used to search for inprogress submission in the mydspace + */ + public static final String DISCOVER_WORKSPACE_CONFIGURATION_NAME = "workspace"; + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class); protected static final String LAST_INDEXED_FIELD = "SolrIndexer.lastIndexed"; protected static final String HANDLE_FIELD = "handle"; + protected static final String RESOURCE_UNIQUE_ID = "search.uniqueid"; protected static final String RESOURCE_TYPE_FIELD = "search.resourcetype"; protected static final String RESOURCE_ID_FIELD = "search.resourceid"; + protected static final String NAMED_RESOURCE_TYPE = "namedresourcetype"; public static final String FILTER_SEPARATOR = "\n|||\n"; @@ -163,6 +183,18 @@ public class SolrServiceImpl implements SearchService, IndexingService { protected HandleService handleService; @Autowired(required = true) protected MetadataAuthorityService metadataAuthorityService; + @Autowired(required = true) + protected WorkspaceItemService workspaceItemService; + @Autowired(required = true) + protected XmlWorkflowItemService workflowItemService; + @Autowired(required = true) + protected ClaimedTaskService claimedTaskService; + @Autowired(required = true) + protected PoolTaskService poolTaskService; + @Autowired(required = true) + protected XmlWorkflowFactory workflowFactory; + @Autowired(required = true) + protected GroupService groupService; /** * Non-Static SolrServer for processing indexing events. @@ -221,7 +253,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { * @throws SQLException if error */ @Override - public void indexContent(Context context, DSpaceObject dso) + public void indexContent(Context context, IndexableObject dso) throws SQLException { indexContent(context, dso, false); } @@ -237,16 +269,11 @@ public class SolrServiceImpl implements SearchService, IndexingService { * @throws SQLException if error */ @Override - public void indexContent(Context context, DSpaceObject dso, + public void indexContent(Context context, IndexableObject dso, boolean force) throws SQLException { - String handle = dso.getHandle(); - - if (handle == null) { - handle = handleService.findHandle(context, dso); - } - try { + String uuid = dso.getID().toString(); switch (dso.getType()) { case Constants.ITEM: Item item = (Item) dso; @@ -254,9 +281,8 @@ public class SolrServiceImpl implements SearchService, IndexingService { /** * If the item is in the repository now, add it to the index */ - if (requiresIndexing(handle, ((Item) dso).getLastModified()) - || force) { - unIndexContent(context, handle); + if (force || requiresIndexing(dso.getUniqueIndexID(), ((Item) dso).getLastModified())) { + unIndexContent(context, dso); buildDocument(context, (Item) dso); } } else { @@ -264,19 +290,24 @@ public class SolrServiceImpl implements SearchService, IndexingService { * Make sure the item is not in the index if it is not in * archive or withdrawn. */ - unIndexContent(context, item); - log.info("Removed Item: " + handle + " from Index"); + unIndexContent(context, dso); + log.info("Removed Item: " + uuid + " from Index"); + + /** + * reindex any in progress submission tasks associated with the item + */ + indexInProgressSubmissionItem(context, (Item) dso); } break; case Constants.COLLECTION: buildDocument(context, (Collection) dso); - log.info("Wrote Collection: " + handle + " to Index"); + log.info("Wrote Collection: " + uuid + " to Index"); break; case Constants.COMMUNITY: buildDocument(context, (Community) dso); - log.info("Wrote Community: " + handle + " to Index"); + log.info("Wrote Community: " + uuid + " to Index"); break; default: @@ -298,7 +329,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { * @throws IOException if IO error */ @Override - public void unIndexContent(Context context, DSpaceObject dso) + public void unIndexContent(Context context, IndexableObject dso) throws SQLException, IOException { unIndexContent(context, dso, false); } @@ -307,20 +338,24 @@ public class SolrServiceImpl implements SearchService, IndexingService { * unIndex removes an Item, Collection, or Community * * @param context The relevant DSpace Context. - * @param dso DSpace Object, can be Community, Item, or Collection + * @param dso extension of DSpace Object, can be Community, Item, Collection or InProgressSubmission * @param commit if true force an immediate commit on SOLR * @throws SQLException if database error * @throws IOException if IO error */ @Override - public void unIndexContent(Context context, DSpaceObject dso, boolean commit) + public void unIndexContent(Context context, IndexableObject dso, boolean commit) throws SQLException, IOException { try { if (dso == null) { return; } - String uniqueID = dso.getType() + "-" + dso.getID(); + String uniqueID = dso.getUniqueIndexID(); + log.debug("Try to delete uniqueID:" + uniqueID); getSolr().deleteById(uniqueID); + if (Constants.ITEM == dso.getType()) { + deleteInProgressSubmissionByItemID(uniqueID); + } if (commit) { getSolr().commit(); } @@ -332,32 +367,33 @@ public class SolrServiceImpl implements SearchService, IndexingService { /** * Unindex a Document in the Lucene index. - * + * * @param context the dspace context - * @param handle the handle of the object to be deleted + * @param searchUniqueID the search uniqueID of the document to be deleted * @throws IOException if IO error - * @throws SQLException if database error */ @Override - public void unIndexContent(Context context, String handle) throws IOException, SQLException { - unIndexContent(context, handle, false); + public void unIndexContent(Context context, String searchUniqueID) throws IOException { + unIndexContent(context, searchUniqueID, false); } /** * Unindex a Document in the Lucene Index. - * + * * @param context the dspace context - * @param handle the handle of the object to be deleted - * @throws SQLException if database error + * @param searchUniqueID the search uniqueID of the document to be deleted * @throws IOException if IO error */ @Override - public void unIndexContent(Context context, String handle, boolean commit) - throws SQLException, IOException { + public void unIndexContent(Context context, String searchUniqueID, boolean commit) + throws IOException { try { if (getSolr() != null) { - getSolr().deleteByQuery(HANDLE_FIELD + ":\"" + handle + "\""); + getSolr().deleteById(searchUniqueID); + if (searchUniqueID.startsWith(Constants.ITEM + "-")) { + deleteInProgressSubmissionByItemID(searchUniqueID); + } if (commit) { getSolr().commit(); } @@ -374,7 +410,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { * @param dso object to re-index */ @Override - public void reIndexContent(Context context, DSpaceObject dso) + public void reIndexContent(Context context, IndexableObject dso) throws SQLException, IOException { try { indexContent(context, dso); @@ -425,23 +461,48 @@ public class SolrServiceImpl implements SearchService, IndexingService { */ @Override public void updateIndex(Context context, boolean force) { + updateIndex(context, force, Constants.ITEM); + updateIndex(context, force, Constants.COLLECTION); + updateIndex(context, force, Constants.COMMUNITY); + } + + + @Override + public void updateIndex(Context context, boolean force, int type) { try { - Iterator items = null; - for (items = itemService.findAllUnfiltered(context); items.hasNext(); ) { - Item item = items.next(); - indexContent(context, item, force); - //To prevent memory issues, discard an object from the cache after processing - context.uncacheEntity(item); - } - - List collections = collectionService.findAll(context); - for (Collection collection : collections) { - indexContent(context, collection, force); - } - - List communities = communityService.findAll(context); - for (Community community : communities) { - indexContent(context, community, force); + switch (type) { + case Constants.ITEM: + Iterator items = itemService.findAllUnfiltered(context); + for (Item item : ImmutableList.copyOf(items)) { + indexContent(context, item, force); + //To prevent memory issues, discard an object from the cache after processing + context.uncacheEntity(item); + } + for (WorkspaceItem wsi : workspaceItemService.findAll(context)) { + indexContent(context, wsi.getItem(), force); + //To prevent memory issues, discard an object from the cache after processing + context.uncacheEntity(wsi); + } + for (WorkflowItem wfi : workflowItemService.findAll(context)) { + indexContent(context, wfi.getItem(), force); + //To prevent memory issues, discard an object from the cache after processing + context.uncacheEntity(wfi); + } + break; + case Constants.COLLECTION: + List collections = collectionService.findAll(context); + for (Collection collection : collections) { + indexContent(context, collection, force); + } + break; + case Constants.COMMUNITY: + List communities = communityService.findAll(context); + for (Community community : communities) { + indexContent(context, community, force); + } + break; + default: + throw new IllegalArgumentException("No type known: " + type); } if (getSolr() != null) { @@ -463,9 +524,29 @@ public class SolrServiceImpl implements SearchService, IndexingService { * @throws SearchServiceException occurs when something went wrong with querying the solr server */ @Override - public void cleanIndex(boolean force) throws IOException, - SQLException, SearchServiceException { + public void cleanIndex(boolean force) throws IOException, SQLException, SearchServiceException { + if (force) { + try { + getSolr().deleteByQuery( + "search.resourcetype:[" + Constants.ITEM + " TO " + Constants.COMMUNITY + "]" + + " AND " + + "search.resourcetype:[" + Constants.WORKSPACEITEM + " TO " + Constants.CLAIMEDTASK + "]"); + } catch (Exception e) { + throw new SearchServiceException(e.getMessage(), e); + } + } else { + cleanIndex(false, Constants.ITEM); + cleanIndex(false, Constants.COLLECTION); + cleanIndex(false, Constants.COMMUNITY); + cleanIndex(false, Constants.WORKSPACEITEM); + cleanIndex(false, Constants.POOLTASK); + cleanIndex(false, Constants.CLAIMEDTASK); + cleanIndex(false, Constants.WORKFLOWITEM); + } + } + @Override + public void cleanIndex(boolean force, int type) throws IOException, SQLException, SearchServiceException { Context context = new Context(); context.turnOffAuthorisationSystem(); @@ -474,13 +555,13 @@ public class SolrServiceImpl implements SearchService, IndexingService { return; } if (force) { - getSolr().deleteByQuery(RESOURCE_TYPE_FIELD + ":[2 TO 4]"); + getSolr().deleteByQuery(RESOURCE_TYPE_FIELD + ":" + type); } else { SolrQuery query = new SolrQuery(); // Query for all indexed Items, Collections and Communities, // returning just their handle query.setFields(HANDLE_FIELD); - query.setQuery(RESOURCE_TYPE_FIELD + ":[2 TO 4]"); + query.setQuery(RESOURCE_TYPE_FIELD + ":" + type); QueryResponse rsp = getSolr().query(query, SolrRequest.METHOD.POST); SolrDocumentList docs = rsp.getResults(); @@ -489,30 +570,27 @@ public class SolrServiceImpl implements SearchService, IndexingService { SolrDocument doc = (SolrDocument) iter.next(); - String handle = (String) doc.getFieldValue(HANDLE_FIELD); + String uniqueID = (String) doc.getFieldValue(RESOURCE_UNIQUE_ID); - DSpaceObject o = handleService.resolveToObject(context, handle); + IndexableObject o = findIndexableObject(context, doc); if (o == null) { - log.info("Deleting: " + handle); + log.info("Deleting: " + uniqueID); /* * Use IndexWriter to delete, its easier to manage * write.lock */ - unIndexContent(context, handle); + unIndexContent(context, uniqueID); } else { - log.debug("Keeping: " + handle); + log.debug("Keeping: " + o.getUniqueIndexID()); } } } } catch (Exception e) { - - throw new SearchServiceException(e.getMessage(), e); + log.error("Error cleaning discovery index: " + e.getMessage(), e); } finally { context.abort(); } - - } /** @@ -616,7 +694,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { boolean inIndex = false; SolrQuery query = new SolrQuery(); - query.setQuery(HANDLE_FIELD + ":" + handle); + query.setQuery(RESOURCE_UNIQUE_ID + ":" + handle); // Specify that we ONLY want the LAST_INDEXED_FIELD returned in the field list (fl) query.setFields(LAST_INDEXED_FIELD); QueryResponse rsp; @@ -693,6 +771,19 @@ public class SolrServiceImpl implements SearchService, IndexingService { return locations; } + protected List getCommunityLocations(Community target) throws SQLException { + List locations = new Vector(); + // build list of community ids + List communities = target.getParentCommunities(); + + // now put those into strings + for (Community community : communities) { + locations.add("m" + community.getID()); + } + + return locations; + } + @Override public String createLocationQueryForAdministrableItems(Context context) throws SQLException { @@ -808,9 +899,12 @@ public class SolrServiceImpl implements SearchService, IndexingService { */ protected void buildDocument(Context context, Community community) throws SQLException, IOException { + + List locations = getCommunityLocations(community); + // Create Document SolrInputDocument doc = buildDocument(Constants.COMMUNITY, community.getID(), - community.getHandle(), null); + community.getHandle(), locations); DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(community); DiscoveryHitHighlightingConfiguration highlightingConfiguration = discoveryConfiguration @@ -964,22 +1058,68 @@ public class SolrServiceImpl implements SearchService, IndexingService { doc.addField("discoverable", item.isDiscoverable()); doc.addField("lastModified", item.getLastModified()); + EPerson submitter = item.getSubmitter(); + if (submitter != null) { + addFacetIndex(doc, "submitter", submitter.getID().toString(), + submitter.getFullName()); + } + + List discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item); + addDiscoveryFields(doc, context, item, discoveryConfigurations); + + //mandatory facet to show status on mydspace + final String typeText = StringUtils.deleteWhitespace(item.getTypeText().toLowerCase()); + String acvalue = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty( + "discovery.facet.namedtype." + typeText, + typeText + SolrServiceImpl.AUTHORITY_SEPARATOR + typeText); + if (StringUtils.isNotBlank(acvalue)) { + addNamedResourceTypeIndex(doc, acvalue); + } + + // write the index and close the inputstreamreaders + try { + writeDocument(doc, new FullTextContentStreams(context, item)); + log.info("Wrote Item: " + item.getUniqueIndexID() + " to Index"); + } catch (RuntimeException e) { + log.error("Error while writing item to discovery index: " + item.getUniqueIndexID() + " message:" + + e.getMessage(), e); + } + } + + protected void addDiscoveryFields(SolrInputDocument doc, Context context, Item item, + List discoveryConfigurations) + throws SQLException, IOException { //Keep a list of our sort values which we added, sort values can only be added once List sortFieldsAdded = new ArrayList(); + Map> searchFilters = null; Set hitHighlightingFields = new HashSet(); try { - List discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item); - //A map used to save each sidebarFacet config by the metadata fields - Map> searchFilters = new HashMap>(); + searchFilters = new HashMap>(); Map sortFields = new HashMap(); Map recentSubmissionsConfigurationMap = new HashMap(); Set moreLikeThisFields = new HashSet(); - for (DiscoveryConfiguration discoveryConfiguration : discoveryConfigurations) { - for (int i = 0; i < discoveryConfiguration.getSearchFilters().size(); i++) { + // some configuration are returned multiple times, skip them to save CPU cycles + Set appliedConf = new HashSet(); + // it is common to have search filter shared between multiple configurations + Set appliedDiscoverySearchFilter = new HashSet(); + for (DiscoveryConfiguration discoveryConfiguration : discoveryConfigurations) { + if (appliedConf.contains(discoveryConfiguration.getId())) { + continue; + } else { + appliedConf.add(discoveryConfiguration.getId()); + } + for (int i = 0; i < discoveryConfiguration.getSearchFilters().size(); i++) { + if (appliedDiscoverySearchFilter + .contains(discoveryConfiguration.getSearchFilters().get(i).getIndexFieldName())) { + continue; + } else { + appliedDiscoverySearchFilter + .add(discoveryConfiguration.getSearchFilters().get(i).getIndexFieldName()); + } List metadataValueList = new LinkedList<>(); boolean shouldExposeMinMax = false; DiscoverySearchFilter discoverySearchFilter = discoveryConfiguration.getSearchFilters().get(i); @@ -1290,7 +1430,6 @@ public class SolrServiceImpl implements SearchService, IndexingService { doc.addField(indexField + "_sort", yearUTC); } } - } else if (searchFilter.getType() .equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { HierarchicalSidebarFacetConfiguration hierarchicalSidebarFacetConfiguration = @@ -1351,7 +1490,11 @@ public class SolrServiceImpl implements SearchService, IndexingService { if (hitHighlightingFields.contains(field) || hitHighlightingFields .contains("*") || hitHighlightingFields.contains(unqualifiedField + "." + Item.ANY)) { - doc.addField(field + "_hl", value); + if (authority != null) { + doc.addField(field + "_hl", value + AUTHORITY_SEPARATOR + authority); + } else { + doc.addField(field + "_hl", value); + } } if (moreLikeThisFields.contains(field) || moreLikeThisFields @@ -1360,6 +1503,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { } doc.addField(field, value); + if (authority != null) { + doc.addField(field + "_authority", authority); + } if (toProjectionFields.contains(field) || toProjectionFields .contains(unqualifiedField + "." + Item.ANY)) { StringBuffer variantsToStore = new StringBuffer(); @@ -1423,13 +1569,156 @@ public class SolrServiceImpl implements SearchService, IndexingService { for (SolrServiceIndexPlugin solrServiceIndexPlugin : solrServiceIndexPlugins) { solrServiceIndexPlugin.additionalIndex(context, item, doc); } + } - // write the index and close the inputstreamreaders - try { - writeDocument(doc, new FullTextContentStreams(context, item)); - log.info("Wrote Item: " + handle + " to Index"); - } catch (RuntimeException e) { - log.error("Error while writing item to discovery index: " + handle + " message:" + e.getMessage(), e); + private void deleteInProgressSubmissionByItemID(String uniqueID) throws SolrServerException, IOException { + String query = "inprogress.item:\"" + uniqueID + "\""; + log.debug("Try to delete all in progress submission [DELETEBYQUERY]:" + query); + getSolr().deleteByQuery(query); + } + + private void addFacetIndex(SolrInputDocument document, String field, String authority, String fvalue) { + addFacetIndex(document, field, fvalue, authority, fvalue); + } + + private void addFacetIndex(SolrInputDocument document, String field, String sortValue, String authority, + String fvalue) { + // the separator for the filter can be eventually configured + String separator = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("discovery.solr.facets.split.char"); + if (separator == null) { + separator = FILTER_SEPARATOR; + } + String acvalue = sortValue + separator + fvalue + SolrServiceImpl.AUTHORITY_SEPARATOR + authority; + document.addField(field + "_filter", acvalue); + // build the solr field used for the keyword search + document.addField(field + "_keyword", fvalue); + // build the solr fields used for the autocomplete + document.addField(field + "_ac", fvalue.toLowerCase() + separator + fvalue); + if (StringUtils.isNotBlank(authority)) { + document.addField(field + "_acid", fvalue.toLowerCase() + separator + fvalue + + SolrServiceImpl.AUTHORITY_SEPARATOR + authority); + document.addField(field + "_authority", authority); + } + } + + private void indexInProgressSubmissionItem(Context context, Item item) + throws SQLException, IOException, SolrServerException, WorkflowConfigurationException { + XmlWorkflowItem workflowItem = workflowItemService.findByItem(context, item); + if (workflowItem == null) { + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, item); + if (workspaceItem == null) { + // mmm... it could be a template item, skip it + return; + } + // workspaceitem + List locations = getCollectionLocations(context, workspaceItem.getCollection()); + SolrInputDocument doc = new SolrInputDocument(); + + doc.addField("lastModified", item.getLastModified()); + EPerson submitter = workspaceItem.getSubmitter(); + if (submitter != null) { + addFacetIndex(doc, "submitter", submitter.getID().toString(), + submitter.getFullName()); + } + + List discoveryConfigurations = SearchUtils + .getAllDiscoveryConfigurations(workspaceItem); + addDiscoveryFields(doc, context, item, discoveryConfigurations); + addBasicInfoToDocument(doc, Constants.WORKSPACEITEM, workspaceItem.getID(), null, locations); + + String acvalue = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("discovery.facet.namedtype.workspace"); + if (StringUtils.isBlank(acvalue)) { + acvalue = workspaceItem.getTypeText(); + } + addNamedResourceTypeIndex(doc, acvalue); + doc.addField("inprogress.item", item.getUniqueIndexID()); + + getSolr().add(doc); + } else { + // so it is an item in the workflow + // get the location string (for searching by collection & community) + List locations = getCollectionLocations(context, workflowItem.getCollection()); + SolrInputDocument doc = new SolrInputDocument(); + + doc.addField("inprogress.item", item.getUniqueIndexID()); + doc.addField("lastModified", item.getLastModified()); + EPerson submitter = workflowItem.getSubmitter(); + if (submitter != null) { + addFacetIndex(doc, "submitter", submitter.getID().toString(), + submitter.getFullName()); + } + + List discoveryConfigurations = SearchUtils + .getAllDiscoveryConfigurations(workflowItem); + addDiscoveryFields(doc, context, item, discoveryConfigurations); + + List claimedTasks = claimedTaskService.find(context, workflowItem); + List pools = poolTaskService.find(context, workflowItem); + + List docs = new ArrayList(); + + if (claimedTasks != null) { + for (ClaimedTask claimedTask : claimedTasks) { + SolrInputDocument claimDoc = doc.deepCopy(); + addBasicInfoToDocument(claimDoc, Constants.CLAIMEDTASK, claimedTask.getID(), null, locations); + addFacetIndex(claimDoc, "action", claimedTask.getActionID(), claimedTask.getActionID()); + addFacetIndex(claimDoc, "step", claimedTask.getStepID(), claimedTask.getStepID()); + + claimDoc.addField("taskfor", "e" + claimedTask.getOwner().getID().toString()); + + String acvalue = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("discovery.facet.namedtype.workflow.claimed"); + if (StringUtils.isBlank(acvalue)) { + acvalue = claimedTask.getTypeText(); + } + addNamedResourceTypeIndex(claimDoc, acvalue); + + docs.add(claimDoc); + } + } + + if (pools != null) { + for (PoolTask poolTask : pools) { + SolrInputDocument claimDoc = doc.deepCopy(); + addBasicInfoToDocument(claimDoc, Constants.POOLTASK, poolTask.getID(), null, locations); + addFacetIndex(claimDoc, "action", poolTask.getActionID(), poolTask.getActionID()); + addFacetIndex(claimDoc, "step", poolTask.getStepID(), poolTask.getStepID()); + + if (poolTask.getEperson() != null) { + claimDoc.addField("taskfor", "e" + poolTask.getEperson().getID().toString()); + } + if (poolTask.getGroup() != null) { + claimDoc.addField("taskfor", "g" + poolTask.getGroup().getID().toString()); + } + + String acvalue = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("discovery.facet.namedtype.workflow.pooled"); + if (StringUtils.isBlank(acvalue)) { + acvalue = poolTask.getTypeText(); + } + addNamedResourceTypeIndex(claimDoc, acvalue); + docs.add(claimDoc); + } + } + + String acvalue = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("discovery.facet.namedtype.workflow.item"); + if (StringUtils.isBlank(acvalue)) { + acvalue = workflowItem.getTypeText(); + } + addNamedResourceTypeIndex(doc, acvalue); + + addBasicInfoToDocument(doc, Constants.WORKFLOWITEM, workflowItem.getID(), null, locations); + docs.add(doc); + + if (docs.size() > 0) { + getSolr().add(docs); + } else { + // no tasks found?!? + log.error("No tasks found for workflowitem " + workflowItem.getID()); + } } } @@ -1445,14 +1734,19 @@ public class SolrServiceImpl implements SearchService, IndexingService { protected SolrInputDocument buildDocument(int type, UUID id, String handle, List locations) { SolrInputDocument doc = new SolrInputDocument(); + addBasicInfoToDocument(doc, type, id, handle, locations); + return doc; + } + private void addBasicInfoToDocument(SolrInputDocument doc, int type, Serializable id, String handle, + List locations) { // want to be able to check when last updated // (not tokenized, but it is indexed) doc.addField(LAST_INDEXED_FIELD, new Date()); // New fields to weaken the dependence on handles, and allow for faster // list display - doc.addField("search.uniqueid", type + "-" + id); + doc.addField(RESOURCE_UNIQUE_ID, type + "-" + id); doc.addField(RESOURCE_TYPE_FIELD, Integer.toString(type)); doc.addField(RESOURCE_ID_FIELD, id.toString()); @@ -1474,8 +1768,6 @@ public class SolrServiceImpl implements SearchService, IndexingService { } } } - - return doc; } /** @@ -1567,14 +1859,14 @@ public class SolrServiceImpl implements SearchService, IndexingService { } @Override - public DiscoverResult search(Context context, DSpaceObject dso, + public DiscoverResult search(Context context, IndexableObject dso, DiscoverQuery query) throws SearchServiceException { return search(context, dso, query, false); } @Override - public DiscoverResult search(Context context, DSpaceObject dso, DiscoverQuery discoveryQuery, + public DiscoverResult search(Context context, IndexableObject dso, DiscoverQuery discoveryQuery, boolean includeUnDiscoverable) throws SearchServiceException { if (dso != null) { if (dso instanceof Community) { @@ -1582,7 +1874,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { } else if (dso instanceof Collection) { discoveryQuery.addFilterQueries("location:l" + dso.getID()); } else if (dso instanceof Item) { - discoveryQuery.addFilterQueries(HANDLE_FIELD + ":" + dso.getHandle()); + discoveryQuery.addFilterQueries(HANDLE_FIELD + ":" + ((Item) dso).getHandle()); } } return search(context, discoveryQuery, includeUnDiscoverable); @@ -1609,7 +1901,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { } protected SolrQuery resolveToSolrQuery(Context context, DiscoverQuery discoveryQuery, - boolean includeUnDiscoverable) { + boolean includeUnDiscoverable) throws SearchServiceException { SolrQuery solrQuery = new SolrQuery(); String query = "*:*"; @@ -1729,6 +2021,41 @@ public class SolrServiceImpl implements SearchService, IndexingService { } + boolean isWorkspace = StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), + DISCOVER_WORKSPACE_CONFIGURATION_NAME); + boolean isWorkflow = StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), + DISCOVER_WORKFLOW_CONFIGURATION_NAME); + EPerson currentUser = context.getCurrentUser(); + + // extra security check to avoid the possibility that an anonymous user + // get access to workspace or workflow + if (currentUser == null && (isWorkflow || isWorkspace)) { + throw new IllegalStateException("An anonymous user cannot perform a workspace or workflow search"); + } + if (isWorkspace) { + // insert filter by submitter + solrQuery + .addFilterQuery("submitter_authority:(" + currentUser.getID() + ")"); + } else if (isWorkflow) { + // Retrieve all the groups the current user is a member of ! + Set groups; + try { + groups = groupService.allMemberGroupsSet(context, currentUser); + } catch (SQLException e) { + throw new org.dspace.discovery.SearchServiceException(e.getMessage(), e); + } + + // insert filter by controllers + StringBuilder controllerQuery = new StringBuilder(); + controllerQuery.append("taskfor:(e" + currentUser.getID()); + for (Group group : groups) { + controllerQuery.append(" OR g").append(group.getID()); + } + controllerQuery.append(")"); + solrQuery.addFilterQuery(controllerQuery.toString()); + } + + //Add any configured search plugins ! List solrServiceSearchPlugins = DSpaceServicesFactory.getInstance().getServiceManager() .getServicesByType( @@ -1737,69 +2064,10 @@ public class SolrServiceImpl implements SearchService, IndexingService { for (SolrServiceSearchPlugin searchPlugin : solrServiceSearchPlugins) { searchPlugin.additionalSearchParameters(context, discoveryQuery, solrQuery); } + return solrQuery; } - @Override - public InputStream searchJSON(Context context, DiscoverQuery query, DSpaceObject dso, String jsonIdentifier) - throws SearchServiceException { - if (dso != null) { - if (dso instanceof Community) { - query.addFilterQueries("location:m" + dso.getID()); - } else if (dso instanceof Collection) { - query.addFilterQueries("location:l" + dso.getID()); - } else if (dso instanceof Item) { - query.addFilterQueries(HANDLE_FIELD + ":" + dso.getHandle()); - } - } - return searchJSON(context, query, jsonIdentifier); - } - - - @Override - public InputStream searchJSON(Context context, DiscoverQuery discoveryQuery, String jsonIdentifier) - throws SearchServiceException { - if (getSolr() == null || !(getSolr() instanceof HttpSolrClient)) { - return null; - } - - SolrQuery solrQuery = resolveToSolrQuery(context, discoveryQuery, false); - //We use json as out output type - solrQuery.setParam("json.nl", "map"); - solrQuery.setParam("json.wrf", jsonIdentifier); - solrQuery.setParam(CommonParams.WT, "json"); - - StringBuilder urlBuilder = new StringBuilder(); - // New url without any query params appended - urlBuilder.append(((HttpSolrClient)getSolr()).getBaseURL()).append("/select"); - // Post setup - NamedList solrParameters = solrQuery.toNamedList(); - List postParameters = new ArrayList<>(); - for (Map.Entry solrParameter : solrParameters) { - if (solrParameter.getValue() instanceof String[]) { - // Multi-valued solr parameter - for (String val : (String[])solrParameter.getValue()) { - postParameters.add(new BasicNameValuePair(solrParameter.getKey(), val)); - } - } else if (solrParameter.getValue() instanceof String) { - postParameters.add(new BasicNameValuePair(solrParameter.getKey(), solrParameter.getValue().toString())); - } else { - log.warn("Search parameters contain non-string value: " + solrParameter.getValue().toString()); - } - } - - try { - HttpPost post = new HttpPost(urlBuilder.toString()); - post.setEntity(new UrlEncodedFormEntity(postParameters)); - HttpResponse response = new DefaultHttpClient().execute(post); - return response.getEntity().getContent(); - - } catch (Exception e) { - log.error("Error while getting json solr result for discovery search recommendation", e); - } - return null; - } - protected DiscoverResult retrieveResult(Context context, DiscoverQuery query, QueryResponse solrQueryResponse) throws SQLException { DiscoverResult result = new DiscoverResult(); @@ -1812,10 +2080,10 @@ public class SolrServiceImpl implements SearchService, IndexingService { List searchFields = query.getSearchFields(); for (SolrDocument doc : solrQueryResponse.getResults()) { - DSpaceObject dso = findDSpaceObject(context, doc); + IndexableObject dso = findIndexableObject(context, doc); if (dso != null) { - result.addDSpaceObject(dso); + result.addIndexableObject(dso); } else { log.error(LogManager.getHeader(context, "Error while retrieving DSpace object from discovery index", "Handle: " + doc.getFirstValue(HANDLE_FIELD))); @@ -1840,11 +2108,18 @@ public class SolrServiceImpl implements SearchService, IndexingService { //We need to remove all the "_hl" appendix strings from our keys Map> resultMap = new HashMap>(); for (String key : highlightedFields.keySet()) { + List highlightOriginalValue = highlightedFields.get(key); + List resultHighlightOriginalValue = new ArrayList(); + for (String highlightValue : highlightOriginalValue) { + String[] splitted = highlightValue.split("###"); + resultHighlightOriginalValue.add(splitted); + + } resultMap.put(key.substring(0, key.lastIndexOf("_hl")), highlightedFields.get(key)); } - result - .addHighlightedResult(dso, new DiscoverResult.DSpaceObjectHighlightResult(dso, resultMap)); + result.addHighlightedResult(dso, + new DiscoverResult.IndexableObjectHighlightResult(dso, resultMap)); } } } @@ -1931,35 +2206,53 @@ public class SolrServiceImpl implements SearchService, IndexingService { } /** - * Find DSpace object by type and UUID or by handle from given Solr document + * Find the indexable object by type and UUID * * @param context The relevant DSpace Context. * @param doc the solr document - * @return DSpace object + * @return an IndexableObject * @throws SQLException An exception that provides information on a database access error or other errors. */ - protected DSpaceObject findDSpaceObject(Context context, SolrDocument doc) throws SQLException { - + protected IndexableObject findIndexableObject(Context context, SolrDocument doc) throws SQLException { Integer type = (Integer) doc.getFirstValue(RESOURCE_TYPE_FIELD); - UUID id = UUID.fromString((String) doc.getFirstValue(RESOURCE_ID_FIELD)); + Object id = doc.getFirstValue(RESOURCE_ID_FIELD); String handle = (String) doc.getFirstValue(HANDLE_FIELD); - + IndexableObject o = null; + Serializable uid = null; if (type != null && id != null) { - return contentServiceFactory.getDSpaceObjectService(type).find(context, id); - } else if (handle != null) { - return handleService.resolveToObject(context, handle); + switch (type) { + case Constants.WORKSPACEITEM: + case Constants.WORKFLOWITEM: + case Constants.POOLTASK: + case Constants.CLAIMEDTASK: + uid = Integer.parseInt((String) id); + break; + default: + uid = UUID.fromString((String) id); + break; + } } - return null; + if (uid != null) { + o = (IndexableObject) contentServiceFactory.getIndexableObjectService(type).findIndexableObject(context, + uid); + } + + if (o == null) { + log.warn("Not able to retrieve object RESOURCE_ID:" + id + " - RESOURCE_TYPE_ID:" + type + " - HANDLE:" + + handle); + } + return o; } - public List search(Context context, String query, int offset, int max, String... filterquery) { + public List search(Context context, String query, int offset, int max, + String... filterquery) { return search(context, query, null, true, offset, max, filterquery); } @Override - public List search(Context context, String query, String orderfield, boolean ascending, int offset, - int max, String... filterquery) { + public List search(Context context, String query, String orderfield, boolean ascending, + int offset, int max, String... filterquery) { try { if (getSolr() == null) { @@ -1982,13 +2275,13 @@ public class SolrServiceImpl implements SearchService, IndexingService { SolrDocumentList docs = rsp.getResults(); Iterator iter = docs.iterator(); - List result = new ArrayList<>(); + List result = new ArrayList(); while (iter.hasNext()) { SolrDocument doc = (SolrDocument) iter.next(); - DSpaceObject o = contentServiceFactory - .getDSpaceObjectService((Integer) doc.getFirstValue(RESOURCE_TYPE_FIELD)) - .find(context, UUID.fromString((String) doc.getFirstValue(RESOURCE_ID_FIELD))); + IndexableObject o = (IndexableObject)contentServiceFactory + .getIndexableObjectService((Integer) doc.getFirstValue(RESOURCE_TYPE_FIELD)) + .findIndexableObject(context, UUID.fromString((String) doc.getFirstValue(RESOURCE_ID_FIELD))); if (o != null) { result.add(o); @@ -1998,8 +2291,8 @@ public class SolrServiceImpl implements SearchService, IndexingService { } catch (Exception e) { // Any acception that we get ignore it. // We do NOT want any crashed to shown by the user - log.error(LogManager.getHeader(context, "Error while quering solr", "Queyr: " + query), e); - return new ArrayList(0); + log.error(LogManager.getHeader(context, "Error while quering solr", "Query: " + query), e); + return new ArrayList(0); } } @@ -2092,7 +2385,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { SolrDocumentList relatedDocs = (SolrDocumentList) mltResults.get(item.getType() + "-" + item.getID()); for (Object relatedDoc : relatedDocs) { SolrDocument relatedDocument = (SolrDocument) relatedDoc; - DSpaceObject relatedItem = findDSpaceObject(context, relatedDocument); + IndexableObject relatedItem = findIndexableObject(context, relatedDocument); if (relatedItem.getType() == Constants.ITEM) { results.add((Item) relatedItem); } @@ -2159,6 +2452,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { } protected String transformDisplayedValue(Context context, String field, String value) throws SQLException { + if (value == null) { + return null; + } if (field.equals("location.comm") || field.equals("location.coll")) { value = locationToName(context, field, value); } else if (field.endsWith("_filter") || field.endsWith("_ac") @@ -2187,6 +2483,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { } protected String transformAuthorityValue(Context context, String field, String value) throws SQLException { + if (value == null) { + return null; + } if (field.equals("location.comm") || field.equals("location.coll")) { return value; } @@ -2217,6 +2516,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { } protected String transformSortValue(Context context, String field, String value) throws SQLException { + if (value == null) { + return null; + } if (field.equals("location.comm") || field.equals("location.coll")) { value = locationToName(context, field, value); } else if (field.endsWith("_filter") || field.endsWith("_ac") @@ -2244,7 +2546,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { } @Override - public void indexContent(Context context, DSpaceObject dso, boolean force, + public void indexContent(Context context, IndexableObject dso, boolean force, boolean commit) throws SearchServiceException, SQLException { indexContent(context, dso, force); if (commit) { @@ -2273,10 +2575,11 @@ public class SolrServiceImpl implements SearchService, IndexingService { } @Override - public FacetYearRange getFacetYearRange(Context context, DSpaceObject scope, DiscoverySearchFilterFacet facet, - List filterQueries) throws SearchServiceException { + public FacetYearRange getFacetYearRange(Context context, IndexableObject scope, + DiscoverySearchFilterFacet facet, List filterQueries, + DiscoverQuery parentQuery) throws SearchServiceException { FacetYearRange result = new FacetYearRange(facet); - result.calculateRange(context, filterQueries, scope, this); + result.calculateRange(context, filterQueries, scope, this, parentQuery); return result; } @@ -2294,9 +2597,9 @@ public class SolrServiceImpl implements SearchService, IndexingService { maxQuery.setSortField(sortField, sortOrder); maxQuery.addSearchField(valueField); DiscoverResult maxResult = this.search(context,maxQuery); - if (0 < maxResult.getDspaceObjects().size()) { + if (0 < maxResult.getIndexableObjects().size()) { List searchDocuments = maxResult - .getSearchDocument(maxResult.getDspaceObjects().get(0)); + .getSearchDocument(maxResult.getIndexableObjects().get(0)); if (0 < searchDocuments.size() && 0 < searchDocuments.get(0).getSearchFieldValues (valueField).size()) { return searchDocuments.get(0).getSearchFieldValues(valueField).get(0); @@ -2304,4 +2607,37 @@ public class SolrServiceImpl implements SearchService, IndexingService { } return null; } + + /** + * Add the necessary fields to the SOLR document to support a Discover Facet on resourcetypename (archived item, + * workspace item, workflow item, etc) + * + * @param document + * the solr document + * @param filterValue + * the filter value (i.e. \n|||\n### + */ + private void addNamedResourceTypeIndex(SolrInputDocument document, String filterValue) { + + // the separator for the filter can be eventually configured + String separator = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("discovery.solr.facets.split.char"); + if (separator == null) { + separator = FILTER_SEPARATOR; + } + + // split the authority part from the sort/display + String[] avalues = filterValue.split(SolrServiceImpl.AUTHORITY_SEPARATOR); + + String sortValue = avalues[0]; + String authorityValue = avalues.length == 2 ? avalues[1] : filterValue; + + // get the display value + int idxSeparator = sortValue.indexOf(separator); + String displayValue = idxSeparator != -1 ? sortValue.substring(idxSeparator + separator.length()) + : sortValue; + + addFacetIndex(document, NAMED_RESOURCE_TYPE, sortValue, authorityValue, displayValue); + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexOutputPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexOutputPlugin.java index 58c0f92c8f..26fc8dab24 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexOutputPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexOutputPlugin.java @@ -8,7 +8,6 @@ package org.dspace.discovery; import org.apache.solr.common.SolrInputDocument; -import org.dspace.content.DSpaceObject; import org.dspace.core.Context; @@ -20,7 +19,7 @@ import org.dspace.core.Context; public class SolrServiceIndexOutputPlugin implements SolrServiceIndexPlugin { @Override - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { - System.out.println("Currently indexing: " + dso.getHandle()); + public void additionalIndex(Context context, IndexableObject dso, SolrInputDocument document) { + System.out.println("Currently indexing: " + dso.getUniqueIndexID()); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexPlugin.java index 2038e9ad06..8527899206 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexPlugin.java @@ -8,7 +8,6 @@ package org.dspace.discovery; import org.apache.solr.common.SolrInputDocument; -import org.dspace.content.DSpaceObject; import org.dspace.core.Context; /** @@ -20,5 +19,5 @@ import org.dspace.core.Context; */ public interface SolrServiceIndexPlugin { - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document); + public void additionalIndex(Context context, IndexableObject dso, SolrInputDocument document); } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java index 5e78173d93..6d1035c4a0 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -17,7 +17,6 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -57,7 +56,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex protected ChoiceAuthorityService choiceAuthorityService; @Override - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { + public void additionalIndex(Context context, IndexableObject dso, SolrInputDocument document) { // Only works for Items if (!(dso instanceof Item)) { return; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java index 4fcc8c38ca..853076fdc2 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.common.SolrInputDocument; @@ -54,34 +55,44 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu protected ResourcePolicyService resourcePolicyService; @Override - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { - try { - List policies = authorizeService.getPoliciesActionFilter(context, dso, Constants.READ); - for (ResourcePolicy resourcePolicy : policies) { - String fieldValue; - if (resourcePolicy.getGroup() != null) { - //We have a group add it to the value - fieldValue = "g" + resourcePolicy.getGroup().getID(); - } else { - //We have an eperson add it to the value - fieldValue = "e" + resourcePolicy.getEPerson().getID(); + public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { + if (idxObj instanceof DSpaceObject) { + DSpaceObject dso = (DSpaceObject) idxObj; + try { + List policies = authorizeService.getPoliciesActionFilter(context, dso, Constants.READ); + for (ResourcePolicy resourcePolicy : policies) { + String fieldValue; + if (resourcePolicy.getGroup() != null) { + //We have a group add it to the value + fieldValue = "g" + resourcePolicy.getGroup().getID(); + } else { + //We have an eperson add it to the value + fieldValue = "e" + resourcePolicy.getEPerson().getID(); + } + + document.addField("read", fieldValue); + + //remove the policy from the cache to save memory + context.uncacheEntity(resourcePolicy); } - - document.addField("read", fieldValue); - - //remove the policy from the cache to save memory - context.uncacheEntity(resourcePolicy); + } catch (SQLException e) { + log.error(LogManager.getHeader(context, "Error while indexing resource policies", + "DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")")); } - } catch (SQLException e) { - log.error(LogManager.getHeader(context, "Error while indexing resource policies", - "DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")")); } } @Override public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery) { try { + // skip workspace and workflow queries as security for it them is builtin in the SolrServiceImpl + if (StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), + SolrServiceImpl.DISCOVER_WORKSPACE_CONFIGURATION_NAME) + || StringUtils.startsWith(discoveryQuery.getDiscoveryConfigurationName(), + SolrServiceImpl.DISCOVER_WORKFLOW_CONFIGURATION_NAME)) { + return; + } if (!authorizeService.isAdmin(context)) { StringBuilder resourceQuery = new StringBuilder(); //Always add the anonymous group id to the query diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSpellIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSpellIndexingPlugin.java index c6a4b602ca..99c3fcf6ef 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSpellIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSpellIndexingPlugin.java @@ -10,7 +10,6 @@ package org.dspace.discovery; import java.util.List; import org.apache.solr.common.SolrInputDocument; -import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; @@ -30,7 +29,7 @@ public class SolrServiceSpellIndexingPlugin implements SolrServiceIndexPlugin { protected ItemService itemService; @Override - public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { + public void additionalIndex(Context context, IndexableObject dso, SolrInputDocument document) { if (dso instanceof Item) { Item item = (Item) dso; List dcValues = itemService.getMetadata(item, Item.ANY, Item.ANY, Item.ANY, Item.ANY); diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java index 69f992a29b..f79596fe1b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java @@ -13,6 +13,7 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.dspace.content.DSpaceObject; +import org.dspace.discovery.IndexableObject; import org.dspace.services.factory.DSpaceServicesFactory; /** @@ -39,12 +40,14 @@ public class DiscoveryConfigurationService { this.toIgnoreMetadataFields = toIgnoreMetadataFields; } - public DiscoveryConfiguration getDiscoveryConfiguration(DSpaceObject dso) { + public DiscoveryConfiguration getDiscoveryConfiguration(IndexableObject dso) { String name; if (dso == null) { name = "site"; + } else if (dso instanceof DSpaceObject) { + name = ((DSpaceObject) dso).getHandle(); } else { - name = dso.getHandle(); + name = dso.getUniqueIndexID(); } return getDiscoveryConfiguration(name); @@ -64,7 +67,7 @@ public class DiscoveryConfigurationService { } public DiscoveryConfiguration getDiscoveryConfigurationByNameOrDso(final String configurationName, - final DSpaceObject dso) { + final IndexableObject dso) { if (StringUtils.isNotBlank(configurationName) && getMap().containsKey(configurationName)) { return getMap().get(configurationName); } else { diff --git a/dspace-api/src/main/java/org/dspace/search/Harvest.java b/dspace-api/src/main/java/org/dspace/search/Harvest.java index 677a760452..493a7d39a6 100644 --- a/dspace-api/src/main/java/org/dspace/search/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/search/Harvest.java @@ -28,6 +28,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.SearchUtils; import org.dspace.eperson.Group; @@ -139,9 +140,10 @@ public class Harvest { DiscoverResult discoverResult = SearchUtils.getSearchService().search(context, discoverQuery); // Process results of query into HarvestedItemInfo objects - Iterator dsoIterator = discoverResult.getDspaceObjects().iterator(); + Iterator dsoIterator = discoverResult.getIndexableObjects().iterator(); while (dsoIterator.hasNext() && ((limit == 0) || (itemCounter < limit))) { - DSpaceObject dso = dsoIterator.next(); + // the query is limited to ITEM + DSpaceObject dso = (DSpaceObject) dsoIterator.next(); HarvestedItemInfo itemInfo = new HarvestedItemInfo(); itemInfo.context = context; itemInfo.handle = dso.getHandle(); diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowItemService.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowItemService.java index 7808c65f70..0a5a8297fe 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowItemService.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowItemService.java @@ -30,6 +30,16 @@ public interface WorkflowItemService extends InProgressS public T create(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; + /** + * Get a workflow item from the database. + * + * @param context The relevant DSpace Context. + * @param id ID of the workflow item + * @return the workflow item, or null if the ID is invalid. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public T find(Context context, int id) throws SQLException; + /** * return all workflowitems * diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItemServiceImpl.java index f3a9bc2d52..8fcd64da83 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItemServiceImpl.java @@ -54,7 +54,7 @@ public class BasicWorkflowItemServiceImpl implements BasicWorkflowItemService { } @Override - public int getSupportsTypeConstant() { + public int getSupportsIndexableObjectTypeConstant() { return Constants.WORKFLOWITEM; } @@ -73,7 +73,7 @@ public class BasicWorkflowItemServiceImpl implements BasicWorkflowItemService { } @Override - public BasicWorkflowItem find(Context context, Integer id) throws SQLException { + public BasicWorkflowItem find(Context context, int id) throws SQLException { BasicWorkflowItem workflowItem = workflowItemDAO.findByID(context, BasicWorkflowItem.class, id); if (workflowItem == null) { @@ -90,6 +90,14 @@ public class BasicWorkflowItemServiceImpl implements BasicWorkflowItemService { return workflowItem; } + @Override + public BasicWorkflowItem findIndexableObject(Context context, Integer id) throws SQLException { + if (id != null) { + return find(context, id); + } + return null; + } + @Override public List findAll(Context context) throws SQLException { return workflowItemDAO.findAll(context, BasicWorkflowItem.class); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java index e5f4345e47..b527fc5701 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java @@ -18,10 +18,10 @@ import javax.persistence.ManyToOne; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import org.dspace.browse.BrowsableObject; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.EPerson; /** @@ -34,7 +34,7 @@ import org.dspace.eperson.EPerson; */ @Entity @Table(name = "cwf_claimtask") -public class ClaimedTask implements ReloadableEntity, BrowsableObject { +public class ClaimedTask implements ReloadableEntity, IndexableObject { @Id @Column(name = "claimtask_id") diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTaskServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTaskServiceImpl.java index 4fb85e48a0..92b0db283b 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTaskServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTaskServiceImpl.java @@ -38,7 +38,7 @@ public class ClaimedTaskServiceImpl implements ClaimedTaskService { } @Override - public int getSupportsTypeConstant() { + public int getSupportsIndexableObjectTypeConstant() { return Constants.CLAIMEDTASK; } @@ -53,7 +53,7 @@ public class ClaimedTaskServiceImpl implements ClaimedTaskService { } @Override - public ClaimedTask find(Context context, Integer id) throws SQLException { + public ClaimedTask findIndexableObject(Context context, Integer id) throws SQLException { if (id == null) { return null; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java index 3017bf959a..5c9c2e79a1 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java @@ -19,10 +19,10 @@ import javax.persistence.OneToOne; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import org.dspace.browse.BrowsableObject; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -36,7 +36,7 @@ import org.dspace.eperson.Group; */ @Entity @Table(name = "cwf_pooltask") -public class PoolTask implements ReloadableEntity, BrowsableObject { +public class PoolTask implements ReloadableEntity, IndexableObject { @Id @Column(name = "pooltask_id") diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java index 0aaf51cd1d..abbc981d26 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java @@ -49,7 +49,7 @@ public class PoolTaskServiceImpl implements PoolTaskService { } @Override - public int getSupportsTypeConstant() { + public int getSupportsIndexableObjectTypeConstant() { return Constants.POOLTASK; } @@ -142,7 +142,7 @@ public class PoolTaskServiceImpl implements PoolTaskService { } @Override - public PoolTask find(Context context, Integer id) throws SQLException { + public PoolTask findIndexableObject(Context context, Integer id) throws SQLException { if (id == null) { return null; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java index bbe17b2609..a0e6836656 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItem.java @@ -21,12 +21,12 @@ import javax.persistence.OneToOne; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import org.dspace.browse.BrowsableObject; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.dspace.discovery.IndexableObject; import org.dspace.eperson.EPerson; import org.dspace.workflow.WorkflowItem; @@ -40,7 +40,7 @@ import org.dspace.workflow.WorkflowItem; */ @Entity @Table(name = "cwf_workflowitem") -public class XmlWorkflowItem implements WorkflowItem, ReloadableEntity, BrowsableObject { +public class XmlWorkflowItem implements WorkflowItem, ReloadableEntity, IndexableObject { @Id @Column(name = "workflowitem_id") diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java index cc82e9720d..4545a5f0ce 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java @@ -64,7 +64,7 @@ public class XmlWorkflowItemServiceImpl implements XmlWorkflowItemService { } @Override - public int getSupportsTypeConstant() { + public int getSupportsIndexableObjectTypeConstant() { return Constants.WORKFLOWITEM; } @@ -78,10 +78,9 @@ public class XmlWorkflowItemServiceImpl implements XmlWorkflowItemService { } @Override - public XmlWorkflowItem find(Context context, Integer id) throws SQLException { + public XmlWorkflowItem find(Context context, int id) throws SQLException { XmlWorkflowItem workflowItem = xmlWorkflowItemDAO.findByID(context, XmlWorkflowItem.class, id); - if (workflowItem == null) { if (log.isDebugEnabled()) { log.debug(LogManager.getHeader(context, "find_workflow_item", @@ -96,6 +95,14 @@ public class XmlWorkflowItemServiceImpl implements XmlWorkflowItemService { return workflowItem; } + @Override + public XmlWorkflowItem findIndexableObject(Context context, Integer id) throws SQLException { + if (id != null) { + return find(context, id); + } + return null; + } + @Override public List findAll(Context context) throws SQLException { return xmlWorkflowItemDAO.findAll(context, XmlWorkflowItem.class); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/ClaimedTaskService.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/ClaimedTaskService.java index b16cf72af5..4f85731472 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/ClaimedTaskService.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/ClaimedTaskService.java @@ -11,7 +11,7 @@ import java.sql.SQLException; import java.util.List; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.service.BrowsableObjectService; +import org.dspace.content.service.IndexableObjectService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.service.DSpaceCRUDService; @@ -26,7 +26,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; * @author kevinvandevelde at atmire.com */ public interface ClaimedTaskService extends DSpaceCRUDService, - BrowsableObjectService { + IndexableObjectService { public List findByWorkflowItem(Context context, XmlWorkflowItem workflowItem) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java index 4a85f5f7c8..2ff61d8b07 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/service/PoolTaskService.java @@ -12,7 +12,7 @@ import java.sql.SQLException; import java.util.List; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.service.BrowsableObjectService; +import org.dspace.content.service.IndexableObjectService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.service.DSpaceCRUDService; @@ -26,7 +26,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; * * @author kevinvandevelde at atmire.com */ -public interface PoolTaskService extends DSpaceCRUDService, BrowsableObjectService { +public interface PoolTaskService extends DSpaceCRUDService, IndexableObjectService { public List findByEperson(Context context, EPerson ePerson) throws SQLException, AuthorizeException, IOException; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index d0c8001433..281494c081 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -117,7 +117,6 @@ public class AuthenticationRestController implements InitializingBean { context = ContextUtil.obtainContext(request); - if (context == null || context.getCurrentUser() == null) { // Note that the actual HTTP status in this case is set by // org.dspace.app.rest.security.StatelessLoginFilter.unsuccessfulAuthentication() @@ -128,4 +127,5 @@ public class AuthenticationRestController implements InitializingBean { return ResponseEntity.ok().build(); } } + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/DiscoveryRestController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/DiscoveryRestController.java index 4afde3df07..6b37d40fa7 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/DiscoveryRestController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/DiscoveryRestController.java @@ -70,7 +70,7 @@ public class DiscoveryRestController implements InitializingBean { @RequestMapping(method = RequestMethod.GET) public SearchSupportResource getSearchSupport(@RequestParam(name = "scope", required = false) String dsoScope, @RequestParam(name = "configuration", required = false) String - configurationName) + configuration) throws Exception { SearchSupportRest searchSupportRest = discoveryRestRepository.getSearchSupport(); @@ -82,14 +82,14 @@ public class DiscoveryRestController implements InitializingBean { @RequestMapping(method = RequestMethod.GET, value = "/search") public SearchConfigurationResource getSearchConfiguration( @RequestParam(name = "scope", required = false) String dsoScope, - @RequestParam(name = "configuration", required = false) String configurationName) throws Exception { + @RequestParam(name = "configuration", required = false) String configuration) throws Exception { if (log.isTraceEnabled()) { log.trace("Retrieving search configuration for scope " + StringUtils.trimToEmpty(dsoScope) - + " and configuration name " + StringUtils.trimToEmpty(configurationName)); + + " and configuration name " + StringUtils.trimToEmpty(configuration)); } SearchConfigurationRest searchConfigurationRest = discoveryRestRepository - .getSearchConfiguration(dsoScope, configurationName); + .getSearchConfiguration(dsoScope, configuration); SearchConfigurationResource searchConfigurationResource = new SearchConfigurationResource( searchConfigurationRest); @@ -101,20 +101,20 @@ public class DiscoveryRestController implements InitializingBean { public FacetsResource getFacets(@RequestParam(name = "query", required = false) String query, @RequestParam(name = "dsoType", required = false) String dsoType, @RequestParam(name = "scope", required = false) String dsoScope, - @RequestParam(name = "configuration", required = false) String configurationName, + @RequestParam(name = "configuration", required = false) String configuration, List searchFilters, Pageable page) throws Exception { if (log.isTraceEnabled()) { log.trace("Searching with scope: " + StringUtils.trimToEmpty(dsoScope) - + ", configuration name: " + StringUtils.trimToEmpty(configurationName) + + ", configuration name: " + StringUtils.trimToEmpty(configuration) + ", dsoType: " + StringUtils.trimToEmpty(dsoType) + ", query: " + StringUtils.trimToEmpty(query) + ", filters: " + Objects.toString(searchFilters)); } SearchResultsRest searchResultsRest = discoveryRestRepository - .getAllFacets(query, dsoType, dsoScope, configurationName, searchFilters); + .getAllFacets(query, dsoType, dsoScope, configuration, searchFilters); FacetsResource facetsResource = new FacetsResource(searchResultsRest, page); halLinkService.addLinks(facetsResource, page); @@ -129,12 +129,12 @@ public class DiscoveryRestController implements InitializingBean { @RequestParam(name = "dsoType", required = false) String dsoType, @RequestParam(name = "scope", required = false) String dsoScope, @RequestParam(name = "configuration", required = false) String - configurationName, + configuration, List searchFilters, Pageable page) throws Exception { if (log.isTraceEnabled()) { log.trace("Searching with scope: " + StringUtils.trimToEmpty(dsoScope) - + ", configuration name: " + StringUtils.trimToEmpty(configurationName) + + ", configuration name: " + StringUtils.trimToEmpty(configuration) + ", dsoType: " + StringUtils.trimToEmpty(dsoType) + ", query: " + StringUtils.trimToEmpty(query) + ", filters: " + Objects.toString(searchFilters) @@ -144,7 +144,7 @@ public class DiscoveryRestController implements InitializingBean { //Get the Search results in JSON format SearchResultsRest searchResultsRest = null; searchResultsRest = discoveryRestRepository - .getSearchObjects(query, dsoType, dsoScope, configurationName, searchFilters, page); + .getSearchObjects(query, dsoType, dsoScope, configuration, searchFilters, page); //Convert the Search JSON results to paginated HAL resources SearchResultsResource searchResultsResource = new SearchResultsResource(searchResultsRest, utils, page); @@ -155,15 +155,15 @@ public class DiscoveryRestController implements InitializingBean { @RequestMapping(method = RequestMethod.GET, value = "/facets") public FacetConfigurationResource getFacetsConfiguration( @RequestParam(name = "scope", required = false) String dsoScope, - @RequestParam(name = "configuration", required = false) String configurationName, + @RequestParam(name = "configuration", required = false) String configuration, Pageable pageable) throws Exception { if (log.isTraceEnabled()) { log.trace("Retrieving facet configuration for scope " + StringUtils.trimToEmpty(dsoScope) - + " and configuration name " + StringUtils.trimToEmpty(configurationName)); + + " and configuration name " + StringUtils.trimToEmpty(configuration)); } FacetConfigurationRest facetConfigurationRest = discoveryRestRepository - .getFacetsConfiguration(dsoScope, configurationName); + .getFacetsConfiguration(dsoScope, configuration); FacetConfigurationResource facetConfigurationResource = new FacetConfigurationResource(facetConfigurationRest); halLinkService.addLinks(facetConfigurationResource, pageable); @@ -176,6 +176,8 @@ public class DiscoveryRestController implements InitializingBean { @RequestParam(name = "query", required = false) String query, @RequestParam(name = "dsoType", required = false) String dsoType, @RequestParam(name = "scope", required = false) String dsoScope, + @RequestParam(name = "configuration", required = false) String + configuration, List searchFilters, Pageable page) throws Exception { if (log.isTraceEnabled()) { @@ -188,7 +190,7 @@ public class DiscoveryRestController implements InitializingBean { } FacetResultsRest facetResultsRest = discoveryRestRepository - .getFacetObjects(facetName, prefix, query, dsoType, dsoScope, searchFilters, page); + .getFacetObjects(facetName, prefix, query, dsoType, dsoScope, configuration, searchFilters, page); FacetResultsResource facetResultsResource = new FacetResultsResource(facetResultsRest); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java index 37664d0f51..3ae879564e 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -22,7 +24,6 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.log4j.Logger; - import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.ScopeResolver; import org.dspace.app.util.SyndicationFeed; @@ -30,7 +31,6 @@ import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.OpenSearchService; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -38,17 +38,16 @@ import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.SearchUtils; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoverySearchFilter; - import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; - import org.w3c.dom.Document; /** @@ -113,7 +112,7 @@ public class OpenSearchController { } // then the rest - we are processing the query - DSpaceObject container = null; + IndexableObject container = null; // support pagination parameters DiscoverQuery queryArgs = new DiscoverQuery(); @@ -140,7 +139,7 @@ public class OpenSearchController { // format and return results Map labelMap = getLabels(request); - List dsoResults = qResults.getDspaceObjects(); + List dsoResults = qResults.getIndexableObjects(); Document resultsDoc = openSearchService.getResultsDoc(context, format, query, (int) qResults.getTotalSearchResults(), qResults.getStart(), qResults.getMaxResults(), container, dsoResults, labelMap); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index ee6518168e..973769f138 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -42,7 +42,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public abstract class AInprogressItemConverter, R extends AInprogressSubmissionRest, ID extends Serializable> - extends BrowsableDSpaceObjectConverter { + implements IndexableObjectConverter { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AInprogressItemConverter.class); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java index a278ae313d..ce23af7b3b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityEntryRestConverter extends DSpaceConverter { +public class AuthorityEntryRestConverter implements DSpaceConverter { @Override public AuthorityEntryRest fromModel(Choice choice) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java index 1e973f162b..b07f4d49e7 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java @@ -23,7 +23,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityRestConverter extends DSpaceConverter { +public class AuthorityRestConverter implements DSpaceConverter { @Override public AuthorityRest fromModel(ChoiceAuthority step) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java index b0a01eb11d..7cebeca7ca 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.BitstreamFormatRest; +import org.dspace.content.BitstreamFormat; import org.springframework.stereotype.Component; /** @@ -17,9 +18,9 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class BitstreamFormatConverter extends DSpaceConverter { +public class BitstreamFormatConverter implements DSpaceConverter { @Override - public BitstreamFormatRest fromModel(org.dspace.content.BitstreamFormat obj) { + public BitstreamFormatRest fromModel(BitstreamFormat obj) { BitstreamFormatRest bf = new BitstreamFormatRest(); bf.setDescription(obj.getDescription()); bf.setExtensions(bf.getExtensions()); @@ -31,7 +32,7 @@ public class BitstreamFormatConverter extends DSpaceConverter { +public class BrowseIndexConverter implements DSpaceConverter { @Override public BrowseIndexRest fromModel(BrowseIndex obj) { BrowseIndexRest bir = new BrowseIndexRest(); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java index aa60aaddd9..d448455286 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.ClaimedTaskRest; -import org.dspace.browse.BrowsableObject; +import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; */ @Component public class ClaimedTaskConverter - extends BrowsableDSpaceObjectConverter { + implements IndexableObjectConverter { @Autowired private WorkflowItemConverter workflowItemConverter; @@ -49,7 +49,7 @@ public class ClaimedTaskConverter } @Override - public boolean supportsModel(BrowsableObject object) { + public boolean supportsModel(IndexableObject object) { return object instanceof ClaimedTask; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index c1a13686db..b51b0ff56d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -24,6 +24,7 @@ import org.dspace.content.Collection; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.springframework.beans.factory.annotation.Autowired; @@ -37,7 +38,8 @@ import org.springframework.stereotype.Component; */ @Component public class CollectionConverter - extends DSpaceObjectConverter { + extends DSpaceObjectConverter + implements IndexableObjectConverter { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionConverter.class); @@ -110,4 +112,9 @@ public class CollectionConverter protected Class getModelClass() { return org.dspace.content.Collection.class; } + + @Override + public boolean supportsModel(IndexableObject idxo) { + return idxo instanceof Collection; + } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java index b1883ccff0..09634e30bd 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.model.CommunityRest; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.discovery.IndexableObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -26,7 +27,9 @@ import org.springframework.stereotype.Component; */ @Component public class CommunityConverter - extends DSpaceObjectConverter { + extends DSpaceObjectConverter + implements IndexableObjectConverter { + @Autowired private BitstreamConverter bitstreamConverter; @@ -77,4 +80,9 @@ public class CommunityConverter protected Class getModelClass() { return org.dspace.content.Community.class; } + + @Override + public boolean supportsModel(IndexableObject idxo) { + return idxo instanceof Community; + } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java index 168d6b6175..d195f1ab8d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java @@ -9,9 +9,9 @@ package org.dspace.app.rest.converter; import org.springframework.core.convert.converter.Converter; -public abstract class DSpaceConverter implements Converter { +public interface DSpaceConverter extends Converter { @Override - public R convert(M source) { + public default R convert(M source) { return fromModel(source); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 45bab137b2..42b57983e3 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.converter; -import org.dspace.browse.BrowsableObject; import org.dspace.content.DSpaceObject; import org.springframework.beans.factory.annotation.Autowired; @@ -20,8 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author Andrea Bollini (andrea.bollini at 4science.it) */ public abstract class DSpaceObjectConverter - extends BrowsableDSpaceObjectConverter { + .DSpaceObjectRest> implements DSpaceConverter { @Autowired(required = true) private MetadataConverter metadataConverter; @@ -43,7 +41,7 @@ public abstract class DSpaceObjectConverter searchFilters, DiscoverResult searchResult, @@ -80,8 +84,6 @@ public class DiscoverFacetResultsConverter { facetResultsRest.setSearchFilters(searchFilters); - SearchFilterToAppliedFilterConverter searchFilterToAppliedFilterConverter = new - SearchFilterToAppliedFilterConverter(); for (SearchFilter searchFilter : CollectionUtils.emptyIfNull(searchFilters)) { facetResultsRest .addAppliedFilter(searchFilterToAppliedFilterConverter.convertSearchFilter(context, searchFilter)); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java index baa60abede..c77d9050df 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java @@ -9,10 +9,12 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.SearchFacetValueRest; import org.dspace.discovery.DiscoverResult; +import org.springframework.stereotype.Component; /** * This class' purpose is to convert a DiscoverResult.FacetResult object into a SearchFacetValueRest object */ +@Component public class DiscoverFacetValueConverter { public SearchFacetValueRest convert(final DiscoverResult.FacetResult value) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java index 79e3234855..f7970415e4 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java @@ -50,61 +50,71 @@ public class DiscoverFacetsConverter { return searchResultsRest; } - - private void addFacetValues(Context context, final DiscoverResult searchResult, - final SearchResultsRest searchResultsRest, - final DiscoveryConfiguration configuration) { + /** + * Fill the facet values information in the SearchResultsRest using the information in the api DiscoverResult object + * according to the configuration applied to the discovery query + * + * @param context + * The relevant DSpace context + * @param searchResult + * The DiscoverResult containing the discovery result + * @param resultsRest + * The SearchResultsRest that need to be filled in + * @param configuration + * The DiscoveryConfiguration applied to the query + */ + public void addFacetValues(Context context, final DiscoverResult searchResult, final SearchResultsRest resultsRest, + final DiscoveryConfiguration configuration) { List facets = configuration.getSidebarFacets(); for (DiscoverySearchFilterFacet field : CollectionUtils.emptyIfNull(facets)) { - List facetValues = searchResult.getFacetResult(field); SearchFacetEntryRest facetEntry = new SearchFacetEntryRest(field.getIndexFieldName()); int valueCount = 0; + facetEntry.setHasMore(false); facetEntry.setFacetLimit(field.getFacetLimit()); - facetEntry.setExposeMinMax(field.exposeMinAndMaxValue()); if (field.exposeMinAndMaxValue()) { - handleExposeMinMaxValues(context,field,facetEntry); + handleExposeMinMaxValues(context, field, facetEntry); } + facetEntry.setExposeMinMax(field.exposeMinAndMaxValue()); + facetEntry.setFacetType(field.getType()); for (DiscoverResult.FacetResult value : CollectionUtils.emptyIfNull(facetValues)) { - //The discover results contains max facetLimit + 1 values. If we reach the "+1", indicate that there are - //more results available. + // The discover results contains max facetLimit + 1 values. If we reach the "+1", indicate that there + // are + // more results available. if (valueCount < field.getFacetLimit()) { SearchFacetValueRest valueRest = facetValueConverter.convert(value); + facetEntry.addValue(valueRest); } else { facetEntry.setHasMore(true); } - if (StringUtils.isBlank(facetEntry.getFacetType())) { - facetEntry.setFacetType(value.getFieldType()); - } - valueCount++; } - searchResultsRest.addFacetEntry(facetEntry); + resultsRest.addFacetEntry(facetEntry); } } /** - * This method will fill the facetEntry with the appropriate min and max values if they're not empty - * @param context The relevant DSpace context - * @param field The DiscoverySearchFilterFacet field to search for this value in solr - * @param facetEntry The SearchFacetEntryRest facetEntry for which this needs to be filled in - */ - private void handleExposeMinMaxValues(Context context,DiscoverySearchFilterFacet field, - SearchFacetEntryRest facetEntry) { + * This method will fill the facetEntry with the appropriate min and max values if they're not empty + * + * @param context + * The relevant DSpace context + * @param field + * The DiscoverySearchFilterFacet field to search for this value in solr + * @param facetEntry + * The SearchFacetEntryRest facetEntry for which this needs to be filled in + */ + private void handleExposeMinMaxValues(Context context, DiscoverySearchFilterFacet field, + SearchFacetEntryRest facetEntry) { try { - String minValue = searchService.calculateExtremeValue(context, - field.getIndexFieldName() + "_min", - field.getIndexFieldName() + "_min_sort", - DiscoverQuery.SORT_ORDER.asc); - String maxValue = searchService.calculateExtremeValue(context, - field.getIndexFieldName() + "_max", - field.getIndexFieldName() + "_max_sort", - DiscoverQuery.SORT_ORDER.desc); + String minValue = searchService.calculateExtremeValue(context, field.getIndexFieldName() + "_min", + field.getIndexFieldName() + "_min_sort", DiscoverQuery.SORT_ORDER.asc); + String maxValue = searchService.calculateExtremeValue(context, field.getIndexFieldName() + "_max", + field.getIndexFieldName() + "_max_sort", DiscoverQuery.SORT_ORDER.desc); if (StringUtils.isNotBlank(minValue) && StringUtils.isNotBlank(maxValue)) { facetEntry.setMinValue(minValue); @@ -115,13 +125,12 @@ public class DiscoverFacetsConverter { } } - private void setRequestInformation(final Context context, final String query, final String dsoType, final String configurationName, final String scope, final List searchFilters, final Pageable page, final SearchResultsRest resultsRest) { resultsRest.setQuery(query); - resultsRest.setConfigurationName(configurationName); + resultsRest.setConfiguration(configurationName); resultsRest.setDsoType(dsoType); resultsRest.setSort(SearchResultsRest.Sorting.fromPage(page)); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java index 45470c3e8e..1cd1779e87 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java @@ -12,23 +12,16 @@ import java.util.Map; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.converter.query.SearchQueryConverter; -import org.dspace.app.rest.model.DSpaceObjectRest; -import org.dspace.app.rest.model.SearchFacetEntryRest; -import org.dspace.app.rest.model.SearchFacetValueRest; +import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.SearchResultEntryRest; import org.dspace.app.rest.model.SearchResultsRest; import org.dspace.app.rest.parameter.SearchFilter; -import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; -import org.dspace.discovery.SearchService; -import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.configuration.DiscoveryConfiguration; -import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -43,12 +36,11 @@ public class DiscoverResultConverter { private static final Logger log = Logger.getLogger(DiscoverResultConverter.class); @Autowired - private List converters; - + private List converters; @Autowired - private SearchService searchService; - - private DiscoverFacetValueConverter facetValueConverter = new DiscoverFacetValueConverter(); + private DiscoverFacetsConverter facetConverter; + @Autowired + private SearchFilterToAppliedFilterConverter searchFilterToAppliedFilterConverter; public SearchResultsRest convert(final Context context, final String query, final String dsoType, final String configurationName, final String scope, @@ -69,77 +61,19 @@ public class DiscoverResultConverter { } private void addFacetValues(Context context, final DiscoverResult searchResult, final SearchResultsRest resultsRest, - final DiscoveryConfiguration configuration) { - - List facets = configuration.getSidebarFacets(); - for (DiscoverySearchFilterFacet field : CollectionUtils.emptyIfNull(facets)) { - List facetValues = searchResult.getFacetResult(field); - - SearchFacetEntryRest facetEntry = new SearchFacetEntryRest(field.getIndexFieldName()); - int valueCount = 0; - facetEntry.setHasMore(false); - facetEntry.setFacetLimit(field.getFacetLimit()); - if (field.exposeMinAndMaxValue()) { - handleExposeMinMaxValues(context,field,facetEntry); - } - facetEntry.setExposeMinMax(field.exposeMinAndMaxValue()); - for (DiscoverResult.FacetResult value : CollectionUtils.emptyIfNull(facetValues)) { - //The discover results contains max facetLimit + 1 values. If we reach the "+1", indicate that there are - //more results available. - if (valueCount < field.getFacetLimit()) { - SearchFacetValueRest valueRest = facetValueConverter.convert(value); - - facetEntry.addValue(valueRest); - } else { - facetEntry.setHasMore(true); - } - - if (StringUtils.isBlank(facetEntry.getFacetType())) { - facetEntry.setFacetType(value.getFieldType()); - } - - valueCount++; - } - - resultsRest.addFacetEntry(facetEntry); - } + final DiscoveryConfiguration configuration) { + facetConverter.addFacetValues(context, searchResult, resultsRest, configuration); } - /** - * This method will fill the facetEntry with the appropriate min and max values if they're not empty - * @param context The relevant DSpace context - * @param field The DiscoverySearchFilterFacet field to search for this value in solr - * @param facetEntry The SearchFacetEntryRest facetEntry for which this needs to be filled in - */ - private void handleExposeMinMaxValues(Context context,DiscoverySearchFilterFacet field, - SearchFacetEntryRest facetEntry) { - try { - String minValue = searchService.calculateExtremeValue(context, - field.getIndexFieldName() + "_min", - field.getIndexFieldName() + "_min_sort", - DiscoverQuery.SORT_ORDER.asc); - String maxValue = searchService.calculateExtremeValue(context, - field.getIndexFieldName() + "_max", - field.getIndexFieldName() + "_max_sort", - DiscoverQuery.SORT_ORDER.desc); - - if (StringUtils.isNotBlank(minValue) && StringUtils.isNotBlank(maxValue)) { - facetEntry.setMinValue(minValue); - facetEntry.setMaxValue(maxValue); - } - } catch (SearchServiceException e) { - log.error(e.getMessage(), e); - } - } private void addSearchResults(final DiscoverResult searchResult, final SearchResultsRest resultsRest) { - for (DSpaceObject dspaceObject : CollectionUtils.emptyIfNull(searchResult.getDspaceObjects())) { + for (IndexableObject dspaceObject : CollectionUtils.emptyIfNull(searchResult.getIndexableObjects())) { SearchResultEntryRest resultEntry = new SearchResultEntryRest(); //Convert the DSpace Object to its REST model - resultEntry.setDspaceObject(convertDSpaceObject(dspaceObject)); + resultEntry.setIndexableObject(convertDSpaceObject(dspaceObject)); //Add hit highlighting for this DSO if present - DiscoverResult.DSpaceObjectHighlightResult highlightedResults = searchResult + DiscoverResult.IndexableObjectHighlightResult highlightedResults = searchResult .getHighlightedResults(dspaceObject); if (highlightedResults != null && MapUtils.isNotEmpty(highlightedResults.getHighlightResults())) { for (Map.Entry> metadataHighlight : highlightedResults.getHighlightResults() @@ -152,10 +86,10 @@ public class DiscoverResultConverter { } } - private DSpaceObjectRest convertDSpaceObject(final DSpaceObject dspaceObject) { - for (DSpaceObjectConverter converter : converters) { + private RestAddressableModel convertDSpaceObject(final IndexableObject dspaceObject) { + for (IndexableObjectConverter converter : converters) { if (converter.supportsModel(dspaceObject)) { - return converter.fromModel(dspaceObject); + return converter.convert(dspaceObject); } } return null; @@ -166,7 +100,7 @@ public class DiscoverResultConverter { final List searchFilters, final Pageable page, final SearchResultsRest resultsRest) { resultsRest.setQuery(query); - resultsRest.setConfigurationName(configurationName); + resultsRest.setConfiguration(configurationName); resultsRest.setDsoType(dsoType); resultsRest.setScope(scope); @@ -178,12 +112,9 @@ public class DiscoverResultConverter { SearchQueryConverter searchQueryConverter = new SearchQueryConverter(); List transformedFilters = searchQueryConverter.convert(searchFilters); - SearchFilterToAppliedFilterConverter searchFilterToAppliedFilterConverter = - new SearchFilterToAppliedFilterConverter(); for (SearchFilter searchFilter : CollectionUtils.emptyIfNull(transformedFilters)) { - resultsRest - .addAppliedFilter(searchFilterToAppliedFilterConverter.convertSearchFilter(context, searchFilter)); + .addAppliedFilter(searchFilterToAppliedFilterConverter.convertSearchFilter(context, searchFilter)); } } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java index a38cfe96c5..ae2ef646c5 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component; * representation of an EntityType and vice versa */ @Component -public class EntityTypeConverter extends DSpaceConverter { +public class EntityTypeConverter implements DSpaceConverter { /** * This method converts the EntityType model object that is passed along in the params to the diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java index 1aec86ab83..2194753a54 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java @@ -18,7 +18,7 @@ import org.springframework.stereotype.Component; * representation about the filter query that has to be used for the given EntityType */ @Component -public class FilteredDiscoveryPageConverter extends DSpaceConverter { @Autowired private EntityTypeToFilterQueryService entityTypeToFilterQueryService; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BrowsableDSpaceObjectConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java similarity index 70% rename from dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BrowsableDSpaceObjectConverter.java rename to dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java index 6f018db8dd..e7887bb9c1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/BrowsableDSpaceObjectConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.converter; -import org.dspace.browse.BrowsableObject; +import org.dspace.discovery.IndexableObject; /** * This is the base converter from/to objects in the DSpace API data model and @@ -17,15 +17,15 @@ import org.dspace.browse.BrowsableObject; * @param the Class in the DSpace REST data model * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public abstract class BrowsableDSpaceObjectConverter extends DSpaceConverter { /** * - * @param bdso - * the browsableDSpaceObject to check - * @return true if the actual converter implementation is able to manage the supplied BrowsableDSpaceObject + * @param idxo + * the IndexableObject to check + * @return true if the actual converter implementation is able to manage the supplied IndexableObject */ - public abstract boolean supportsModel(BrowsableObject bdso); + public boolean supportsModel(IndexableObject idxo); } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index fd9f3bd899..f4f682daff 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -25,6 +25,7 @@ import org.dspace.content.Relationship; import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipService; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -35,7 +36,10 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class ItemConverter extends DSpaceObjectConverter { +public class ItemConverter + extends DSpaceObjectConverter + implements IndexableObjectConverter { + @Autowired(required = true) private CollectionConverter collectionConverter; @Autowired(required = true) @@ -120,4 +124,8 @@ public class ItemConverter extends DSpaceObjectConverter { +public class MetadataFieldConverter implements DSpaceConverter { @Autowired(required = true) private MetadataSchemaConverter metadataSchemaConverter; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java index 46017f9608..a0847612ca 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class MetadataSchemaConverter extends DSpaceConverter { +public class MetadataSchemaConverter implements DSpaceConverter { @Override public MetadataSchemaRest fromModel(org.dspace.content.MetadataSchema obj) { MetadataSchemaRest schema = new MetadataSchemaRest(); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java index cc496ab5c8..34299c1b0a 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.PoolTaskRest; -import org.dspace.browse.BrowsableObject; +import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; */ @Component public class PoolTaskConverter - extends BrowsableDSpaceObjectConverter { + implements IndexableObjectConverter { @Autowired private WorkflowItemConverter workflowItemConverter; @@ -57,7 +57,7 @@ public class PoolTaskConverter } @Override - public boolean supportsModel(BrowsableObject object) { + public boolean supportsModel(IndexableObject object) { return object instanceof PoolTask; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java index 9f5d4ba66f..2e46b28341 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java @@ -18,7 +18,7 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException; * representation of an Relationship and vice versa */ @Component -public class RelationshipConverter extends DSpaceConverter { +public class RelationshipConverter implements DSpaceConverter { @Autowired private RelationshipTypeConverter relationshipTypeConverter; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java index ce0065c608..fa8dcc8a1e 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component; * representation of an RelationshipType and vice versa */ @Component -public class RelationshipTypeConverter extends DSpaceConverter { +public class RelationshipTypeConverter implements DSpaceConverter { @Autowired private EntityTypeConverter entityTypeConverter; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java index db7c4daab1..1b67d3b9fc 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class ResourcePolicyConverter extends DSpaceConverter { +public class ResourcePolicyConverter implements DSpaceConverter { @Autowired ResourcePolicyService resourcePolicyService; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SearchFilterToAppliedFilterConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SearchFilterToAppliedFilterConverter.java index 4f85bd9835..4bfce24935 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SearchFilterToAppliedFilterConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SearchFilterToAppliedFilterConverter.java @@ -13,18 +13,28 @@ import org.dspace.authority.AuthorityValue; import org.dspace.authority.service.AuthorityValueService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; /** * This class' purpose is to convert the SearchFilter object into a SearchResultsRest.AppliedFilter object */ +@Component public class SearchFilterToAppliedFilterConverter { @Autowired private AuthorityValueService authorityValueService; public SearchResultsRest.AppliedFilter convertSearchFilter(Context context, SearchFilter searchFilter) { + AuthorityValue authorityValue = null; if (searchFilter.hasAuthorityOperator()) { + // FIXME this is obviously wrong as it assumes that the authorityValueService is able to retrieve the label + // for each Authority. Indeed, the AuthorityValueService regardless to his name is specific of the + // "SOLRAuthority" implementation and should not have a prominent role. + // Moreover, it is not possible to discover which authority is responsible for the value selected in the + // facet as the authority is bind at the metadata level and so a facet could contains values from multiple + // authorities + // https://jira.duraspace.org/browse/DS-4209 authorityValue = authorityValueService.findByUID(context, searchFilter.getValue()); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java index e9abeb8633..cc9c8ddd73 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class SubmissionDefinitionConverter extends DSpaceConverter { +public class SubmissionDefinitionConverter implements DSpaceConverter { private static final Logger log = org.apache.logging.log4j.LogManager .getLogger(SubmissionDefinitionConverter.class); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index 2d3873904f..2675c6a0b1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -36,7 +36,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class SubmissionFormConverter extends DSpaceConverter { +public class SubmissionFormConverter implements DSpaceConverter { private static final String INPUT_TYPE_ONEBOX = "onebox"; private static final String INPUT_TYPE_NAME = "name"; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java index 33cf0303fa..43dd014d27 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java @@ -23,7 +23,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class SubmissionSectionConverter extends DSpaceConverter { +public class SubmissionSectionConverter implements DSpaceConverter { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionSectionConverter.class); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java index 3a370f30b7..ece24ece35 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.WorkflowItemRest; import org.dspace.app.util.SubmissionConfigReaderException; -import org.dspace.browse.BrowsableObject; +import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.stereotype.Component; @@ -41,7 +41,7 @@ public class WorkflowItemConverter } @Override - public boolean supportsModel(BrowsableObject object) { + public boolean supportsModel(IndexableObject object) { return object instanceof XmlWorkflowItem; } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java index 1764c90071..88ba5522af 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java @@ -9,8 +9,8 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.util.SubmissionConfigReaderException; -import org.dspace.browse.BrowsableObject; import org.dspace.content.WorkspaceItem; +import org.dspace.discovery.IndexableObject; import org.springframework.stereotype.Component; /** @@ -41,7 +41,7 @@ public class WorkspaceItemConverter } @Override - public boolean supportsModel(BrowsableObject object) { + public boolean supportsModel(IndexableObject object) { return object instanceof WorkspaceItem; } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/search/DiscoveryRestHalLinkFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/search/DiscoveryRestHalLinkFactory.java index 26d8b4c20d..0479322881 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/search/DiscoveryRestHalLinkFactory.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/search/DiscoveryRestHalLinkFactory.java @@ -29,7 +29,7 @@ public abstract class DiscoveryRestHalLinkFactory extends HalLinkFactory extends HalLinkFactory extends HalLinkFactory extends HalLinkFactory { private SearchResultsRest.Sorting sort; @JsonIgnore private String dsoType; + @JsonIgnore + private List searchFilters; + private String configuration; public String getCategory() { return CATEGORY; @@ -94,16 +98,14 @@ public abstract class DiscoveryResultsRest extends BaseObjectRest { } - public String getConfigurationName() { - return configurationName; + public String getConfiguration() { + return configuration; } - public void setConfigurationName(final String configurationName) { - this.configurationName = configurationName; + public void setConfiguration(final String configuration) { + this.configuration = configuration; } - private String configurationName; - public void setSearchFilters(final List searchFilters) { this.searchFilters = searchFilters; } @@ -112,6 +114,5 @@ public abstract class DiscoveryResultsRest extends BaseObjectRest { return searchFilters; } - @JsonIgnore - private List searchFilters; + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 386a75d8f3..5798b9289b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.model; import java.util.Date; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; import org.dspace.app.rest.RestResourceController; @@ -38,9 +39,6 @@ public class EPersonRest extends DSpaceObjectRest { @JsonProperty(access = Access.WRITE_ONLY) private String password; - // FIXME this should be annotated with @JsonIgnore but right now only simple - // rest resource can be embedded not list, see - // https://jira.duraspace.org/browse/DS-3483 private List groups; @Override @@ -105,6 +103,8 @@ public class EPersonRest extends DSpaceObjectRest { this.password = password; } + @LinkRest(linkClass = GroupRest.class) + @JsonIgnore public List getGroups() { return groups; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ErrorRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ErrorRest.java index 59bf9176dd..172dbf1cf0 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ErrorRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ErrorRest.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -43,7 +43,7 @@ public class ErrorRest { */ public List getPaths() { if (this.paths == null) { - this.paths = new ArrayList(); + this.paths = new LinkedList(); } return paths; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java index 6e4d960a79..4b8244eba2 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java @@ -25,7 +25,7 @@ public class FacetConfigurationRest extends BaseObjectRest { private String scope; - private String configurationName; + private String configuration; @JsonIgnore private LinkedList sidebarFacets = new LinkedList<>(); @@ -50,12 +50,12 @@ public class FacetConfigurationRest extends BaseObjectRest { this.scope = scope; } - public String getConfigurationName() { - return configurationName; + public String getConfiguration() { + return configuration; } - public void setConfigurationName(String configurationName) { - this.configurationName = configurationName; + public void setConfiguration(String configurationName) { + this.configuration = configurationName; } public List getSidebarFacets() { @@ -73,8 +73,8 @@ public class FacetConfigurationRest extends BaseObjectRest { .append(this.getType(), ((FacetConfigurationRest) object).getType()) .append(this.getController(), ((FacetConfigurationRest) object).getController()) .append(this.getScope(), ((FacetConfigurationRest) object).getScope()) - .append(this.getConfigurationName(), - ((FacetConfigurationRest) object).getConfigurationName()) + .append(this.getConfiguration(), + ((FacetConfigurationRest) object).getConfiguration()) .append(this.getSidebarFacets(), ((FacetConfigurationRest) object).getSidebarFacets()) .isEquals()); } @@ -86,7 +86,7 @@ public class FacetConfigurationRest extends BaseObjectRest { .append(this.getType()) .append(this.getController()) .append(this.getScope()) - .append(this.getConfigurationName()) + .append(this.getConfiguration()) .append(this.getSidebarFacets()) .toHashCode(); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/GroupRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/GroupRest.java index 421ecc5587..6cb6a5723d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/GroupRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/GroupRest.java @@ -28,7 +28,6 @@ public class GroupRest extends DSpaceObjectRest { private boolean permanent; - @JsonIgnore private List groups; @Override @@ -57,6 +56,7 @@ public class GroupRest extends DSpaceObjectRest { this.permanent = permanent; } + @JsonIgnore @LinkRest(linkClass = GroupRest.class) public List getGroups() { return groups; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java index 92d21b7a20..7d8c99584c 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java @@ -26,7 +26,7 @@ public class SearchConfigurationRest extends BaseObjectRest { @JsonIgnore private String scope; @JsonIgnore - private String configurationName; + private String configuration; private List filters = new LinkedList<>(); private List sortOptions = new LinkedList<>(); @@ -51,12 +51,12 @@ public class SearchConfigurationRest extends BaseObjectRest { this.scope = scope; } - public String getConfigurationName() { - return configurationName; + public String getConfiguration() { + return configuration; } - public void setConfigurationName(String configurationName) { - this.configurationName = configurationName; + public void setConfiguration(String configurationName) { + this.configuration = configurationName; } public void addFilter(Filter filter) { @@ -82,8 +82,8 @@ public class SearchConfigurationRest extends BaseObjectRest { .append(this.getType(), ((SearchConfigurationRest) object).getType()) .append(this.getController(), ((SearchConfigurationRest) object).getController()) .append(this.getScope(), ((SearchConfigurationRest) object).getScope()) - .append(this.getConfigurationName(), - ((SearchConfigurationRest) object).getConfigurationName()) + .append(this.getConfiguration(), + ((SearchConfigurationRest) object).getConfiguration()) .append(this.getFilters(), ((SearchConfigurationRest) object).getFilters()) .append(this.getSortOptions(), ((SearchConfigurationRest) object).getSortOptions()) .isEquals()); @@ -96,7 +96,7 @@ public class SearchConfigurationRest extends BaseObjectRest { .append(this.getType()) .append(this.getController()) .append(this.getScope()) - .append(this.getConfigurationName()) + .append(this.getConfiguration()) .append(this.getFilters()) .append(this.getSortOptions()) .toHashCode(); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java index 87e47f1bb2..59826816e0 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; + import org.dspace.app.rest.DiscoveryRestController; /** @@ -24,10 +25,9 @@ public class SearchResultEntryRest implements RestAddressableModel { private Map> hitHighlights; + private RestAddressableModel indexableObject; + @JsonIgnore - private DSpaceObjectRest dspaceObject; - - public String getCategory() { return CATEGORY; } @@ -36,6 +36,7 @@ public class SearchResultEntryRest implements RestAddressableModel { return NAME; } + @JsonIgnore public Class getController() { return DiscoveryRestController.class; } @@ -55,11 +56,12 @@ public class SearchResultEntryRest implements RestAddressableModel { this.hitHighlights = hitHighlights; } - public DSpaceObjectRest getDspaceObject() { - return dspaceObject; + @JsonIgnore + public RestAddressableModel getIndexableObject() { + return indexableObject; } - public void setDspaceObject(final DSpaceObjectRest dspaceObject) { - this.dspaceObject = dspaceObject; + public void setIndexableObject(final RestAddressableModel indexableObject) { + this.indexableObject = indexableObject; } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java index c838451336..3c0b852656 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java @@ -11,6 +11,7 @@ import java.util.LinkedList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java index d59227b7a0..75dfabdd2b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.DSpaceObjectRest; +import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.SearchResultEntryRest; import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.utils.Utils; @@ -17,7 +17,7 @@ import org.dspace.app.rest.utils.Utils; */ public class SearchResultEntryResource extends HALResource { - public static final String DSPACE_OBJECT_LINK = "dspaceObject"; + public static final String INDEXABLE_OBJECT_LINK = "indexableObject"; public SearchResultEntryResource(final SearchResultEntryRest data, final Utils utils) { super(data); @@ -27,12 +27,12 @@ public class SearchResultEntryResource extends HALResource page = new PageImpl(binfo.getBrowseItemResults(), pageResultInfo, binfo.getTotal()) - .map(converter); + Pageable pageResultInfo = + new PageRequest((binfo.getStart() - 1) / binfo.getResultsPerPage(), binfo.getResultsPerPage()); + List tmpResult = new ArrayList(); + for (IndexableObject bb : binfo.getBrowseItemResults()) { + tmpResult.add((Item) bb); + } + Page page = new PageImpl(tmpResult, pageResultInfo, binfo.getTotal()).map(converter); return page; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index c9d99992de..93ef0f2c0e 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -373,9 +373,11 @@ public abstract class DSpaceRestRepository searchFilters, final Pageable page) - throws InvalidRequestException, BadRequestException { + throws InvalidRequestException { Context context = obtainContext(); - - DSpaceObject scopeObject = scopeResolver.resolveScope(context, dsoScope); - DiscoveryConfiguration configuration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configurationName, scopeObject); + IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); + DiscoveryConfiguration discoveryConfiguration = searchConfigurationService + .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; try { discoverQuery = queryBuilder - .buildQuery(context, scopeObject, configuration, query, searchFilters, dsoType, page); + .buildQuery(context, scopeObject, discoveryConfiguration, query, searchFilters, dsoType, page); searchResult = searchService.search(context, scopeObject, discoverQuery); } catch (SearchServiceException e) { @@ -115,18 +112,18 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { } return discoverResultConverter - .convert(context, query, dsoType, configurationName, dsoScope, searchFilters, page, searchResult, - configuration); + .convert(context, query, dsoType, configuration, dsoScope, searchFilters, page, searchResult, + discoveryConfiguration); } - public FacetConfigurationRest getFacetsConfiguration(final String dsoScope, final String configurationName) { + public FacetConfigurationRest getFacetsConfiguration(final String dsoScope, final String configuration) { Context context = obtainContext(); - DSpaceObject scopeObject = scopeResolver.resolveScope(context, dsoScope); - DiscoveryConfiguration configuration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configurationName, scopeObject); + IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); + DiscoveryConfiguration discoveryConfiguration = searchConfigurationService + .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); - return discoverFacetConfigurationConverter.convert(configurationName, dsoScope, configuration); + return discoverFacetConfigurationConverter.convert(configuration, dsoScope, discoveryConfiguration); } public SearchSupportRest getSearchSupport() { @@ -134,21 +131,20 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { } public FacetResultsRest getFacetObjects(String facetName, String prefix, String query, String dsoType, - String dsoScope, List searchFilters, Pageable page) + String dsoScope, final String configuration, List searchFilters, Pageable page) throws InvalidRequestException { Context context = obtainContext(); - DSpaceObject scopeObject = scopeResolver.resolveScope(context, dsoScope); - DiscoveryConfiguration configuration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(facetName, scopeObject); + IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); + DiscoveryConfiguration discoveryConfiguration = searchConfigurationService + .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; try { - discoverQuery = queryBuilder - .buildFacetQuery(context, scopeObject, configuration, prefix, query, - searchFilters, dsoType, page, facetName); + discoverQuery = queryBuilder.buildFacetQuery(context, scopeObject, discoveryConfiguration, prefix, query, + searchFilters, dsoType, page, facetName); searchResult = searchService.search(context, scopeObject, discoverQuery); } catch (SearchServiceException e) { @@ -156,36 +152,34 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { //TODO TOM handle search exception } - FacetResultsRest facetResultsRest = discoverFacetResultsConverter - .convert(context, facetName, prefix, query, dsoType, dsoScope, searchFilters, - searchResult, configuration, page); + FacetResultsRest facetResultsRest = discoverFacetResultsConverter.convert(context, facetName, prefix, query, + dsoType, dsoScope, searchFilters, searchResult, discoveryConfiguration, page); return facetResultsRest; } - public SearchResultsRest getAllFacets(String query, String dsoType, String dsoScope, String configurationName, + public SearchResultsRest getAllFacets(String query, String dsoType, String dsoScope, String configuration, List searchFilters) throws InvalidRequestException { Context context = obtainContext(); Pageable page = new PageRequest(1, 1); - DSpaceObject scopeObject = scopeResolver.resolveScope(context, dsoScope); - DiscoveryConfiguration configuration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configurationName, scopeObject); + IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); + DiscoveryConfiguration discoveryConfiguration = searchConfigurationService + .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; try { discoverQuery = queryBuilder - .buildQuery(context, scopeObject, configuration, query, searchFilters, dsoType, page); + .buildQuery(context, scopeObject, discoveryConfiguration, query, searchFilters, dsoType, page); searchResult = searchService.search(context, scopeObject, discoverQuery); } catch (SearchServiceException e) { log.error("Error while searching with Discovery", e); } - SearchResultsRest searchResultsRest = discoverFacetsConverter - .convert(context, query, dsoType, configurationName, dsoScope, searchFilters, page, configuration, - searchResult); + SearchResultsRest searchResultsRest = discoverFacetsConverter.convert(context, query, dsoType, + configuration, dsoScope, searchFilters, page, discoveryConfiguration, searchResult); return searchResultsRest; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index f672e64077..3da43b1751 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -186,7 +186,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository { +public interface LinkRestRepository { public abstract HALResource wrapResource(L model, String... rels); public default boolean isEmbeddableRelation(Object data, String name) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java index a15238ec41..2ef8560ca5 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java @@ -42,6 +42,7 @@ import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -136,6 +137,9 @@ public class PoolTaskRestRepository extends DSpaceRestRepository { @@ -33,7 +35,6 @@ public class EPersonPatch extends DSpaceObjectPatch { * @throws PatchBadRequestException */ protected EPersonRest replace(EPersonRest eperson, Operation operation) { - ResourcePatchOperation patchOperation = patchFactory.getReplaceOperationForPath(operation.getPath()); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java index b34a62d1ea..cc8a331f62 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java @@ -18,6 +18,8 @@ import org.springframework.stereotype.Component; /** * Provides PATCH operations for item updates. + * + * @author Michael Spalti */ @Component public class ItemPatch extends DSpaceObjectPatch { @@ -26,7 +28,7 @@ public class ItemPatch extends DSpaceObjectPatch { ItemOperationFactory patchFactory; /** - * Peforms the replace operation. + * Performs the replace operation. * @param item the rest representation of the item * @param operation the replace operation * @throws UnprocessableEntityException diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java index 27cee32ee4..c979a4f953 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/EPersonOperationFactory.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.repository.patch.factories; import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.repository.patch.factories.impl.EPersonCertificateReplaceOperation; +import org.dspace.app.rest.repository.patch.factories.impl.EPersonEmailReplaceOperation; import org.dspace.app.rest.repository.patch.factories.impl.EPersonLoginReplaceOperation; import org.dspace.app.rest.repository.patch.factories.impl.EPersonNetidReplaceOperation; import org.dspace.app.rest.repository.patch.factories.impl.EPersonPasswordReplaceOperation; @@ -35,13 +36,16 @@ public class EPersonOperationFactory { EPersonCertificateReplaceOperation certificateReplaceOperation; @Autowired - EPersonNetidReplaceOperation netidReplaceOperation; + EPersonNetidReplaceOperation netIdReplaceOperation; - private static final String OPERATION_PASSWORD_CHANGE = "/password"; - private static final String OPERATION_CAN_LOGIN = "/canLogin"; - private static final String OPERATION_REQUIRE_CERTIFICATE = "/certificate"; - private static final String OPERATION_SET_NETID = "/netid"; + @Autowired + EPersonEmailReplaceOperation emailReplaceOperation; + public static final String OPERATION_PASSWORD_CHANGE = "/password"; + public static final String OPERATION_CAN_LOGIN = "/canLogin"; + public static final String OPERATION_REQUIRE_CERTIFICATE = "/certificate"; + public static final String OPERATION_SET_NETID = "/netid"; + public static final String OPERATION_SET_EMAIL = "/email"; /** * Returns the patch instance for the replace operation (based on the operation path). * @@ -59,7 +63,9 @@ public class EPersonOperationFactory { case OPERATION_REQUIRE_CERTIFICATE: return certificateReplaceOperation; case OPERATION_SET_NETID: - return netidReplaceOperation; + return netIdReplaceOperation; + case OPERATION_SET_EMAIL: + return emailReplaceOperation; default: throw new PatchBadRequestException("Missing patch operation for: " + path); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java index 8f75a1fd8c..1ab27d57cc 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonCertificateReplaceOperation.java @@ -37,7 +37,7 @@ public class EPersonCertificateReplaceOperation extends ReplacePatchOperation + * curl -X PATCH http://${dspace.url}/api/epersons/eperson/<:id-eperson> -H " + * Content-Type: application/json" -d '[{ "op": "replace", "path": " + * /email", "value": "new@email"]' + * + * + * @author Michael Spalti + */ +@Component +public class EPersonEmailReplaceOperation extends ReplacePatchOperation + implements ResourcePatchOperation { + @Override + EPersonRest replace(EPersonRest eperson, Operation operation) { + + eperson.setEmail((String) operation.getValue()); + return eperson; + } + + @Override + void checkModelForExistingValue(EPersonRest resource, Operation operation) { + if (resource.getEmail() == null) { + throw new PatchBadRequestException("Attempting to replace a non-existent value."); + } + } + + @Override + protected Class getArrayClassForEvaluation() { + + return String[].class; + } + + @Override + protected Class getClassForEvaluation() { + + return String.class; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonLoginReplaceOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonLoginReplaceOperation.java index d8e5e90c02..533e2804a4 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonLoginReplaceOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/EPersonLoginReplaceOperation.java @@ -36,7 +36,7 @@ public class EPersonLoginReplaceOperation extends ReplacePatchOperation getArrayClassForEvaluation() { + return String[].class; } @Override protected Class getClassForEvaluation() { + return String.class; } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ItemDiscoverableReplaceOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ItemDiscoverableReplaceOperation.java index fca3f31b36..b8e1a63faa 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ItemDiscoverableReplaceOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ItemDiscoverableReplaceOperation.java @@ -40,7 +40,7 @@ public class ItemDiscoverableReplaceOperation extends ReplacePatchOperation if (value instanceof String) { bool = BooleanUtils.toBooleanObject((String) value); if (bool == null) { - // make sure the string was converted to boolean. throw new PatchBadRequestException("Boolean value not provided."); } } else { @@ -67,35 +62,6 @@ public abstract class PatchOperation return bool; } - // This is duplicated code (see org.dspace.app.rest.submit.factory.impl.PatchOperation) - // If it stays here, it should be DRY. Current patch resource patch operations do not - // use these methods since operation values are either strings or booleans. - // These methods handle JsonValueEvaluator instances for json objects and arrays, - // as returned by the JsonPatchConverter. A complete implementation of the PatchOperation - // class will need these methods. - public List evaluateArrayObject(LateObjectEvaluator value) { - List results = new ArrayList(); - T[] list = null; - if (value != null) { - LateObjectEvaluator object = (LateObjectEvaluator) value; - list = (T[]) object.evaluate(getArrayClassForEvaluation()); - } - - for (T t : list) { - results.add(t); - } - return results; - } - - public T evaluateSingleObject(LateObjectEvaluator value) { - T single = null; - if (value != null) { - LateObjectEvaluator object = (LateObjectEvaluator) value; - single = (T) object.evaluate(getClassForEvaluation()); - } - return single; - } - /** * This method should return the typed array to be used in the * LateObjectEvaluator evaluation of json arrays. diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java index 64a1615437..f7c441918b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/ReplacePatchOperation.java @@ -25,6 +25,7 @@ public abstract class ReplacePatchOperation * Before performing the replace operation this method checks * for a non-null operation value and a non-null value on the rest model * (replace operations should only be applied to an existing value). + * * @param resource the rest model. * @param operation the replace patch operation. * @return the updated rest model. @@ -35,7 +36,7 @@ public abstract class ReplacePatchOperation public R perform(R resource, Operation operation) { checkOperationValue(operation.getValue()); - checkModelForExistingValue(resource); + checkModelForExistingValue(resource, operation); return replace(resource, operation); } @@ -43,7 +44,7 @@ public abstract class ReplacePatchOperation /** * Executes the replace patch operation. * - * @param resource the rest model. + * @param resource the rest model. * @param operation the replace patch operation. * @return the updated rest model. * @throws PatchBadRequestException @@ -56,9 +57,11 @@ public abstract class ReplacePatchOperation * Null values may exist in the RestModel for certain fields * (usually non-boolean). This method should be implemented * to assure that the replace operation acts only on an existing value. + * * @param resource the rest model. * @throws PatchBadRequestException */ - abstract void checkModelForExistingValue(R resource); + abstract void checkModelForExistingValue(R resource, Operation operation); + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java index d51cbb80b4..dd04fa95ee 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java @@ -43,8 +43,8 @@ public class AdminRestPermissionEvaluatorPlugin extends RestObjectPermissionEval private EPersonService ePersonService; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, - Object permission) { + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission permission) { //We do not check the "permission" object here because administrators are allowed to do everything diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java index e915f74790..c5e9541976 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java @@ -52,8 +52,8 @@ public class AuthorizeServicePermissionEvaluatorPlugin extends RestObjectPermiss private ContentServiceFactory contentServiceFactory; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, - Object permission) { + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission permission) { DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); if (restPermission == null) { @@ -94,4 +94,5 @@ public class AuthorizeServicePermissionEvaluatorPlugin extends RestObjectPermiss return false; } + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java index 6ec4b3ce97..8304277dff 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java @@ -46,8 +46,8 @@ public class ClaimedTaskRestPermissionEvaluatorPlugin extends RestObjectPermissi private EPersonService ePersonService; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, - String targetType, Object permission) { + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, + String targetType, DSpaceRestPermission permission) { if (Constants.getTypeID(targetType) != Constants.CLAIMEDTASK) { return false; @@ -77,4 +77,5 @@ public class ClaimedTaskRestPermissionEvaluatorPlugin extends RestObjectPermissi } return false; } + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java index a8c6564243..551dd545f2 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java @@ -9,9 +9,14 @@ package org.dspace.app.rest.security; import java.io.Serializable; import java.sql.SQLException; +import java.util.List; import java.util.UUID; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.factories.EPersonOperationFactory; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -33,6 +38,9 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv private static final Logger log = LoggerFactory.getLogger(EPersonRestPermissionEvaluatorPlugin.class); + @Autowired + AuthorizeService authorizeService; + @Autowired private RequestService requestService; @@ -40,9 +48,9 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv private EPersonService ePersonService; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, - String targetType, Object permission) { - //For now this plugin only evaluates READ access + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, + String targetType, DSpaceRestPermission permission) { + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); if (!DSpaceRestPermission.READ.equals(restPermission) && !DSpaceRestPermission.WRITE.equals(restPermission) @@ -55,11 +63,18 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv Request request = requestService.getCurrentRequest(); Context context = ContextUtil.obtainContext(request.getServletRequest()); + EPerson ePerson = null; + try { ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal()); UUID dsoId = UUID.fromString(targetId.toString()); + // anonymous user + if (ePerson == null) { + return false; + } + if (dsoId.equals(ePerson.getID())) { return true; } @@ -70,4 +85,32 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv return false; } + + @Override + public boolean hasPatchPermission(Authentication authentication, Serializable targetId, String targetType, + Patch patch) { + + /** + * First verify that the user has write permission on the eperson. + */ + if (!hasPermission(authentication, targetId, targetType, "WRITE")) { + return false; + } + + List operations = patch.getOperations(); + + /** + * The entire Patch request should be denied if it contains operations that are + * restricted to Dspace administrators. The authenticated user is currently allowed to + * update their own password. + */ + for (Operation op: operations) { + if (!op.getPath().contentEquals(EPersonOperationFactory.OPERATION_PASSWORD_CHANGE)) { + return false; + } + } + + return true; + } + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java index 800c9110ee..91cc302aa1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java @@ -45,8 +45,8 @@ public class GroupRestPermissionEvaluatorPlugin extends RestObjectPermissionEval private EPersonService ePersonService; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, - String targetType, Object permission) { + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, + String targetType, DSpaceRestPermission permission) { //This plugin only evaluates READ access DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java index 00bec2ac87..34b1bbf2cb 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java @@ -48,8 +48,8 @@ public class PoolTaskRestPermissionEvaluatorPlugin extends RestObjectPermissionE private EPersonService ePersonService; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, - String targetType, Object permission) { + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, + String targetType, DSpaceRestPermission permission) { if (Constants.getTypeID(targetType) != Constants.POOLTASK) { return false; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java index 2f467a8fd5..6ee17f8420 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java @@ -7,7 +7,10 @@ */ package org.dspace.app.rest.security; +import java.io.Serializable; + import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.patch.Patch; import org.springframework.security.core.Authentication; /** @@ -27,11 +30,50 @@ public abstract class RestObjectPermissionEvaluatorPlugin implements RestPermis * expression system. This corresponds to the DSpace action. Not null. * @return true if the permission is granted by one of the plugins, false otherwise */ + @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { BaseObjectRest restObject = (BaseObjectRest) targetDomainObject; return hasPermission(authentication, restObject.getId(), restObject.getType(), permission); } + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, + Object permission) { + + if (permission instanceof Patch) { + return hasPatchPermission(authentication, targetId, targetType, (Patch) permission); + } else { + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); + return hasDSpacePermission(authentication, targetId, targetType, restPermission); + } + } + + /** + * Checks permissions for {@link Patch } requests. Override the default implementation in + * plugins that require this capability. + * @param authentication Authentication object providing user details of the authenticated user + * @param targetId Unique identifier of the target object the user wants to view or manipulate + * @param targetType Type of the target object the users wants to view or manipulate + * @param patch The {@link Patch } instance + * @return true if the user is allowed to perform the action described by the permission. False otherwise + */ + public boolean hasPatchPermission(Authentication authentication, Serializable targetId, String targetType, + Patch patch) { + + return hasPermission(authentication, targetId, targetType, "WRITE"); + } + + /** + * Plugins must implement this method to receive {@link RestPermissionEvaluatorPlugin} hasPermission + * requests. + * @param authentication Authentication object providing user details of the authenticated user + * @param targetId Unique identifier of the target object the user wants to view or manipulate + * @param targetType Type of the target object the users wants to view or manipulate + * @param restPermission Permission object that describes the action the user wants to perform on the target object + * @return true if the user is allowed to perform the action described by the permission. False otherwise. + */ + public abstract boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java index 3673a4cada..54040fb5f0 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java @@ -56,8 +56,8 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE private EPersonService ePersonService; @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, - String targetType, Object permission) { + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, + String targetType, DSpaceRestPermission permission) { //This plugin currently only evaluates READ access DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java index 70d97906ae..e1fdad7afd 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java @@ -22,7 +22,6 @@ import org.dspace.app.rest.exception.InvalidSearchFacetException; import org.dspace.app.rest.exception.InvalidSearchFilterException; import org.dspace.app.rest.exception.InvalidSortingException; import org.dspace.app.rest.parameter.SearchFilter; -import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; @@ -31,6 +30,7 @@ import org.dspace.discovery.DiscoverFilterQuery; import org.dspace.discovery.DiscoverHitHighlightingField; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.FacetYearRange; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; @@ -67,7 +67,7 @@ public class DiscoverQueryBuilder implements InitializingBean { pageSizeLimit = configurationService.getIntProperty("rest.search.max.results", 100); } - public DiscoverQuery buildQuery(Context context, DSpaceObject scope, + public DiscoverQuery buildQuery(Context context, IndexableObject scope, DiscoveryConfiguration discoveryConfiguration, String query, List searchFilters, String dsoType, Pageable page) @@ -100,7 +100,7 @@ public class DiscoverQueryBuilder implements InitializingBean { } } - public DiscoverQuery buildFacetQuery(Context context, DSpaceObject scope, + public DiscoverQuery buildFacetQuery(Context context, IndexableObject scope, DiscoveryConfiguration discoveryConfiguration, String prefix, String query, List searchFilters, String dsoType, Pageable page, String facetName) @@ -127,9 +127,9 @@ public class DiscoverQueryBuilder implements InitializingBean { } } - private DiscoverQuery addFacetingForFacets(Context context, DSpaceObject scope, String prefix, - DiscoverQuery queryArgs, DiscoveryConfiguration discoveryConfiguration, - String facetName, Pageable page) throws InvalidSearchFacetException { + private DiscoverQuery addFacetingForFacets(Context context, IndexableObject scope, String prefix, + DiscoverQuery queryArgs, DiscoveryConfiguration discoveryConfiguration, String facetName, Pageable page) + throws InvalidSearchFacetException { DiscoverySearchFilterFacet facet = discoveryConfiguration.getSidebarFacet(facetName); if (facet != null) { @@ -145,12 +145,12 @@ public class DiscoverQueryBuilder implements InitializingBean { return queryArgs; } - private void fillFacetIntoQueryArgs(Context context, DSpaceObject scope, String prefix, DiscoverQuery queryArgs, - DiscoverySearchFilterFacet facet, final int pageSize) { + private void fillFacetIntoQueryArgs(Context context, IndexableObject scope, String prefix, + DiscoverQuery queryArgs, DiscoverySearchFilterFacet facet, final int pageSize) { if (facet.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { try { - FacetYearRange facetYearRange = searchService - .getFacetYearRange(context, scope, facet, queryArgs.getFilterQueries()); + FacetYearRange facetYearRange = + searchService.getFacetYearRange(context, scope, facet, queryArgs.getFilterQueries(), queryArgs); queryArgs.addYearRangeFacet(facet, facetYearRange); @@ -193,6 +193,7 @@ public class DiscoverQueryBuilder implements InitializingBean { private DiscoverQuery buildBaseQueryForConfiguration(DiscoveryConfiguration discoveryConfiguration) { DiscoverQuery queryArgs = new DiscoverQuery(); + queryArgs.setDiscoveryConfigurationName(discoveryConfiguration.getId()); queryArgs.addFilterQueries(discoveryConfiguration.getDefaultFilterQueries() .toArray( new String[discoveryConfiguration.getDefaultFilterQueries() @@ -309,7 +310,7 @@ public class DiscoverQueryBuilder implements InitializingBean { return filterQueries.toArray(new String[filterQueries.size()]); } - private DiscoverQuery addFaceting(Context context, DSpaceObject scope, DiscoverQuery queryArgs, + private DiscoverQuery addFaceting(Context context, IndexableObject scope, DiscoverQuery queryArgs, DiscoveryConfiguration discoveryConfiguration) { List facets = discoveryConfiguration.getSidebarFacets(); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java index 22083beccd..ffc6b16d2d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java @@ -12,10 +12,10 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.content.DSpaceObject; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -33,8 +33,8 @@ public class ScopeResolver { @Autowired CommunityService communityService; - public DSpaceObject resolveScope(Context context, String scope) { - DSpaceObject scopeObj = null; + public IndexableObject resolveScope(Context context, String scope) { + IndexableObject scopeObj = null; if (StringUtils.isNotBlank(scope)) { try { UUID uuid = UUID.fromString(scope); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 19107d46b4..aa476a0249 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -8,8 +8,6 @@ package org.dspace.app.rest; import static java.lang.Thread.sleep; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; @@ -25,7 +23,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Base64; import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.matcher.GroupMatcher; +import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; @@ -72,9 +70,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) - .andExpect(jsonPath("$._embedded.eperson.email", is(eperson.getEmail()))) - .andExpect(jsonPath("$._embedded.eperson.groups", contains( - GroupMatcher.matchGroupWithName("Anonymous")))); + .andExpect(jsonPath("$._embedded.eperson", + EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous"))); } @Test @@ -113,7 +110,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) - .andExpect(jsonPath("$._embedded.eperson.email", is(eperson.getEmail()))); + .andExpect(jsonPath("$._embedded.eperson", + EPersonMatcher.matchEPersonOnEmail(eperson.getEmail()))); getClient(token2).perform(get("/api/authn/status")) @@ -126,7 +124,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) - .andExpect(jsonPath("$._embedded.eperson.email", is(eperson.getEmail()))); + .andExpect(jsonPath("$._embedded.eperson", + EPersonMatcher.matchEPersonOnEmail(eperson.getEmail()))); } @@ -379,9 +378,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) - .andExpect(jsonPath("$._embedded.eperson.email", is(eperson.getEmail()))) - .andExpect(jsonPath("$._embedded.eperson.groups", containsInAnyOrder( - GroupMatcher.matchGroupWithName("Anonymous"), GroupMatcher.matchGroupWithName("Reviewers")))); + .andExpect(jsonPath("$._embedded.eperson", + EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous", "Reviewers"))); } @Test @@ -415,10 +413,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) - .andExpect(jsonPath("$._embedded.eperson.email", is(eperson.getEmail()))) - .andExpect(jsonPath("$._embedded.eperson.groups", containsInAnyOrder( - GroupMatcher.matchGroupWithName("Administrator"), - GroupMatcher.matchGroupWithName("Anonymous")))); + .andExpect(jsonPath("$._embedded.eperson", + EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous", "Administrator"))); //Simulate that a new shibboleth authentication has happened from another IP token = getClient().perform(post("/api/authn/login") @@ -437,9 +433,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) - .andExpect(jsonPath("$._embedded.eperson.email", is(eperson.getEmail()))) - .andExpect(jsonPath("$._embedded.eperson.groups", contains( - GroupMatcher.matchGroupWithName("Anonymous")))); + .andExpect(jsonPath("$._embedded.eperson", + EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous"))); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index a069135419..e911e86f3c 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -13,35 +13,49 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; import java.util.UUID; +import com.jayway.jsonpath.matchers.JsonPathMatchers; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.builder.BitstreamBuilder; +import org.dspace.app.rest.builder.ClaimedTaskBuilder; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.builder.WorkflowItemBuilder; +import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.AppliedFilterMatcher; import org.dspace.app.rest.matcher.FacetEntryMatcher; import org.dspace.app.rest.matcher.FacetValueMatcher; +import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.SearchFilterMatcher; import org.dspace.app.rest.matcher.SearchResultMatcher; import org.dspace.app.rest.matcher.SortOptionMatcher; +import org.dspace.app.rest.matcher.WorkflowItemMatcher; +import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; +import org.springframework.http.MediaType; public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest { @@ -905,14 +919,14 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // given in the structure defined above. //Seeing as everything fits onto one page, they have to all be present .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("community", "communities"), - SearchResultMatcher.match("community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), //This has to be like this because collections don't have anything else SearchResultMatcher.match(), SearchResultMatcher.match(), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) @@ -987,15 +1001,15 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //All elements have to be present in the embedded.objects section, these are the ones we made in // the structure defined above .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("community", "communities"), - SearchResultMatcher.match("community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), //Match without any parameters because collections don't have anything special to check in the // json SearchResultMatcher.match(), SearchResultMatcher.match(), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) @@ -1076,14 +1090,14 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //All the elements created in the structure above have to be present in the embedded.objects section .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("community", "communities"), - SearchResultMatcher.match("community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), //Collections are specified like this because they don't have any special properties SearchResultMatcher.match(), SearchResultMatcher.match(), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) @@ -1158,8 +1172,8 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //Only the two item elements match the query, therefore those are the only ones that can be in the // embedded.objects section .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //We need to display the appliedFilters object that contains the query that we've ran .andExpect(jsonPath("$.appliedFilters", contains( @@ -1238,14 +1252,14 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.scope", is("test"))) //All the elements created in the structure above have to be present in the embedded.objects section .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("community", "communities"), - SearchResultMatcher.match("community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), //Collections are specified like this because they don't have any special properties SearchResultMatcher.match(), SearchResultMatcher.match(), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) @@ -1320,9 +1334,9 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //Only the three items can be present in the embedded.objects section as that's what we specified // in the dsoType parameter .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) @@ -1398,9 +1412,9 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //Only the three items can be present in the embedded.objects section as that's what we specified // in the dsoType parameter .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items"), - SearchResultMatcher.match("item", "items") + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items") ))) //Here we want to match on the item name in a certain specified order because we want to check the // sort properly @@ -1781,8 +1795,8 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //These are the items that aren't set to private .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("community", "communities"), - SearchResultMatcher.match("community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), //Collections are specified like this because they don't have any special properties SearchResultMatcher.match(), SearchResultMatcher.match(), @@ -2987,4 +3001,590 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ; } + + @Test + /** + * This test is intent to verify that inprogress submission (workspaceitem, workflowitem, pool task and claimed + * tasks) don't interfers with the standard search + * + * @throws Exception + */ + public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception { + + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + // the second collection has a workflow active + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withWorkflowGroup(1, admin) + .build(); + + // 2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald").withAuthor("Testing, Works") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test 2") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test") + .withAuthor("test2, test2").withAuthor("Maybe, Maybe") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + context.setCurrentUser(eperson); + WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") + .build(); + + WorkspaceItem wsItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2).withTitle("Workspace Item 2") + .build(); + + XmlWorkflowItem wfItem1 = WorkflowItemBuilder.createWorkflowItem(context, col2).withTitle("Workflow Item 1") + .build(); + + // 4. a claimed task from the administrator + ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") + .build(); + + // 5. other inprogress submissions made by the administrator + context.setCurrentUser(admin); + WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Admin Workspace Item 1").build(); + + WorkspaceItem wsItem2Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withTitle("Admin Workspace Item 2").build(); + + XmlWorkflowItem wfItem1Admin = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withTitle("Admin Workflow Item 1").build(); + + //** WHEN ** + // An anonymous user, the submitter and the admin that browse this endpoint to find the public objects in the + // system should not retrieve the inprogress submissions and related objects + String[] tokens = new String[] { + null, + getAuthToken(eperson.getEmail(), password), + getAuthToken(admin.getEmail(), password), + }; + + for (String token : tokens) { + getClient(token).perform(get("/api/discover/search/objects")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7) + ))) + //These search results have to be shown in the embedded.objects section as these are the items + // given in the structure defined above. + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "community", "communities"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + SearchResultMatcher.match("core", "item", "items"), + //This has to be like this because collections don't have anything else + // these matchers also need to be the last otherwise they will be potentially consumed for + // other staff + SearchResultMatcher.match(), + SearchResultMatcher.match() + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + // property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + } + } + + @Test + /** + * This test is intent to verify that workspaceitem are only visible to the submitter + * + * @throws Exception + */ + public void discoverSearchObjectsWorkspaceConfigurationTest() throws Exception { + + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + // the second collection has a workflow active + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withWorkflowGroup(1, admin) + .build(); + + // 2. Three public items that are readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald").withAuthor("Testing, Works") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test 2") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.com") + .withPassword(password).build(); + context.setCurrentUser(submitter); + // a public item from our submitter + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item from submitter") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test") + .withAuthor("test2, test2").withAuthor("Maybe, Maybe") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + //3. three inprogress submission from our submitter user (2 ws, 1 wf that will produce also a pooltask) + WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2).withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + XmlWorkflowItem wfItem1 = WorkflowItemBuilder.createWorkflowItem(context, col2).withTitle("Workflow Item 1") + .withIssueDate("2010-11-03") + .build(); + + // 4. a claimed task from the administrator + ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") + .withIssueDate("2010-11-03") + .build(); + + // 5. other inprogress submissions made by the administrator + context.setCurrentUser(admin); + WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withIssueDate("2010-07-23") + .withTitle("Admin Workspace Item 1").build(); + + WorkspaceItem wsItem2Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workspace Item 2").build(); + + XmlWorkflowItem wfItem1Admin = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workflow Item 1").build(); + + context.restoreAuthSystemState(); + //** WHEN ** + // each submitter, including the administrator should see only her submission + String submitterToken = getAuthToken(submitter.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(submitterToken).perform(get("/api/discover/search/objects").param("configuration", "workspace")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 5) + ))) + // These search results have to be shown in the embedded.objects section + // one public item, two workspaceitems and two worfklowitems submitted by our submitter user + // as by the structure defined above. + // Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("core", "item", "items"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, + "Public item from submitter", "2010-02-13"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1, + "Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued( + wsItem2, "Workspace Item 2", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + wfItem1, "Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + cTask.getWorkflowItem(), "Claimed Item","2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + // property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + + getClient(adminToken).perform(get("/api/discover/search/objects").param("configuration", "workspace")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) + ))) + // These search results have to be shown in the embedded.objects section two workspaceitems and one + // worfklowitem submitted by the admin user as by the structure defined above. Please note that the + // claimedTask should be not visible here as this is the workspace configuration + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued( + wsItem1Admin, "Admin Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued( + wsItem2Admin, "Admin Workspace Item 2", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + wfItem1Admin, "Admin Workflow Item 1", "2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + // property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + } + + @Test + /** + * This test is intent to verify that tasks are only visible to the appropriate users (reviewers) + * + * @throws Exception + */ + public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception { + + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. Two reviewers + EPerson reviewer1 = EPersonBuilder.createEPerson(context).withEmail("reviewer1@example.com") + .withPassword(password).build(); + EPerson reviewer2 = EPersonBuilder.createEPerson(context).withEmail("reviewer2@example.com") + .withPassword(password).build(); + + // 2. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + // the second collection has two workflow steps active + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withWorkflowGroup(1, admin, reviewer1) + .withWorkflowGroup(2, reviewer2) + .build(); + + // 2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald").withAuthor("Testing, Works") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test 2") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("Testing, Works") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane").withAuthor("test,test") + .withAuthor("test2, test2").withAuthor("Maybe, Maybe") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + context.setCurrentUser(eperson); + WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2).withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + XmlWorkflowItem wfItem1 = WorkflowItemBuilder.createWorkflowItem(context, col2).withTitle("Workflow Item 1") + .withIssueDate("2010-11-03") + .build(); + + // 4. a claimed task from the administrator + ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") + .withIssueDate("2010-11-03") + .build(); + + // 5. other inprogress submissions made by the administrator + context.setCurrentUser(admin); + WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withIssueDate("2010-07-23") + .withTitle("Admin Workspace Item 1").build(); + + WorkspaceItem wsItem2Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workspace Item 2").build(); + + XmlWorkflowItem wfItem1Admin = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workflow Item 1").build(); + + // 6. a pool taks in the second step of the workflow + ClaimedTask cTask2 = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Pool Step2 Item") + .withIssueDate("2010-11-04") + .build(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + + getClient(adminToken).perform(post("/api/workflow/claimedtasks/" + cTask2.getID()) + .param("submit_approve", "true") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + context.restoreAuthSystemState(); + //** WHEN ** + // the submitter should not see anything in the workfow configuration + getClient(epersonToken).perform(get("/api/discover/search/objects").param("configuration", "workflow")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + + // reviewer1 should see two pool items one from the submitter and one from the administrator + // the other task in step1 is claimed by the adminstrator so it should be not visible to the reviewer1 + getClient(reviewer1Token).perform(get("/api/discover/search/objects").param("configuration", "workflow")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2) + ))) + // These search results have to be shown in the embedded.objects section two workspaceitems and one + // worfklowitem submitted by the admin user as by the structure defined above. Please note that the + // claimedTask should be not visible here as this is the workspace configuration + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject._embedded.workflowitem", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject._embedded.workflowitem", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Admin Workflow Item 1", "2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + // property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.submitterFacet(false) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + + // admin should see two pool items one from the submitter and one from herself + // and a claimed task + getClient(adminToken).perform(get("/api/discover/search/objects").param("configuration", "workflow")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) + ))) + // These search results have to be shown in the embedded.objects section two workspaceitems and one + // worfklowitem submitted by the admin user as by the structure defined above. Please note that the + // claimedTask should be not visible here as this is the workspace configuration + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject._embedded.workflowitem", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject._embedded.workflowitem", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Admin Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "claimedtask", "claimedtask"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject._embedded.workflowitem", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Claimed Item", "2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + // property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.submitterFacet(false) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + + // admin should see two pool items one from the submitter and one from herself + // and a claimed task + getClient(reviewer2Token).perform(get("/api/discover/search/objects").param("configuration", "workflow")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1) + ))) + // These search results have to be shown in the embedded.objects section two workspaceitems and one + // worfklowitem submitted by the admin user as by the structure defined above. Please note that the + // claimedTask should be not visible here as this is the workspace configuration + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject._embedded.workflowitem", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Pool Step2 Item", "2010-11-04"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + // property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.submitterFacet(false) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) + ; + + } + } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index d3b8c0b20c..0a7ffe1d35 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -48,6 +48,7 @@ import org.junit.Test; public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { + @Test public void createTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -555,7 +556,6 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { ReplaceOperation replaceOperation = new ReplaceOperation("/netid", "newNetId"); ops.add(replaceOperation); String patchBody = getPatchContent(ops); - // should be unauthorized. getClient().perform(patch("/api/eperson/epersons/" + ePerson.getID()) .content(patchBody) @@ -879,7 +879,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson ePerson = EPersonBuilder.createEPerson(context) .withNameInMetadata("John", "Doe") .withEmail("Johndoe@fake-email.com") - .withPassword("7Testpass") + .withPassword(password) .build(); String newPassword = "newpassword"; @@ -904,6 +904,134 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { } + @Test + public void patchPasswordIsForbidden() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + EPerson ePerson2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jane", "Doe") + .withEmail("Janedoe@fake-email.com") + .withPassword(password) + .build(); + + String newPassword = "newpassword"; + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/password", newPassword); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + // eperson one + String token = getAuthToken(ePerson1.getEmail(), password); + + // should not be able to update the password of eperson two + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson2.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + // login with old password + token = getAuthToken(ePerson2.getEmail(), password); + getClient(token).perform(get("/api/")) + .andExpect(status().isOk()); + + } + @Test + public void patchPasswordForNonAdminUser() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + String newPassword = "newpassword"; + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/password", newPassword); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(ePerson.getEmail(), password); + + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // login with new password + token = getAuthToken(ePerson.getEmail(), newPassword); + getClient(token).perform(get("/api/")) + .andExpect(status().isOk()); + + } + + @Test + public void patchCanLoginNonAdminUser() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("CanLogin@fake-email.com") + .withPassword(password) + .build(); + + context.restoreAuthSystemState(); + + ReplaceOperation replaceOperation = new ReplaceOperation("/canLogin", true); + List ops = new ArrayList(); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(ePerson.getEmail(), password); + + // should return + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + } + + @Test + public void patchCertificateNonAdminUser() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("CanLogin@fake-email.com") + .withPassword(password) + .build(); + + context.restoreAuthSystemState(); + + ReplaceOperation replaceOperation = new ReplaceOperation("/certificate", true); + List ops = new ArrayList(); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(ePerson.getEmail(), password); + + // should return + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + } + @Test public void patchPasswordMissingValue() throws Exception { @@ -949,6 +1077,100 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { } + @Test + public void patchEmail() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + String newEmail = "janedoe@real-email.com"; + + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/email", newEmail); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + // updates email + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(jsonPath("$.email", Matchers.is(newEmail))) + .andExpect(status().isOk()); + + // login with new email address + token = getAuthToken(newEmail, password); + getClient(token).perform(get("/api/")) + .andExpect(status().isOk()); + + } + + @Test + public void patchEmailNonAdminUser() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + context.restoreAuthSystemState(); + + String newEmail = "new@email.com"; + + ReplaceOperation replaceOperation = new ReplaceOperation("/email", newEmail); + List ops = new ArrayList(); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(ePerson.getEmail(), password); + + // returns 403 + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + } + + @Test + public void patchEmailMissingValue() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@fake-email.com") + .withPassword(password) + .build(); + + String token = getAuthToken(admin.getEmail(), password); + + List ops = new ArrayList(); + ReplaceOperation replaceOperation2 = new ReplaceOperation("/email", null); + ops.add(replaceOperation2); + String patchBody = getPatchContent(ops); + + // should return bad request + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isBadRequest()); + + // login with original password + token = getAuthToken(ePerson.getEmail(), password); + getClient(token).perform(get("/api/")) + .andExpect(status().isOk()); + + } + @Test public void patchMultipleOperationsWithSuccess() throws Exception { diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java index 4391ebe9f7..1bc7ef7ffc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java @@ -581,6 +581,135 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.page.totalElements", is(1))); } + @Test + /** + * Test unauthorized claiming of a pool task + * + * @throws Exception + */ + public void claimTaskUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. a reviewer + EPerson reviewer = EPersonBuilder.createEPerson(context) + .withEmail("reviewer@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") + .withWorkflowGroup(1, reviewer).build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //4. a pool task + PoolTask poolTask = PoolTaskBuilder.createPoolTask(context, col1, reviewer) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + XmlWorkflowItem witem = poolTask.getWorkflowItem(); + + context.restoreAuthSystemState(); + + String reviewerToken = getAuthToken(reviewer.getEmail(), password); + + // try to claim the task with an anonymous user + getClient().perform(post("/api/workflow/pooltasks/" + poolTask.getID()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isUnauthorized()); + + // verify that the pool task is still here + getClient(reviewerToken).perform(get("/api/workflow/pooltasks/" + poolTask.getID())) + .andExpect(status().isOk()); + } + + @Test + /** + * Test claiming of another user pool task + * + * @throws Exception + */ + public void claimTaskForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. two reviewers + EPerson reviewer = EPersonBuilder.createEPerson(context) + .withEmail("reviewer@example.com") + .withPassword(password) + .build(); + + EPerson reviewer2 = EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + //2. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") + .withWorkflowGroup(1, reviewer) + .withWorkflowGroup(2, reviewer2).build(); + + //3. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + + context.setCurrentUser(submitter); + + //4. a pool task + PoolTask poolTask = PoolTaskBuilder.createPoolTask(context, col1, reviewer) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + XmlWorkflowItem witem = poolTask.getWorkflowItem(); + + context.restoreAuthSystemState(); + + String reviewerToken = getAuthToken(reviewer.getEmail(), password); + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + + // reviewer2 try to claim a task that is only available for reviewer1 + getClient(reviewer2Token).perform(post("/api/workflow/pooltasks/" + poolTask.getID()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isForbidden()); + + // verify that the pool task is still here + getClient(reviewerToken).perform(get("/api/workflow/pooltasks/" + poolTask.getID())) + .andExpect(status().isOk()); + } + + @Test + public void claimTaskNotExistingTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/workflow/pooltasks/" + Integer.MAX_VALUE) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNotFound()); + } + @Test /** * Test retrieval of a claimed task diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java index fa52905d8f..81e5552f5e 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java @@ -100,6 +100,8 @@ public class ClaimedTaskBuilder extends AbstractBuilder { return this; } + public EPersonBuilder withLanguage(String lang) throws SQLException { + ePerson.setLanguage(context, lang); + return this; + } + + public EPersonBuilder withPhone(String phone) throws SQLException { + ePersonService.setMetadataSingleValue( + context, + ePerson, + "eperson", + "phone", + null, + null, + phone + ); + return this; + } + public EPersonBuilder withGroupMembership(Group group) { groupService.addMember(context, group, ePerson); return this; diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java index aef0a5ce58..7cc32433b7 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java @@ -138,5 +138,4 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return itemService; } - } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java index 751382f8d1..6116840967 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/PoolTaskBuilder.java @@ -76,6 +76,8 @@ public class PoolTaskBuilder extends AbstractBuilder workflowItem = workflowService.start(context, workspaceItem); workspaceItem = null; poolTask = getService().findByWorkflowIdAndEPerson(context, workflowItem, user); + context.dispatchEvents(); + indexingService.commit(); return poolTask; } catch (Exception e) { return handleException(e); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java index eaa41ef3e3..b0095c6373 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java @@ -72,6 +72,8 @@ public class WorkflowItemBuilder extends AbstractBuilder matchEPersonOnEmail(String email) { return allOf( hasJsonPath("$.type", is("eperson")), @@ -44,6 +46,20 @@ public class EPersonMatcher { ); } + public static Matcher matchEPersonWithGroups(String email, String... groups) { + Matcher[] matchers = + (Matcher[]) Array.newInstance(Matcher.class, groups.length); + for (int i = 0; i < groups.length; i++) { + matchers[i] = GroupMatcher.matchGroupWithName(groups[i]); + } + + return allOf( + hasJsonPath("$.type", is("eperson")), + hasJsonPath("$.email", is(email)), + hasJsonPath("$._embedded.groups._embedded.groups", containsInAnyOrder( + matchers))); + } + public static Matcher matchDefaultTestEPerson() { return allOf( hasJsonPath("$.type", is("eperson")) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 5ae6fe5fba..5e3c477506 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -56,6 +56,17 @@ public class FacetEntryMatcher { ); } + public static Matcher submitterFacet(boolean hasNext) { + return allOf( + hasJsonPath("$.name", is("submitter")), + hasJsonPath("$.facetType", is("authority")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/submitter")), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/submitter")) + + ); + } + public static Matcher dateIssuedFacet(boolean hasNext) { return allOf( hasJsonPath("$.name", is("dateIssued")), @@ -88,6 +99,41 @@ public class FacetEntryMatcher { ); } + /** + * Check that a facet over the dc.type exists and match the default configuration + * + * @param b + * true if we expect more values + * @return a Matcher + */ + public static Matcher typeFacet(boolean b) { + return allOf( + hasJsonPath("$.name", is("itemtype")), + hasJsonPath("$.facetType", is("text")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/itemtype")), + hasJsonPath("$._links", matchNextLink(b, "api/discover/facets/itemtype")) + ); + } + + /** + * Check that a facet over the object type (workspaceitem, workflowitem, etc.) exists and match the default + * configuration + * + * @param b + * true if we expect more values + * @return a Matcher + */ + public static Matcher resourceTypeFacet(boolean b) { + return allOf( + hasJsonPath("$.name", is("namedresourcetype")), + hasJsonPath("$.facetType", is("authority")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/namedresourcetype")), + hasJsonPath("$._links", matchNextLink(b, "api/discover/facets/namedresourcetype")) + ); + } + private static AnyOf matchNextLink(boolean hasNext, String path) { return anyOf(hasJsonPath("$.next.href", containsString(path)), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java index c51e1097fd..e675a290d0 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/GroupMatcher.java @@ -10,6 +10,7 @@ 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.containsString; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; import java.util.UUID; @@ -25,16 +26,18 @@ public class GroupMatcher { hasJsonPath("$.uuid", is(uuid.toString())), hasJsonPath("$.name", is(name)), hasJsonPath("$.type", is("group")), - hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/" + uuid.toString())) + hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/" + uuid.toString())), + hasJsonPath("$._links.groups.href", endsWith(uuid.toString() + "/groups")) ); } public static Matcher matchGroupWithName(String name) { return allOf( hasJsonPath("$.name", is(name)), - hasJsonPath("$.type", is("group")) + hasJsonPath("$.type", is("group")), + hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/")), + hasJsonPath("$._links.groups.href", endsWith("/groups")) ); } - } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchResultMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchResultMatcher.java index 40da1be48c..07a37134de 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchResultMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchResultMatcher.java @@ -14,17 +14,18 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import org.hamcrest.Matcher; +import org.hamcrest.Matchers; public class SearchResultMatcher { private SearchResultMatcher() { } - public static Matcher match(String type, String typePlural) { + public static Matcher match(String category, String type, String typePlural) { return allOf( hasJsonPath("$.type", is("discover")), - hasJsonPath("$._links.dspaceObject.href", containsString("/api/core/" + typePlural)), + hasJsonPath("$._links.indexableObject.href", containsString("/api/" + category + "/" + typePlural)), hasJsonPath("$._embedded", notNullValue()), - hasJsonPath("$._embedded.dspaceObject", is( + hasJsonPath("$._embedded.indexableObject", is( matchEmbeddedObject(type) )) ); @@ -38,8 +39,13 @@ public class SearchResultMatcher { private static Matcher matchEmbeddedObject(String type) { return allOf( - hasJsonPath("$.uuid", notNullValue()), - hasJsonPath("$.name", notNullValue()), + Matchers.anyOf( + allOf( + hasJsonPath("$.uuid", notNullValue()), + hasJsonPath("$.name", notNullValue()) + ), + hasJsonPath("$.id", notNullValue()) + ), hasJsonPath("$.type", is(type)) ); } @@ -47,9 +53,9 @@ public class SearchResultMatcher { public static Matcher matchOnItemName(String type, String typePlural, String itemName) { return allOf( hasJsonPath("$.type", is("discover")), - hasJsonPath("$._links.dspaceObject.href", containsString("/api/core/" + typePlural)), + hasJsonPath("$._links.indexableObject.href", containsString("/api/core/" + typePlural)), hasJsonPath("$._embedded", notNullValue()), - hasJsonPath("$._embedded.dspaceObject", is( + hasJsonPath("$._embedded.indexableObject", is( matchEmbeddedObjectOnItemName(type, itemName) )) ); @@ -70,9 +76,9 @@ public class SearchResultMatcher { hasJsonPath("$.type", is("discover")), hasJsonPath("$.hitHighlights", is( HitHighlightMatcher.entry(hitHighlightQuery, expectedFieldInHitHighlightning))), - hasJsonPath("$._links.dspaceObject.href", containsString("/api/core/" + typePlural)), + hasJsonPath("$._links.indexableObject.href", containsString("/api/core/" + typePlural)), hasJsonPath("$._embedded", notNullValue()), - hasJsonPath("$._embedded.dspaceObject", is( + hasJsonPath("$._embedded.indexableObject", is( matchEmbeddedObjectOnItemName(type, itemName) )) ); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java new file mode 100644 index 0000000000..21fcd60b38 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPluginTest.java @@ -0,0 +1,71 @@ +/** + * 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.security; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.core.Authentication; + +/** + * This class verifies that {@link EPersonRestPermissionEvaluatorPlugin} properly + * evaluates Patch requests. + */ +public class EPersonRestPermissionEvaluatorPluginTest { + + private EPersonRestPermissionEvaluatorPlugin ePersonRestPermissionEvaluatorPlugin; + + private Authentication authentication; + + @Before + public void setUp() throws Exception { + ePersonRestPermissionEvaluatorPlugin = spy(EPersonRestPermissionEvaluatorPlugin.class); + authentication = mock(Authentication.class); + DSpaceRestPermission restPermission = DSpaceRestPermission.convert("WRITE"); + when(ePersonRestPermissionEvaluatorPlugin + .hasDSpacePermission(authentication, null, null, restPermission)).thenReturn(true); + } + + @Test + public void testHasPatchPermissionAuthFails() throws Exception { + + List ops = new ArrayList(); + ReplaceOperation passwordOperation = new ReplaceOperation("/password", "testpass"); + ops.add(passwordOperation); + ReplaceOperation canLoginOperation = new ReplaceOperation("/canLogin", false); + ops.add(canLoginOperation); + Patch patch = new Patch(ops); + assertFalse(ePersonRestPermissionEvaluatorPlugin + .hasPatchPermission(authentication, null, null, patch)); + + } + + @Test + public void testHasPatchPermissionAuthOk() throws Exception { + + List ops = new ArrayList(); + ReplaceOperation passwordOperation = new ReplaceOperation("/password", "testpass"); + ops.add(passwordOperation); + Patch patch = new Patch(ops); + assertTrue(ePersonRestPermissionEvaluatorPlugin + .hasPatchPermission(authentication, null, null, patch)); + + } + +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/utils/DiscoverQueryBuilderTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/utils/DiscoverQueryBuilderTest.java index 75a22bf141..b65190f508 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/utils/DiscoverQueryBuilderTest.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/utils/DiscoverQueryBuilderTest.java @@ -28,7 +28,6 @@ import org.dspace.app.rest.exception.InvalidSearchFacetException; import org.dspace.app.rest.exception.InvalidSearchFilterException; import org.dspace.app.rest.exception.InvalidSortingException; import org.dspace.app.rest.parameter.SearchFilter; -import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.DiscoverFacetField; @@ -36,6 +35,7 @@ import org.dspace.discovery.DiscoverFilterQuery; import org.dspace.discovery.DiscoverHitHighlightingField; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.FacetYearRange; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SolrServiceImpl; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; @@ -75,7 +75,7 @@ public class DiscoverQueryBuilderTest { private Context context; @Mock - private DSpaceObject scope; + private IndexableObject scope; private DiscoveryConfiguration discoveryConfiguration; private String query; @@ -92,30 +92,29 @@ public class DiscoverQueryBuilderTest { .then(invocation -> invocation.getArguments()[0] + "_sort"); when(searchService - .getFacetYearRange(eq(context), any(DSpaceObject.class), any(DiscoverySearchFilterFacet.class), any())) - .then(invocation - -> new FacetYearRange((DiscoverySearchFilterFacet) invocation.getArguments()[2])); + .getFacetYearRange(eq(context), any(IndexableObject.class), any(DiscoverySearchFilterFacet.class), + any(), any(DiscoverQuery.class))) + .then(invocation -> new FacetYearRange((DiscoverySearchFilterFacet) invocation.getArguments()[2])); when(searchService.toFilterQuery(any(Context.class), any(String.class), any(String.class), any(String.class))) .then(invocation -> new DiscoverFilterQuery((String) invocation.getArguments()[1], - invocation.getArguments()[1] + ":\"" + invocation - .getArguments()[3] + "\"", - (String) invocation.getArguments()[3])); + invocation.getArguments()[1] + ":\"" + invocation.getArguments()[3] + "\"", + (String) invocation.getArguments()[3])); discoveryConfiguration = new DiscoveryConfiguration(); discoveryConfiguration.setDefaultFilterQueries(Arrays.asList("archived:true")); - DiscoveryHitHighlightingConfiguration discoveryHitHighlightingConfiguration = new - DiscoveryHitHighlightingConfiguration(); + DiscoveryHitHighlightingConfiguration discoveryHitHighlightingConfiguration = + new DiscoveryHitHighlightingConfiguration(); List discoveryHitHighlightFieldConfigurations = new LinkedList<>(); - DiscoveryHitHighlightFieldConfiguration discoveryHitHighlightFieldConfiguration = new - DiscoveryHitHighlightFieldConfiguration(); + DiscoveryHitHighlightFieldConfiguration discoveryHitHighlightFieldConfiguration = + new DiscoveryHitHighlightFieldConfiguration(); discoveryHitHighlightFieldConfiguration.setField("dc.title"); - DiscoveryHitHighlightFieldConfiguration discoveryHitHighlightFieldConfiguration1 = new - DiscoveryHitHighlightFieldConfiguration(); + DiscoveryHitHighlightFieldConfiguration discoveryHitHighlightFieldConfiguration1 = + new DiscoveryHitHighlightFieldConfiguration(); discoveryHitHighlightFieldConfiguration1.setField("fulltext"); discoveryHitHighlightFieldConfigurations.add(discoveryHitHighlightFieldConfiguration1); @@ -164,8 +163,8 @@ public class DiscoverQueryBuilderTest { @Test public void testBuildQuery() throws Exception { - DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, scope, discoveryConfiguration, query, - Arrays.asList(searchFilter), "item", page); + DiscoverQuery discoverQuery = queryBuilder + .buildQuery(context, scope, discoveryConfiguration, query, Arrays.asList(searchFilter), "item", page); assertThat(discoverQuery.getFilterQueries(), containsInAnyOrder("archived:true", "subject:\"Java\"")); assertThat(discoverQuery.getQuery(), is(query)); @@ -177,24 +176,21 @@ public class DiscoverQueryBuilderTest { assertThat(discoverQuery.getFacetMinCount(), is(1)); assertThat(discoverQuery.getFacetOffset(), is(0)); assertThat(discoverQuery.getFacetFields(), hasSize(2)); - assertThat(discoverQuery.getFacetFields(), containsInAnyOrder( - new ReflectionEquals(new DiscoverFacetField("subject", DiscoveryConfigurationParameters.TYPE_TEXT, 6, - DiscoveryConfigurationParameters.SORT.COUNT)), - new ReflectionEquals( - new DiscoverFacetField("hierarchy", DiscoveryConfigurationParameters.TYPE_HIERARCHICAL, 8, - DiscoveryConfigurationParameters.SORT.VALUE)) - )); + assertThat(discoverQuery.getFacetFields(), containsInAnyOrder(new ReflectionEquals( + new DiscoverFacetField("subject", DiscoveryConfigurationParameters.TYPE_TEXT, 6, + DiscoveryConfigurationParameters.SORT.COUNT)), new ReflectionEquals( + new DiscoverFacetField("hierarchy", DiscoveryConfigurationParameters.TYPE_HIERARCHICAL, 8, + DiscoveryConfigurationParameters.SORT.VALUE)))); assertThat(discoverQuery.getHitHighlightingFields(), hasSize(2)); - assertThat(discoverQuery.getHitHighlightingFields(), containsInAnyOrder( - new ReflectionEquals(new DiscoverHitHighlightingField("dc.title", 0, 3)), - new ReflectionEquals(new DiscoverHitHighlightingField("fulltext", 0, 3)) - )); + assertThat(discoverQuery.getHitHighlightingFields(), + containsInAnyOrder(new ReflectionEquals(new DiscoverHitHighlightingField("dc.title", 0, 3)), + new ReflectionEquals(new DiscoverHitHighlightingField("fulltext", 0, 3)))); } @Test public void testBuildQueryDefaults() throws Exception { - DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null, discoveryConfiguration, null, - null, null, null); + DiscoverQuery discoverQuery = + queryBuilder.buildQuery(context, null, discoveryConfiguration, null, null, null, null); assertThat(discoverQuery.getFilterQueries(), containsInAnyOrder("archived:true")); assertThat(discoverQuery.getQuery(), isEmptyOrNullString()); @@ -208,26 +204,23 @@ public class DiscoverQueryBuilderTest { assertThat(discoverQuery.getFacetMinCount(), is(1)); assertThat(discoverQuery.getFacetOffset(), is(0)); assertThat(discoverQuery.getFacetFields(), hasSize(2)); - assertThat(discoverQuery.getFacetFields(), containsInAnyOrder( - new ReflectionEquals(new DiscoverFacetField("subject", DiscoveryConfigurationParameters.TYPE_TEXT, 6, - DiscoveryConfigurationParameters.SORT.COUNT)), - new ReflectionEquals( - new DiscoverFacetField("hierarchy", DiscoveryConfigurationParameters.TYPE_HIERARCHICAL, 8, - DiscoveryConfigurationParameters.SORT.VALUE)) - )); + assertThat(discoverQuery.getFacetFields(), containsInAnyOrder(new ReflectionEquals( + new DiscoverFacetField("subject", DiscoveryConfigurationParameters.TYPE_TEXT, 6, + DiscoveryConfigurationParameters.SORT.COUNT)), new ReflectionEquals( + new DiscoverFacetField("hierarchy", DiscoveryConfigurationParameters.TYPE_HIERARCHICAL, 8, + DiscoveryConfigurationParameters.SORT.VALUE)))); assertThat(discoverQuery.getHitHighlightingFields(), hasSize(2)); - assertThat(discoverQuery.getHitHighlightingFields(), containsInAnyOrder( - new ReflectionEquals(new DiscoverHitHighlightingField("dc.title", 0, 3)), - new ReflectionEquals(new DiscoverHitHighlightingField("fulltext", 0, 3)) - )); + assertThat(discoverQuery.getHitHighlightingFields(), + containsInAnyOrder(new ReflectionEquals(new DiscoverHitHighlightingField("dc.title", 0, 3)), + new ReflectionEquals(new DiscoverHitHighlightingField("fulltext", 0, 3)))); } @Test public void testSortByScore() throws Exception { page = new PageRequest(2, 10, Sort.Direction.ASC, "SCORE"); - DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null, discoveryConfiguration, null, - null, null, page); + DiscoverQuery discoverQuery = + queryBuilder.buildQuery(context, null, discoveryConfiguration, null, null, null, page); assertThat(discoverQuery.getFilterQueries(), containsInAnyOrder("archived:true")); assertThat(discoverQuery.getQuery(), isEmptyOrNullString()); @@ -241,39 +234,36 @@ public class DiscoverQueryBuilderTest { assertThat(discoverQuery.getFacetMinCount(), is(1)); assertThat(discoverQuery.getFacetOffset(), is(0)); assertThat(discoverQuery.getFacetFields(), hasSize(2)); - assertThat(discoverQuery.getFacetFields(), containsInAnyOrder( - new ReflectionEquals(new DiscoverFacetField("subject", DiscoveryConfigurationParameters.TYPE_TEXT, 6, - DiscoveryConfigurationParameters.SORT.COUNT)), - new ReflectionEquals( - new DiscoverFacetField("hierarchy", DiscoveryConfigurationParameters.TYPE_HIERARCHICAL, 8, - DiscoveryConfigurationParameters.SORT.VALUE)) - )); + assertThat(discoverQuery.getFacetFields(), containsInAnyOrder(new ReflectionEquals( + new DiscoverFacetField("subject", DiscoveryConfigurationParameters.TYPE_TEXT, 6, + DiscoveryConfigurationParameters.SORT.COUNT)), new ReflectionEquals( + new DiscoverFacetField("hierarchy", DiscoveryConfigurationParameters.TYPE_HIERARCHICAL, 8, + DiscoveryConfigurationParameters.SORT.VALUE)))); assertThat(discoverQuery.getHitHighlightingFields(), hasSize(2)); - assertThat(discoverQuery.getHitHighlightingFields(), containsInAnyOrder( - new ReflectionEquals(new DiscoverHitHighlightingField("dc.title", 0, 3)), - new ReflectionEquals(new DiscoverHitHighlightingField("fulltext", 0, 3)) - )); + assertThat(discoverQuery.getHitHighlightingFields(), + containsInAnyOrder(new ReflectionEquals(new DiscoverHitHighlightingField("dc.title", 0, 3)), + new ReflectionEquals(new DiscoverHitHighlightingField("fulltext", 0, 3)))); } @Test(expected = InvalidDSpaceObjectTypeException.class) public void testInvalidDSOType() throws Exception { - queryBuilder.buildQuery(context, scope, discoveryConfiguration, query, - Arrays.asList(searchFilter), "TEST", page); + queryBuilder + .buildQuery(context, scope, discoveryConfiguration, query, Arrays.asList(searchFilter), "TEST", page); } @Test(expected = InvalidSortingException.class) public void testInvalidSortField() throws Exception { page = new PageRequest(2, 10, Sort.Direction.ASC, "test"); - queryBuilder.buildQuery(context, scope, discoveryConfiguration, query, - Arrays.asList(searchFilter), "ITEM", page); + queryBuilder + .buildQuery(context, scope, discoveryConfiguration, query, Arrays.asList(searchFilter), "ITEM", page); } @Test(expected = InvalidSearchFilterException.class) public void testInvalidSearchFilter1() throws Exception { searchFilter = new SearchFilter("test", "equals", "Smith, Donald"); - queryBuilder.buildQuery(context, scope, discoveryConfiguration, query, - Arrays.asList(searchFilter), "ITEM", page); + queryBuilder + .buildQuery(context, scope, discoveryConfiguration, query, Arrays.asList(searchFilter), "ITEM", page); } @Test(expected = InvalidSearchFilterException.class) @@ -281,8 +271,8 @@ public class DiscoverQueryBuilderTest { when(searchService.toFilterQuery(any(Context.class), any(String.class), any(String.class), any(String.class))) .thenThrow(SQLException.class); - queryBuilder.buildQuery(context, scope, discoveryConfiguration, query, - Arrays.asList(searchFilter), "ITEM", page); + queryBuilder + .buildQuery(context, scope, discoveryConfiguration, query, Arrays.asList(searchFilter), "ITEM", page); } @Test @@ -310,7 +300,7 @@ public class DiscoverQueryBuilderTest { @Test(expected = InvalidSearchFacetException.class) public void testInvalidSearchFacet() throws Exception { queryBuilder.buildFacetQuery(context, scope, discoveryConfiguration, null, query, - Arrays.asList(searchFilter), "item", page, "test"); + Arrays.asList(searchFilter), "item", page, "test"); } } \ No newline at end of file diff --git a/dspace/config/modules/discovery.cfg b/dspace/config/modules/discovery.cfg index b9d35a65df..27435be08a 100644 --- a/dspace/config/modules/discovery.cfg +++ b/dspace/config/modules/discovery.cfg @@ -14,11 +14,18 @@ discovery.search.server = ${solr.server}/search #Char used to ensure that the sidebar facets are case insensitive #discovery.solr.facets.split.char=\n|||\n -# index.ignore-variants = false -# index.ignore-authority = false +# discovery.index.ignore-variants = false +# discovery.index.ignore-authority = false discovery.index.projection=dc.title,dc.contributor.*,dc.date.issued -# ONLY-FOR-JSPUI: -# 1) you need to set the DiscoverySearchRequestProcessor in the dspace.cfg -# 2) to show facet on Site/Community/etc. you need to add a Site/Community/Collection -# Processors plugin in the dspace.cfg +# Value used for the namedresourcetype facet used by the mydspace +# \n|||\n### +# the separator between the sort-value and the display-value \n|||\n must +# match the value of the discovery.solr.facets.split.char defined above +# the sort-value can be used to force a fixed order for the facet if it is +# configured in the discovery.xml to be sorted by value +discovery.facet.namedtype.item = 000item\n|||\nArchived###item +discovery.facet.namedtype.workspace = 001workspace\n|||\nWorkspace###workspace +discovery.facet.namedtype.workflow.item = 002workflow\n|||\nWorkflow###workflow +discovery.facet.namedtype.workflow.claimed = 003workflow\n|||\nValidation###validation +discovery.facet.namedtype.workflow.pooled = 004workflow\n|||\nWaiting for Controller###waitingforcontroller diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 174655a484..4d41ad7c29 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -50,6 +50,9 @@ + + + @@ -102,7 +105,7 @@ - + @@ -115,7 +118,7 @@ - + @@ -141,13 +144,14 @@ - - - - - - - + + + + + search.resourcetype:2 OR search.resourcetype:3 OR search.resourcetype:4 + + @@ -242,7 +246,7 @@ - + @@ -255,7 +259,7 @@ - + @@ -281,6 +285,14 @@ + + + + + search.resourcetype:2 OR search.resourcetype:3 OR search.resourcetype:4 + + @@ -343,6 +355,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:2 OR search.resourcetype:[8 TO 9] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:[10 TO 11] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -368,70 +532,70 @@ - - - - - - - - - - - - - - + + - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -591,6 +755,48 @@ + + + + + dc.type + + + + + + + + + placeholder.placeholder.placeholder + + + + + + + + + + placeholder.placeholder.placeholder + + + + + + + + + + + placeholder.placeholder.placeholder + + + + diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 11c622f99e..1d98a94c39 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -254,8 +254,12 @@ - + + + + +