Skip to content

Commit

Permalink
Resolves #412 - Access other versions of a resource (#834)
Browse files Browse the repository at this point in the history
* Show versions link

* corrections

* ...

* WIP /{item_id}/versions stub

* newer jdbc

* ...

* try implementing string[] in database manager

* Revert "..."

This reverts commit 68cc479a83de73e66a66e4bfdab072d3005d305d.

* Revert "newer jdbc"

This reverts commit 77b59e69bae92df9105b403d38c44acc22b1d51e.

* Implement the actual logic of /versions

* confused data types

* dropdown

* dropdown displaying versions

* switch the order of replaces/replaced by

* handle foreign items better, update the order

* can get null from resolve

* loading indicator, spaces

* centering of button under i

* reformating and template for dropdown

* show different stuff for replaces only items

* localization strings

* e.stopPropagation or the anchors don't work

* current version

* padding

* Tests for relations chains, no recursive sql.

* Revert "try implementing string[] in database manager"

This reverts commit 7bb9aa5.

* Reverting rest of the changes for TableRow

* sort by dc.date.available

* fix conflicting handles in tests

* left align the dropdown list of versions
  • Loading branch information
kosarko authored Feb 16, 2018
1 parent 41f055d commit 9dd0bbd
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 58 deletions.
60 changes: 55 additions & 5 deletions dspace-api/src/main/java/org/dspace/content/Item.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@
import java.io.InputStream;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.*;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeConfiguration;
Expand Down Expand Up @@ -2246,5 +2242,59 @@ public void setReplacedBy(String pid) {
this.addMetadatum(md);
}
}

/**
* Follow the provided relation and record the visited handles
*
* @param relation - name of relation e.g. isreplacedby or replaces
* @return A list of handles that are in relation to this item
* @throws SQLException
*/
public java.util.Collection<String> getRelationChain(String relation) throws SQLException {
String handle = this.getHandle();
Set<String> relatedHandles = new HashSet<>();

// Are there relations for this item?
TableRow row = DatabaseManager.querySingle(ourContext, "select count(*) as relation_count from metadatavalue mv" +
" natural join metadatafieldregistry join handle h on" +
" h.resource_id = mv.resource_id and h.resource_type_id = mv.resource_type_id" +
" where handle = ? and element = 'relation' and qualifier = ?;",
handle, relation);
if(row == null || row.getIntColumn("relation_count") < 1){
return relatedHandles;
}

//There are relations for this handle so fetch all relations and walk through them
TableRowIterator rows = DatabaseManager.query(ourContext,
"select concat('http://hdl.handle.net/', handle) as handle, text_value " +
"as relation from metadatavalue mv natural join metadatafieldregistry join handle h on " +
"h.resource_id = mv.resource_id and h.resource_type_id = mv.resource_type_id" +
" where element = 'relation' and qualifier=?;", relation);
Map<String,List<String>> handle2relations = new HashMap<>();
while(rows.hasNext()){
row = rows.next(ourContext);
String row_handle = row.getStringColumn("handle");
List<String> relations = handle2relations.get(row_handle);
if(relations == null){
relations = new LinkedList<>();
handle2relations.put(row_handle, relations);
}
relations.add(row.getStringColumn("relation"));
}
LinkedList<String> handlesToProcess = new LinkedList<>();
handlesToProcess.add("http://hdl.handle.net/" + handle);
while(!handlesToProcess.isEmpty()){
List<String> relations = handle2relations.get(handlesToProcess.pop());
if(relations != null) {
for (String rel : relations) {
if(!relatedHandles.contains(rel)) {
relatedHandles.add(rel);
handlesToProcess.push(rel);
}
}
}
}
return relatedHandles;
}
}

100 changes: 96 additions & 4 deletions dspace-api/src/test/java/org/dspace/content/ItemTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
import org.apache.commons.lang.time.DateUtils;
import org.dspace.authorize.AuthorizeException;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import java.util.*;

import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.handle.HandleManager;
import org.dspace.workflow.WorkflowItem;
import org.junit.*;
import static org.junit.Assert.* ;
Expand Down Expand Up @@ -1856,4 +1856,96 @@ public void testFindByAuthorityValue() throws Exception
assertThat("testFindByAuthorityValue 5",result.next(),equalTo(it));
}

@Test
public void testGetRelationChainNotNull() throws SQLException, AuthorizeException
{
context.turnOffAuthorisationSystem();
String handle = "123/123";
HandleManager.createHandle(context, it, handle);
context.commit();
context.restoreAuthSystemState();
assertThat("testSetSubmitter 1", it.getRelationChain("replaces"), notNullValue());
}

