Skip to content

Commit

Permalink
Add method to remove a version
Browse files Browse the repository at this point in the history
- Update versionRemove to also remove labels and refuse to remove the current version
- Add HTML UI for deleting versions

Resolves: https://www.pivotaltracker.com/story/show/69177560
  • Loading branch information
escowles authored and Andrew Woods committed Apr 13, 2014
1 parent 7df0138 commit 95a825e
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 6 deletions.
Expand Up @@ -34,7 +34,9 @@

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.version.VersionException;
import javax.jcr.Session;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
Expand All @@ -55,6 +57,7 @@
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
import static javax.ws.rs.core.MediaType.TEXT_HTML;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.noContent;
import static javax.ws.rs.core.Response.status;
Expand Down Expand Up @@ -154,6 +157,29 @@ public Response revertToVersion(@PathParam("path") final List<PathSegment> pathL
}
}

/**
* Removes the version specified by the label.
* @param pathList The resource the version is associated with.
* @param label The version label
* @return 204 No Content
* @throws RepositoryException
**/
@DELETE
@Path("/{label:.+}")
public Response removeVersion(@PathParam("path") final List<PathSegment> pathList,
@PathParam("label") final String label) throws RepositoryException {
final String path = toPath(pathList);
LOGGER.info("Removing {} version {}.", path, label);
try {
versionService.removeVersion(session.getWorkspace(), path, label);
return noContent().build();
} catch ( VersionException ex ) {
return status(BAD_REQUEST).entity(ex.getMessage()).build();
} finally {
session.logout();
}
}

/**
* Create a new version checkpoint with no label.
*/
Expand Down
Expand Up @@ -9,6 +9,9 @@
<form id="action_revert" method="PATCH" action="$uriInfo.getAbsolutePath().toString()" data-redirect-after-submit="$helpers.getVersionSubjectUrl($uriInfo, $topic)" >
<button type="submit" class="btn btn-primary">Revert to this Version</button>
</form>
<form id="action_remove_version" method="DELETE" action="$uriInfo.getAbsolutePath().toString()" data-redirect-after-submit="$helpers.getVersionSubjectUrl($uriInfo, $topic)/fcr:versions" >
<button type="submit" class="btn btn-danger">Delete this Version</button>
</form>
#end

#if ($content != "")
Expand Down Expand Up @@ -175,4 +178,4 @@ WHERE { }
<hr />
</form>
#end
#end
#end
19 changes: 19 additions & 0 deletions fcrepo-http-api/src/main/resources/views/common.js
Expand Up @@ -113,6 +113,7 @@ $(function() {
$('#action_cnd_update').submit(sendCndUpdate);
$('#action_sparql_select').submit(sendSparqlQuery);
$('#action_revert').submit(patchAndReload);
$('#action_remove_version').submit(removeVersion);

});

Expand All @@ -128,6 +129,24 @@ function submitAndFollowLocation() {
return false;
}

function removeVersion() {
var $form = $(this);

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 204 ) {
window.location = $form.attr('data-redirect-after-submit');
} else {
ajaxErrorHandler(xhr, "", "Error removing version");
}
}
}
xhr.open( "DELETE", $form.attr('action') );
xhr.send(null);

return false;
}
function patchAndReload() {
var $form = $(this);
var patchURI = $form.attr('action');
Expand Down
Expand Up @@ -165,4 +165,26 @@ public void testRevertToVersionFailure() throws RepositoryException {
testObj.revertToVersion(createPathList(pid), versionLabel);
}

@Test
public void testRemoveVersion() throws RepositoryException {
final String pid = UUID.randomUUID().toString();
final String versionLabel = UUID.randomUUID().toString();
when(mockNodes.getObject(any(Session.class), anyString())).thenReturn(
mockResource);
final Response response = testObj.removeVersion(createPathList(pid), versionLabel);
verify(mockVersions).removeVersion(testObj.session.getWorkspace(), "/" + pid, versionLabel);
assertNotNull(response);
}

@Test (expected = PathNotFoundException.class)
public void testRemoveVersionFailure() throws RepositoryException {
final String pid = UUID.randomUUID().toString();
final String versionLabel = UUID.randomUUID().toString();
when(mockNodes.getObject(any(Session.class), anyString())).thenReturn(
mockResource);
doThrow(PathNotFoundException.class)
.when(mockVersions).removeVersion(any(Workspace.class), anyString(), anyString());
testObj.removeVersion(createPathList(pid), versionLabel);
}

}
Expand Up @@ -22,6 +22,7 @@
import com.hp.hpl.jena.sparql.core.Quad;
import com.hp.hpl.jena.update.GraphStore;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
Expand All @@ -41,6 +42,8 @@
import static com.hp.hpl.jena.graph.NodeFactory.createLiteral;
import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource;
import static java.util.UUID.randomUUID;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.OK;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static org.fcrepo.kernel.RdfLexicon.DC_TITLE;
Expand Down Expand Up @@ -292,7 +295,6 @@ public void testInvalidVersionReversion() throws Exception {
createObject(objId);
addMixin(objId, MIX_NAMESPACE + "versionable");
final HttpPatch patch = new HttpPatch(serverAddress + objId + "/fcr:versions/invalid-version-label");
execute(patch);
assertEquals(NOT_FOUND.getStatusCode(), getStatus(patch));
}

