Skip to content

Commit

Permalink
Improve lock HTML rendering
Browse files Browse the repository at this point in the history
- Add lock link to locked nodes' RDF

Resolves: https://www.pivotaltracker.com/story/show/69734316
  • Loading branch information
mikedurbin authored and Andrew Woods committed Apr 30, 2014
1 parent c4dafd0 commit d005e6a
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 4 deletions.
Expand Up @@ -20,6 +20,7 @@
import com.hp.hpl.jena.graph.Triple;
import org.fcrepo.http.commons.AbstractResource;
import org.fcrepo.http.commons.api.rdf.HttpIdentifierTranslator;
import org.fcrepo.http.commons.responses.HtmlTemplate;
import org.fcrepo.http.commons.session.InjectedSession;
import org.fcrepo.jcr.FedoraJcrTypes;
import org.fcrepo.kernel.Lock;
Expand Down Expand Up @@ -82,6 +83,7 @@ public class FedoraLocks extends AbstractResource implements FedoraJcrTypes {
* Gets a description of the lock resource.
*/
@GET
@HtmlTemplate(value = "fcr:lock")
@Produces({TURTLE, N3, N3_ALT2, RDF_XML, NTRIPLES, APPLICATION_XML, TEXT_PLAIN, TURTLE_X,
TEXT_HTML, APPLICATION_XHTML_XML})
public RdfStream getLock(@PathParam("path") final List<PathSegment> pathList) throws RepositoryException {
Expand Down Expand Up @@ -149,7 +151,7 @@ private RdfStream getLockRdfStream(Node node, Lock lock) throws RepositoryExcept
final Triple[] lockTriples;
final Triple locksT = create(lockSubject, LOCKS.asNode(), nodeSubject);
final Triple isDeepT = create(lockSubject, IS_DEEP.asNode(),
createLiteral("false", "", XSDDatatype.XSDboolean));
createLiteral(String.valueOf(lock.isDeep()), "", XSDDatatype.XSDboolean));
if (lock.getLockToken() != null) {
lockTriples = new Triple[] {
locksT,
Expand Down
Expand Up @@ -21,6 +21,7 @@
import static java.util.Collections.singletonMap;
import static org.fcrepo.jcr.FedoraJcrTypes.ROOT;
import static org.fcrepo.kernel.RdfLexicon.HAS_FIXITY_SERVICE;
import static org.fcrepo.kernel.RdfLexicon.HAS_LOCK;
import static org.fcrepo.kernel.RdfLexicon.HAS_NAMESPACE_SERVICE;
import static org.fcrepo.kernel.RdfLexicon.HAS_SEARCH_SERVICE;
import static org.fcrepo.kernel.RdfLexicon.HAS_SERIALIZATION;
Expand All @@ -35,6 +36,7 @@
import org.fcrepo.http.api.FedoraExport;
import org.fcrepo.http.api.FedoraFieldSearch;
import org.fcrepo.http.api.FedoraFixity;
import org.fcrepo.http.api.FedoraLocks;
import org.fcrepo.http.api.FedoraSitemap;
import org.fcrepo.http.api.FedoraVersions;
import org.fcrepo.http.api.repository.FedoraRepositoryExport;
Expand All @@ -48,6 +50,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.ws.rs.core.UriInfo;

Expand Down Expand Up @@ -100,6 +103,19 @@ private void addNodeStatements(final FedoraResource resource, final UriInfo uriI
final Map<String, String> pathMap =
singletonMap("path", resource.getPath().substring(1));

// hasLock
if (resource.getNode().isLocked()) {
final String path = resource.getNode().getPath();
final Node lockHoldingNode
= resource.getNode().getSession().getWorkspace()
.getLockManager().getLock(path).getNode();
final Map<String, String> lockedNodePathMap =
singletonMap("path", lockHoldingNode.getPath().substring(1));
model.add(s, HAS_LOCK, createResource(uriInfo
.getBaseUriBuilder().path(FedoraLocks.class).buildFromMap(
lockedNodePathMap).toASCIIString()));
}

// fcr:versions
model.add(s, HAS_VERSION_HISTORY, createResource(uriInfo
.getBaseUriBuilder().path(FedoraVersions.class).buildFromMap(
Expand Down
61 changes: 61 additions & 0 deletions fcrepo-http-api/src/main/resources/views/fcr-lock.vsl
@@ -0,0 +1,61 @@
<!DOCTYPE html>
#set( $title = $helpers.getObjectTitle($rdf, $topic) )

#parse("views/common.vsl")
<html>
<head>
<title>$title</title>
<meta charset="UTF-8">
#parse("views/common-head.vsl")
</head>


<body class="nt_folder">
<div id="main" class="container" resource="$topic.getURI()">
#parse("views/common-node-header.vsl")


<div class="row">

<div class="col-md-12">
#parse("views/common-breadcrumb.vsl")
</div>

<div id="sidebar" class="col-md-3 col-md-push-9 clearfix">
<button id="toggle-actions" type="button" class="visible-xs visible-sm btn btn-danger" data-toggle="collapse" data-target=".actions">
<span>Toggle actions</span>
</button>

## output actions
<div class="actions collapse visible-lg visible-md" id="actions">
<form id="action_delete" name="action_delete" action="javascript:deleteItem()" method="POST">
<input type="hidden" name="_method" value="DELETE" />
<h3>Unlock</h3>
<p>
Deleting this lock will allow the locked resources to be updated by those other
than the lock owner. It is not good practice to delete another user's lock unless
it is known to be abandoned.
</p>
<button name="delete-button" type="submit" class="btn btn-danger">Delete Lock</button>
</form>
</div>
</div>

<div id="metadata" class="col-md-9 col-md-pull-3">

## output triples for the topic node
<div class="panel panel-default">
<div class="panel-heading">
<h4>Properties</h4>
</div>
<div class="panel-body">
#triples($topic)
</div>
</div>

</div>


</div>
</body>
</html>
Expand Up @@ -16,7 +16,10 @@

package org.fcrepo.integration.http.api;

import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.sparql.core.Quad;
import com.hp.hpl.jena.update.GraphStore;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
Expand All @@ -34,6 +37,7 @@
import org.slf4j.Logger;

import java.io.IOException;
import java.util.Iterator;

import static com.hp.hpl.jena.graph.Node.ANY;
import static com.hp.hpl.jena.graph.NodeFactory.createLiteral;
Expand All @@ -43,7 +47,9 @@
import static javax.ws.rs.core.Response.Status.CREATED;
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.HAS_LOCK;
import static org.fcrepo.kernel.RdfLexicon.HAS_LOCK_TOKEN;
import static org.fcrepo.kernel.RdfLexicon.IS_DEEP;
import static org.fcrepo.kernel.RdfLexicon.LOCKS;
import static org.slf4j.LoggerFactory.getLogger;

Expand Down Expand Up @@ -118,6 +124,34 @@ public void testLockMetadataAvailability() throws IOException {
assertUnlockWithToken(pid, lockToken);
}

/**
* Whether a lock is deep or shallow is important information to know
* and should be presented in the metadata for the lock.
* @throws IOException
*/
@Test
public void testDeepIsReflectedInLockMetadata() throws IOException {
final String pid = getRandomUniquePid();
createObject(pid);

final Node lockURI = createURI(serverAddress + pid + "/" + FCR_LOCK);
final Node falseLiteral = createLiteral(String.valueOf(false), "", XSDDatatype.XSDboolean);
final Node trueLiteral = createLiteral(String.valueOf(true), "", XSDDatatype.XSDboolean);

getLockToken(lockObject(pid, true));
GraphStore lockTriples = getLockProperties(pid, null);
Assert.assertTrue("Lock should be listed as deep!",
lockTriples.contains(ANY, lockURI, createURI(IS_DEEP.getURI()), trueLiteral));
assertUnlockWithoutToken(pid);

getLockToken(lockObject(pid, false));
lockTriples = getLockProperties(pid, null);
Assert.assertTrue("Lock should not be listed as deep!",
lockTriples.contains(ANY, lockURI, createURI(IS_DEEP.getURI()), falseLiteral));
assertUnlockWithoutToken(pid);

}

/**
* A basic test to ensure that deep locking prevents updates to child nodes
* while a shallow lock does not.
Expand Down Expand Up @@ -236,6 +270,39 @@ public void testLockMissingNode() throws IOException {
NOT_FOUND.getStatusCode(), lockObject(pid).getStatusLine().getStatusCode());
}

@Test
public void testLockLinkIsPresentLockedNode() throws IOException {
final String pid = getRandomUniquePid();
createObject(pid);
final String lockToken = getLockToken(lockObject(pid));

final Node nodeURI = createURI(serverAddress + pid);
final Node lockURI = createURI(serverAddress + pid + "/" + FCR_LOCK);

final GraphStore store = getGraphStore(getObjectProperties(pid));
Assert.assertTrue("HAS_LOCK assertion should be in the object's RDF.",
store.contains(Node.ANY, nodeURI, HAS_LOCK.asNode(), lockURI));
assertUnlockWithToken(pid, lockToken);
}

@Test
public void testLockLinkIsPresentOnChildrenOfDeepLockedNode() throws IOException {
final String pid = getRandomUniquePid();
final String childPid = pid + "/" + getRandomUniquePid();
createObject(pid);
createObject(childPid);

final String lockToken = getLockToken(lockObject(pid, true));

final Node childNodeURI = createURI(serverAddress + childPid);
final Node lockURI = createURI(serverAddress + pid + "/" + FCR_LOCK);

final GraphStore store = getGraphStore(getObjectProperties(childPid));
Assert.assertTrue("HAS_LOCK assertion should be in the child object's RDF.",
store.contains(Node.ANY, childNodeURI, HAS_LOCK.asNode(), lockURI));
assertUnlockWithToken(pid, lockToken);
}

/**
* Test that a transaction will fail on commit if a lock has been taken
* out on any touched resources between the completion of that operation
Expand Down Expand Up @@ -301,8 +368,7 @@ private void assertUnlockWithToken(String pid, String lockToken) throws IOExcept
*/
private String getLockToken(HttpResponse response) {
final StatusLine status = response.getStatusLine();
Assert.assertEquals(CREATED.getStatusCode(),
response.getStatusLine().getStatusCode());
Assert.assertEquals(CREATED.getStatusCode(), status.getStatusCode());
final Header lockToken = response.getFirstHeader("Lock-Token");
Assert.assertNotNull("Lock-Token was not provided in response!", lockToken);
return lockToken.getValue();
Expand Down
Expand Up @@ -157,7 +157,7 @@ void init() throws IOException, RepositoryException {
final List<String> otherTemplates =
ImmutableList.of("search:results", "jcr:namespaces",
"jcr:workspaces", "jcr:nodetypes",
"node", "fcr:versions");
"node", "fcr:versions", "fcr:lock");

for (final String key : otherTemplates) {
final Template template =
Expand Down
Expand Up @@ -167,6 +167,8 @@ public final class RdfLexicon {
// Locks
public static final Property LOCKS
= createProperty(REPOSITORY_NAMESPACE + "locks");
public static final Property HAS_LOCK
= createProperty(REPOSITORY_NAMESPACE + "hasLock");
public static final Property HAS_LOCK_TOKEN
= createProperty(REPOSITORY_NAMESPACE + "hasLockToken");
public static final Property IS_DEEP
Expand Down

0 comments on commit d005e6a

Please sign in to comment.