@Test
public void testGetRelationChainReplacedByOne() throws SQLException, AuthorizeException
{
context.turnOffAuthorisationSystem();
String handle = "123/456";
HandleManager.createHandle(context, it, handle);

Item replacingItem = Item.create(context);
replacingItem.setArchived(true);
replacingItem.setSubmitter(context.getCurrentUser());
String replaces = "http://hdl.handle.net/" + handle;
replacingItem.addMetadata("dc", "relation", "replaces", null, replaces);
replacingItem.update();

handle = "123/457";
HandleManager.createHandle(context, replacingItem, handle);

String replacedBy = "http://hdl.handle.net/" + handle;
it.setReplacedBy(replacedBy);
it.update();

context.commit();
context.restoreAuthSystemState();
java.util.Collection<String> replacedByChain = it.getRelationChain("isreplacedby");
assertThat("Unexpected size for replacedby", replacedByChain.size(), equalTo(1));
assertTrue("Unexpected replacedby", replacedByChain.contains(replacedBy));
java.util.Collection<String> replacesChain = replacingItem.getRelationChain("replaces");
assertThat("Unexpected size for replaces", replacesChain.size(), equalTo(1));
assertTrue("Unexpected replaces", replacesChain.contains(replaces));
}

@Test
public void testGetRelationChainMany() throws SQLException, AuthorizeException
{
context.turnOffAuthorisationSystem();
String handle = "123/1";
HandleManager.createHandle(context, it, handle);

Item[] items = new Item[9];
items[1] = it;
for(int i = 2; i <= 8; i++){
Item replacingItem = Item.create(context);
replacingItem.setArchived(true);
replacingItem.setSubmitter(context.getCurrentUser());
handle = "123/" + i;
HandleManager.createHandle(context, replacingItem, handle);
items[i] = replacingItem;
}

for(int i : new int[]{2,3,4}){
items[1].setReplacedBy("http://hdl.handle.net/" + items[i].getHandle());
items[i].addMetadata("dc", "relation", "replaces", null, "http://hdl.handle.net/" + items[1].getHandle());
}

items[2].setReplacedBy("http://hdl.handle.net/" + items[5].getHandle());
items[5].addMetadata("dc", "relation", "replaces", null, "http://hdl.handle.net/" + items[2].getHandle());

items[4].setReplacedBy("http://hdl.handle.net/" + items[7].getHandle());
items[7].addMetadata("dc", "relation", "replaces", null, "http://hdl.handle.net/" + items[4].getHandle());

items[3].setReplacedBy("http://hdl.handle.net/" + items[6].getHandle());
items[6].addMetadata("dc", "relation", "replaces", null, "http://hdl.handle.net/" + items[3].getHandle());
items[6].setReplacedBy("http://hdl.handle.net/" + items[7].getHandle());
items[7].addMetadata("dc", "relation", "replaces", null, "http://hdl.handle.net/" + items[6].getHandle());
items[7].setReplacedBy("http://hdl.handle.net/" + items[8].getHandle());
items[8].addMetadata("dc", "relation", "replaces", null, "http://hdl.handle.net/" + items[7].getHandle());

for(int i = 1; i <= 8; i++){
items[i].update();
}

context.commit();
context.restoreAuthSystemState();
assertThat(it.getRelationChain("isreplacedby").size(), equalTo(7));
assertThat(items[4].getRelationChain("isreplacedby").size(), equalTo(2));
assertThat(items[8].getRelationChain("replaces").size(), equalTo(5));
assertThat(items[7].getRelationChain("replaces").size(), equalTo(4));
assertTrue(items[7].getRelationChain("replaces").containsAll(Arrays.asList("http://hdl.handle.net/123/1",
"http://hdl.handle.net/123/3", "http://hdl.handle.net/123/4", "http://hdl.handle.net/123/6")));
}

}
102 changes: 98 additions & 4 deletions dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.*;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
Expand All @@ -37,15 +34,20 @@
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.*;
import org.dspace.content.Collection;
import org.dspace.content.service.ItemService;
import org.dspace.eperson.Group;
import org.dspace.identifier.IdentifierNotFoundException;
import org.dspace.identifier.IdentifierNotResolvableException;
import org.dspace.identifier.IdentifierService;
import org.dspace.rest.common.Bitstream;
import org.dspace.rest.common.Item;
import org.dspace.rest.common.MetadataEntry;
import org.dspace.rest.exceptions.ContextException;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.storage.rdbms.TableRowIterator;
import org.dspace.usage.UsageEvent;
import org.dspace.utils.DSpace;