Expand Down Expand Up @@ -340,6 +342,57 @@ public void testVersionReversion() throws Exception {
patchLiteralProperty(serverAddress + objId, DC_TITLE.getURI(), "additional change");
}

@Test
public void testRemoveVersion() throws Exception {
// create an object and a named version
final String objId = UUID.randomUUID().toString();
final String versionLabel1 = "versionLabelNumberOne";
final String versionLabel2 = "versionLabelNumberTwo";
final Resource subject = createResource(serverAddress + objId);
createObject(objId);
addMixin(objId, MIX_NAMESPACE + "versionable");
postObjectVersion(objId, versionLabel1);
postObjectVersion(objId, versionLabel2);

// make sure the version exists
final HttpGet get1 = new HttpGet(serverAddress + objId + "/fcr:versions/" + versionLabel1);
assertEquals(OK.getStatusCode(), getStatus(get1));

// remove the version we created
final HttpDelete remove = new HttpDelete(serverAddress + objId + "/fcr:versions/" + versionLabel1);
assertEquals(NO_CONTENT.getStatusCode(),getStatus(remove));

// make sure the version is gone
final HttpGet get2 = new HttpGet(serverAddress + objId + "/fcr:versions/" + versionLabel1);
assertEquals(NOT_FOUND.getStatusCode(), getStatus(get2));
}

@Test
public void testRemoveInvalidVersion() throws Exception {
// create an object
final String objId = UUID.randomUUID().toString();
createObject(objId);
addMixin(objId, MIX_NAMESPACE + "versionable");

// removing a non-existent version should 404
final HttpDelete delete = new HttpDelete(serverAddress + objId + "/fcr:versions/invalid-version-label");
assertEquals(NOT_FOUND.getStatusCode(), getStatus(delete));
}

@Test
public void testRemoveCurrentVersion() throws Exception {
// create an object
final String versionLabel = "testVersionNumberUno";
final String objId = UUID.randomUUID().toString();
createObject(objId);
addMixin(objId, MIX_NAMESPACE + "versionable");
postObjectVersion(objId, versionLabel);

// removing a non-existent version should 404
final HttpDelete delete = new HttpDelete(serverAddress + objId + "/fcr:versions/" + versionLabel);
assertEquals(BAD_REQUEST.getStatusCode(), getStatus(delete));
}

private void testDatastreamContentUpdatesCreateNewVersions(final String objName, final String dsName) throws IOException {
final String firstVersionText = "foo";
final String secondVersionText = "bar";
Expand Down Expand Up @@ -440,13 +493,11 @@ public void postDsVersion(final String pid, final String dsId) throws IOExceptio
public void postVersion(final String path, final String label) throws IOException {
logger.info("Posting version");
final HttpPost postVersion = postObjMethod(path + "/fcr:versions" + (label == null ? "" : "/" + label));
execute(postVersion);
assertEquals(NO_CONTENT.getStatusCode(), getStatus(postVersion));
}

private void revertToVersion(String objId, String versionLabel) throws IOException {
final HttpPatch patch = new HttpPatch(serverAddress + objId + "/fcr:versions/" + versionLabel);
execute(patch);
assertEquals(NO_CONTENT.getStatusCode(), getStatus(patch));
}
}
Expand Up @@ -76,6 +76,18 @@ void createVersion(Workspace workspace, Collection<String> paths)
void revertToVersion(Workspace workspace, String absPath, String label)
throws RepositoryException;

/**
* Remove a version of a node. This method will throw a PathNotFoundException
* if no version with the given label is found.
*
* @param workspace the workspace in which the node resides
* @param absPath the path to the node whose version is to be removed
* @param label identifies the historic version by label or id
* @throws RepositoryException
*/
void removeVersion(Workspace workspace, String absPath, String label)
throws RepositoryException;

/**
* Creates a version checkpoint for the given node if versioning is enabled
* for that node type. When versioning is enabled this is the equivalent of
Expand All @@ -91,4 +103,4 @@ void revertToVersion(Workspace workspace, String absPath, String label)
* @param txService the txService to set
*/
void setTxService(final TransactionService txService);
}
}
Expand Up @@ -125,6 +125,28 @@ public void revertToVersion(final Workspace workspace, final String absPath,
}
}

@Override
public void removeVersion(final Workspace workspace, final String absPath,
final String label) throws RepositoryException {
final Version v = getVersionForLabel(workspace, absPath, label);

if (v == null) {
throw new PathNotFoundException("Unknown version \"" + label + "\"!");
} else if (workspace.getVersionManager().getBaseVersion(absPath).equals(v) ) {
throw new VersionException("Cannot remove current version");
} else {
// remove labels
final VersionHistory history = v.getContainingHistory();
final String[] versionLabels = history.getVersionLabels(v);
for ( final String versionLabel : versionLabels ) {
LOGGER.debug("Removing label: {}", versionLabel);
history.removeVersionLabel( versionLabel );
}
history.removeVersion( v.getName() );
}
}


private Version getVersionForLabel(final Workspace workspace, final String absPath,
final String label) throws RepositoryException {
// first see if there's a version label
Expand Down
Expand Up @@ -284,9 +284,81 @@ public void testRevertToVersionByLabelWithAutoVersioning() throws RepositoryExce

testObj.revertToVersion(mockWorkspace, "/example-auto-versioned", versionLabel);
verify(mockVersionManager).restore(mockVersion1, true);

verify(mockVersionManager).checkpoint("/example-auto-versioned");
}

@Test
public void testRemoveVersionByLabel() throws RepositoryException {
String versionLabel = "versionName";
String versionUUID = "uuid";
String versionName = "Bob";
String[] versionLabels = new String[]{ versionLabel };
VersionManager mockVersionManager = mock(VersionManager.class);
VersionHistory mockHistory = mock(VersionHistory.class);
Version mockVersion1 = mock(Version.class);
Version mockVersion2 = mock(Version.class);
when(mockVersion1.getContainingHistory()).thenReturn(mockHistory);
when(mockHistory.hasVersionLabel(versionLabel)).thenReturn(true);
when(mockHistory.getVersionByLabel(versionLabel)).thenReturn(mockVersion1);
when(mockHistory.getVersionLabels(mockVersion1)).thenReturn(versionLabels);
when(mockWorkspace.getVersionManager()).thenReturn(mockVersionManager);
when(mockVersionManager.getVersionHistory("/example")).thenReturn(mockHistory);
when(mockVersionManager.getBaseVersion("/example")).thenReturn(mockVersion2);
Node mockFrozenNode = mock(Node.class);
when(mockVersion1.getFrozenNode()).thenReturn(mockFrozenNode);
when(mockFrozenNode.getIdentifier()).thenReturn(versionUUID);
when(mockVersion1.getIdentifier()).thenReturn(versionUUID);
when(mockVersion1.getName()).thenReturn(versionName);

testObj.removeVersion(mockWorkspace, "/example", versionLabel);
verify(mockHistory).removeVersion(versionName);
verify(mockHistory).removeVersionLabel(versionLabel);
verify(mockVersionManager, never()).checkpoint("/example");
}

@Test
public void testRemoveVersionByUUID() throws RepositoryException {
String versionName = "Bob";
String versionUUID = "uuid";
String[] versionLabels = new String[]{ };
VersionManager mockVersionManager = mock(VersionManager.class);
VersionHistory mockHistory = mock(VersionHistory.class);
Version mockVersion1 = mock(Version.class);
Version mockVersion2 = mock(Version.class);
when(mockVersion1.getContainingHistory()).thenReturn(mockHistory);
when(mockHistory.getVersionByLabel(versionUUID)).thenThrow(VersionException.class);
VersionIterator mockVersionIterator = mock(VersionIterator.class);
when(mockHistory.getAllVersions()).thenReturn(mockVersionIterator);
when(mockHistory.getVersionLabels(mockVersion1)).thenReturn(versionLabels);
when(mockVersionIterator.hasNext()).thenReturn(true);
when(mockVersionIterator.nextVersion()).thenReturn(mockVersion1);
Node mockFrozenNode = mock(Node.class);
when(mockVersion1.getFrozenNode()).thenReturn(mockFrozenNode);
when(mockFrozenNode.getIdentifier()).thenReturn(versionUUID);
when(mockVersion1.getIdentifier()).thenReturn(versionUUID);
when(mockVersion1.getName()).thenReturn(versionName);
when(mockWorkspace.getVersionManager()).thenReturn(mockVersionManager);
when(mockVersionManager.getVersionHistory("/example")).thenReturn(mockHistory);
when(mockVersionManager.getBaseVersion("/example")).thenReturn(mockVersion2);

testObj.removeVersion(mockWorkspace, "/example", versionUUID);
verify(mockHistory).removeVersion(versionName);
}

@Test(expected = PathNotFoundException.class)
public void testRemoveUnknownVersion() throws RepositoryException {
String versionUUID = "uuid";
VersionManager mockVersionManager = mock(VersionManager.class);
VersionHistory mockHistory = mock(VersionHistory.class);
Version mockVersion = mock(Version.class);
when(mockHistory.getVersionByLabel(versionUUID)).thenThrow(VersionException.class);
VersionIterator mockVersionIterator = mock(VersionIterator.class);
when(mockHistory.getAllVersions()).thenReturn(mockVersionIterator);
when(mockVersionIterator.hasNext()).thenReturn(false);
when(mockWorkspace.getVersionManager()).thenReturn(mockVersionManager);
when(mockVersionManager.getVersionHistory("/example")).thenReturn(mockHistory);

testObj.removeVersion(mockWorkspace, "/example", versionUUID);
}

}

0 comments on commit 95a825e

Please sign in to comment.