/**
* Class which provide all CRUD methods over items.
Expand Down Expand Up @@ -1063,6 +1065,98 @@ public Item[] findItemsByMetadataField(MetadataEntry metadataEntry, @QueryParam(
return items.toArray(new Item[0]);
}

/**
* Returns the "versions" of this item. It's obtained by following the "isreplacedby" and "replaces" relations.
* If you have the following graph (where numbers are individual versions),
*
* 2-5
* /
* 1-3-6-7-8
* \ /
* 4
*
* versions of 1 are all the nodes, versions of 4 are just 1; 4 and 7, versions of 8 are all the nodes except 2
* and 5.
*
* @param itemId
* @param expand
* @param user_ip
* @param user_agent
* @param xforwardedfor
* @param headers
* @param request
* @return
* @throws WebApplicationException
*/
@GET
@Path("/{item_id}/versions")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Item[] versions(@PathParam("item_id") Integer itemId, @QueryParam("expand") String expand,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, @Context HttpServletRequest request)
throws WebApplicationException{

org.dspace.core.Context context = null;
Item[] items = new Item[]{};
Map<Item,Date> item2date = new HashMap<>();

try {
context = createContext(headers);
org.dspace.content.Item item = findItem(context, itemId, org.dspace.core.Constants
.READ);
java.util.Collection<String> relations = item.getRelationChain("isreplacedby");
relations.add(item.getHandle());
relations.addAll(item.getRelationChain ("replaces"));


IdentifierService identifierService = new DSpace().getSingletonService(IdentifierService.class);
for(String handleRelation : relations){
org.dspace.content.Item resolvedItem;
try {
resolvedItem = (org.dspace.content.Item)identifierService.resolve(context, handleRelation);
}catch (IdentifierNotFoundException | IdentifierNotResolvableException e){
resolvedItem = null;
}
if(resolvedItem == null) {
Item fakeItem = new Item();
fakeItem.setHandle(handleRelation);
fakeItem.setName(handleRelation);
item2date.put(fakeItem, DCDate.getCurrent().toDate());
}else {
Date submitDate;
Metadatum[] mds = resolvedItem.getMetadata("dc","date", "available",
org.dspace.content.Item.ANY);
if(mds != null && mds.length > 0){
submitDate = new DCDate(mds[0].value).toDate();
}else{
submitDate = DCDate.getCurrent().toDate();
}

item2date.put(new Item(resolvedItem, expand, context), submitDate);
}
}
context.complete();
List<Map.Entry<Item,Date>> entries = new LinkedList<>(item2date.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<Item, Date>>() {
@Override
public int compare(Map.Entry<Item, Date> o1, Map.Entry<Item, Date> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
items = new Item[entries.size()];
int i = 0;
for(Map.Entry<Item, Date> entry : entries){
items[i++] = entry.getKey();
}
}catch (SQLException | ContextException e){
processException("Could not fetch versions(id=" + itemId + "), " + e.getClass().getName() +"." +
" Message:" + e.getMessage(), context);
}finally {
processFinally(context);
}
return items;
}

/**
* Find item from DSpace database. It is encapsulation of method
* org.dspace.content.Item.find with checking if item exist and if user
Expand Down
2 changes: 2 additions & 0 deletions dspace-xmlui/src/main/webapp/i18n/messages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2961,6 +2961,8 @@
<message key="xmlui.UFAL.artifactbrowser.piwik.views">Views Over Time</message>
<message key="xmlui.UFAL.artifactbrowser.item_view.replaced_one">This item is replaced by a newer submission:</message>
<message key="xmlui.UFAL.artifactbrowser.item_view.replaced_many">This item is replaced by newer submissions:</message>
<message key="xmlui.UFAL.artifactbrowser.item_view.versions_dt">Other versions</message>
<message key="xmlui.UFAL.artifactbrowser.item_view.versions_dropdown">List all versions</message>
<message key="xmlui.UFAL.artifactbrowser.item_view.licensed_under">This item is {0} and licensed under:<BR/></message>
<message key="xmlui.UFAL.artifactbrowser.item_view.preview">Preview</message>
<message key="xmlui.UFAL.artifactbrowser.item_view.file_preview">File Preview</message>
Expand Down
Loading

0 comments on commit 9dd0bbd

Please sign in to comment.