Skip to content

Commit

Permalink
add a way for modules to inject triples into the describe responses f…
Browse files Browse the repository at this point in the history
…or a resource (to add e.g. HATEOS responsess)
  • Loading branch information
cbeer committed May 29, 2013
1 parent e308b39 commit 070d7c4
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 15 deletions.
6 changes: 6 additions & 0 deletions fcrepo-http-api/pom.xml
Expand Up @@ -26,6 +26,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-kernel</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Apache Velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
Expand Down
Expand Up @@ -95,7 +95,11 @@ public Dataset describe(@PathParam("path")
throw new WebApplicationException(builder.cacheControl(cc)
.lastModified(date).build());
}
return resource.getPropertiesDataset(new HttpGraphSubjects(FedoraNodes.class, uriInfo));
final HttpGraphSubjects subjects = new HttpGraphSubjects(FedoraNodes.class, uriInfo);
final Dataset propertiesDataset = resource.getPropertiesDataset(subjects);
addResponseInformationToDataset(resource, propertiesDataset, uriInfo, subjects);

return propertiesDataset;

} finally {
session.logout();
Expand Down
61 changes: 61 additions & 0 deletions fcrepo-http-api/src/main/java/org/fcrepo/url/HttpApiResources.java
@@ -0,0 +1,61 @@
package org.fcrepo.url;

import com.google.common.collect.ImmutableBiMap;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import org.fcrepo.FedoraResource;
import org.fcrepo.api.FedoraExport;
import org.fcrepo.api.FedoraFieldSearch;
import org.fcrepo.api.FedoraFixity;
import org.fcrepo.api.FedoraNodes;
import org.fcrepo.api.FedoraSitemap;
import org.fcrepo.api.FedoraTransactions;
import org.fcrepo.api.FedoraVersions;
import org.fcrepo.api.rdf.HttpComponentAppender;
import org.fcrepo.api.repository.FedoraRepositoryNamespaces;
import org.fcrepo.rdf.GraphSubjects;
import org.fcrepo.serialization.FedoraObjectSerializer;
import org.springframework.stereotype.Component;

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

import java.util.Map;

@Component
public class HttpApiResources implements HttpComponentAppender {

@javax.annotation.Resource
protected Map<String, FedoraObjectSerializer> serializers;

@Override
public void addResponseInformationToDataset(FedoraResource resource, Model model, UriInfo uriInfo, GraphSubjects graphSubjects) throws RepositoryException {

final Resource s = graphSubjects.getGraphSubject(resource.getNode());

if (resource.getNode().getPrimaryNodeType().isNodeType("mode:root")) {
model.add(s, model.createProperty("http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#link-type-search"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraFieldSearch.class).build().toASCIIString()));
model.add(s, model.createProperty("http://microformats.org/wiki/rel-sitemap"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraSitemap.class).build().toASCIIString()));
model.add(s, model.createProperty("info:fedora/hasTransactionProvider"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraTransactions.class).build().toASCIIString()));
model.add(s, model.createProperty("info:fedora/hasNamespaces"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraRepositoryNamespaces.class).build().toASCIIString()));

} else {

for (String key : serializers.keySet()) {
final Map<String, String> pathMap = ImmutableBiMap.of("path", resource.getPath().substring(1), "format", key);
model.add(s, model.createProperty("info:fedora/exportsAs"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraExport.class).buildFromMap(pathMap).toASCIIString()));
}

final Map<String, String> pathMap = ImmutableBiMap.of("path", resource.getPath().substring(1));
model.add(s, model.createProperty("info:fedora/hasVersions"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraVersions.class).buildFromMap(pathMap).toASCIIString()));

}

if (resource.hasContent()) {
final Map<String, String> pathMap = ImmutableBiMap.of("path", resource.getPath().substring(1));
model.add(s, model.createProperty("info:fedora/runFixityCheck"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraFixity.class).buildFromMap(pathMap).toASCIIString()));
}


}
}
2 changes: 1 addition & 1 deletion fcrepo-http-api/src/test/resources/spring-test/rest.xml
Expand Up @@ -14,7 +14,7 @@
<context:annotation-config/>

<context:component-scan
base-package="org.fcrepo.api, org.fcrepo.serialization, org.fcrepo.responses, org.fcrepo.exceptionhandlers"/>
base-package="org.fcrepo.api, org.fcrepo.serialization, org.fcrepo.responses, org.fcrepo.exceptionhandlers, org.fcrepo.url"/>

<util:map id="serializers" key-type="java.lang.String" map-class="java.util.HashMap"
value-type="org.fcrepo.serialization.FedoraObjectSerializer">
Expand Down
14 changes: 12 additions & 2 deletions fcrepo-http-commons/src/main/java/org/fcrepo/AbstractResource.java
Expand Up @@ -2,7 +2,6 @@
package org.fcrepo;

import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
import static javax.ws.rs.core.Response.noContent;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.IOException;
Expand All @@ -17,15 +16,17 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;

import com.hp.hpl.jena.query.Dataset;
import org.apache.commons.io.IOUtils;
import org.apache.jena.riot.WebContent;
import org.fcrepo.api.rdf.HttpComponentInjector;
import org.fcrepo.api.rdf.HttpGraphSubjects;
import org.fcrepo.exception.InvalidChecksumException;
import org.fcrepo.identifiers.PidMinter;
import org.fcrepo.rdf.GraphSubjects;
import org.fcrepo.services.DatastreamService;
import org.fcrepo.services.NodeService;
import org.fcrepo.services.ObjectService;
Expand Down Expand Up @@ -79,6 +80,9 @@ public abstract class AbstractResource {
@Autowired
protected DatastreamService datastreamService;

@Autowired(required=false)
private HttpComponentInjector httpComponentInjector;

/**
* A resource that can mint new Fedora PIDs.
*/
Expand Down Expand Up @@ -183,6 +187,12 @@ protected FedoraResource createObjectOrDatastreamFromRequestContent(
return result;
}

protected void addResponseInformationToDataset(final FedoraResource resource, final Dataset dataset, final UriInfo uriInfo, GraphSubjects subjects) throws RepositoryException {
if (httpComponentInjector != null) {
httpComponentInjector.addResponseInformationToDataset(resource, dataset, uriInfo, subjects);
}
}

/**
* A testing convenience setter for otherwise injected resources
* @param repo
Expand Down
@@ -0,0 +1,12 @@
package org.fcrepo.api.rdf;

import com.hp.hpl.jena.rdf.model.Model;
import org.fcrepo.FedoraResource;
import org.fcrepo.rdf.GraphSubjects;

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

public interface HttpComponentAppender {

This comment has been minimized.

Copy link
@cbeer

cbeer May 29, 2013

Author Contributor

Suggestions for better names?

This comment has been minimized.

Copy link
@awoods

awoods May 30, 2013

Interface = HttpResourceAppender?
with method = addResourceInformationToDataset(...)
Impl = HttpApiResourceAppender, or HttpResourceAppenderImpl?

This comment has been minimized.

Copy link
@ajs6f

ajs6f May 30, 2013

Contributor

interface TripleAppender or TripleSource or something like that. The fact that it currently relies on HTTP types is incidental and won't be the case forever, right?

This comment has been minimized.

Copy link
@barmintor

barmintor via email May 30, 2013

Contributor

This comment has been minimized.

Copy link
@barmintor

barmintor via email May 30, 2013

Contributor

This comment has been minimized.

Copy link
@cbeer

cbeer May 30, 2013

Author Contributor

We are passing the UriInfo around, which makes it kinda HTTPy.

This comment has been minimized.

Copy link
@ajs6f

ajs6f May 30, 2013

Contributor

Are we going to keep doing that, or rely on the URI->URL machinery in serialization?

This comment has been minimized.

Copy link
@cbeer

cbeer May 30, 2013

Author Contributor

@ajs6f This is in addition to the GraphSubjects.. I think we need to pass through the UriInfo in order to build paths to other JAX-RS resources. Is there a better way?

This comment has been minimized.

Copy link
@ajs6f

ajs6f May 30, 2013

Contributor

That's the right tool to use. I guess I'm wondering if we shouldn't be producing in-repo URIs here, and letting the serialization machinery translate all the internal URIs, not just subjects.

void addResponseInformationToDataset(final FedoraResource resource, final Model model, final UriInfo uriInfo, GraphSubjects graphSubjects) throws RepositoryException;
}
@@ -0,0 +1,47 @@
package org.fcrepo.api.rdf;

import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import org.fcrepo.FedoraResource;
import org.fcrepo.rdf.GraphSubjects;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.jcr.RepositoryException;
import javax.ws.rs.core.UriInfo;
import java.util.Collection;

import static org.slf4j.LoggerFactory.getLogger;

@Component
public class HttpComponentInjector implements ApplicationContextAware {

This comment has been minimized.

Copy link
@cbeer

cbeer May 29, 2013

Author Contributor

Suggestions for better names?

This comment has been minimized.

Copy link
@awoods

awoods May 30, 2013

This class also implements HttpComponentAppender, no?

This comment has been minimized.

Copy link
@cbeer

cbeer May 30, 2013

Author Contributor

It's true (except if it does, then you have to explicitly exclude it from the loop of "appenders"). Seemed more dangerous than helpful, but I'm open to re-adding it.

private static final Logger LOGGER = getLogger(HttpComponentInjector.class);
private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

public void addResponseInformationToDataset(FedoraResource resource, Dataset dataset, UriInfo uriInfo, GraphSubjects graphSubjects) throws RepositoryException {

final Model model = ModelFactory.createDefaultModel();
LOGGER.debug("Adding additional HTTP context triples to dataset");
for (HttpComponentAppender httpComponentAppender : getAppenders()) {
LOGGER.debug("Adding response information using {}", httpComponentAppender.getClass().toString());

httpComponentAppender.addResponseInformationToDataset(resource, model, uriInfo, graphSubjects);
}

dataset.addNamedModel("info:fedora/http-uris", model);
}

private Collection<HttpComponentAppender> getAppenders() {
return applicationContext.getBeansOfType(HttpComponentAppender.class).values();

}
}
Expand Up @@ -6,6 +6,7 @@

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

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
Expand All @@ -21,7 +22,7 @@ public class GraphStoreStreamingOutput implements StreamingOutput {
private static final Logger LOGGER =
getLogger(GraphStoreStreamingOutput.class);

private final Model model;
private final Dataset dataset;

private final String format;

Expand All @@ -33,14 +34,7 @@ public GraphStoreStreamingOutput(final GraphStore graphStore,

public GraphStoreStreamingOutput(final Dataset dataset,
final MediaType mediaType) {
this.model = dataset.getDefaultModel();
format =
contentTypeToLang(mediaType.toString()).getName().toUpperCase();
}

public GraphStoreStreamingOutput(final Model model,
final MediaType mediaType) {
this.model = model;
this.dataset = dataset;
format =
contentTypeToLang(mediaType.toString()).getName().toUpperCase();
}
Expand All @@ -49,7 +43,14 @@ public GraphStoreStreamingOutput(final Model model,
public void write(final OutputStream out) throws IOException,
WebApplicationException {
LOGGER.debug("Serializing graph model as {}", format);
model.write(out, format);
final Iterator<String> iterator = dataset.listNames();

while (iterator.hasNext()) {
final Model model = dataset.getNamedModel(iterator.next());
model.write(out, format);
}

dataset.getDefaultModel().write(out, format);

}

Expand Down
@@ -0,0 +1,58 @@
package org.fcrepo.api.rdf;

import com.google.common.collect.ImmutableBiMap;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.query.DatasetFactory;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import org.fcrepo.FedoraResource;
import org.fcrepo.rdf.GraphSubjects;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;

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

import java.util.Map;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class HttpComponentInjectorTest {

private HttpComponentInjector testObj;
private Dataset dataset;
private UriInfo mockUriInfo;
private GraphSubjects mockSubjects;
private ApplicationContext mockContext;

@Before
public void setUp() {
mockContext = mock(ApplicationContext.class);
testObj = new HttpComponentInjector();
testObj.setApplicationContext(mockContext);

dataset = DatasetFactory.create(ModelFactory.createDefaultModel());
mockUriInfo = mock(UriInfo.class);
mockSubjects = mock(GraphSubjects.class);


}

@Test
public void shouldAddTriplesFromRegisteredBeans() throws RepositoryException {
final FedoraResource mockResource = mock(FedoraResource.class);

HttpComponentAppender mockBean1 = mock(HttpComponentAppender.class);
HttpComponentAppender mockBean2 = mock(HttpComponentAppender.class);
Map<String, HttpComponentAppender> mockBeans = ImmutableBiMap.of("doesnt", mockBean1, "matter", mockBean2);
when(mockContext.getBeansOfType(HttpComponentAppender.class)).thenReturn(mockBeans);
testObj.addResponseInformationToDataset(mockResource, dataset, mockUriInfo, mockSubjects);
verify(mockBean1).addResponseInformationToDataset(eq(mockResource), any(Model.class), eq(mockUriInfo), eq(mockSubjects));
verify(mockBean2).addResponseInformationToDataset(eq(mockResource), any(Model.class), eq(mockUriInfo), eq(mockSubjects));
}
}
6 changes: 6 additions & 0 deletions fcrepo-jcr/pom.xml
Expand Up @@ -17,6 +17,12 @@
<artifactId>modeshape-jcr</artifactId>
</dependency>


<dependency>
<groupId>org.modeshape</groupId>
<artifactId>modeshape-common</artifactId>
</dependency>

<dependency>
<groupId>org.jboss.jbossts</groupId>
<artifactId>jbossjta</artifactId>
Expand Down
22 changes: 22 additions & 0 deletions fcrepo-rss/src/main/java/org/fcrepo/syndication/RssResources.java
@@ -0,0 +1,22 @@
package org.fcrepo.syndication;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import org.fcrepo.FedoraResource;
import org.fcrepo.api.rdf.HttpComponentAppender;
import org.fcrepo.rdf.GraphSubjects;

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

public class RssResources implements HttpComponentAppender {
@Override
public void addResponseInformationToDataset(FedoraResource resource, Model model, UriInfo uriInfo, GraphSubjects graphSubjects) throws RepositoryException {

final Resource s = graphSubjects.getGraphSubject(resource.getNode());

if (resource.getNode().getPrimaryNodeType().isNodeType("mode:root")) {
model.add(s, model.createProperty("http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#link-type-feed"), model.createResource(uriInfo.getBaseUriBuilder().path(RSSPublisher.class).build().toASCIIString()));
}
}
}
2 changes: 1 addition & 1 deletion fcrepo-webapp/src/main/resources/spring/rest.xml
Expand Up @@ -17,7 +17,7 @@
<bean class="org.fcrepo.identifiers.UUIDPidMinter"/>
<bean class="org.fcrepo.session.SessionFactory"/>

<context:component-scan base-package="org.modeshape.web.jcr.rest, org.fcrepo.api, org.fcrepo.serialization, org.fcrepo.responses, org.fcrepo.exceptionhandlers, org.fcrepo.audit"/>
<context:component-scan base-package="org.modeshape.web.jcr.rest, org.fcrepo.api, org.fcrepo.serialization, org.fcrepo.responses, org.fcrepo.exceptionhandlers, org.fcrepo.audit, org.fcrepo.url"/>

<util:map id="serializers" key-type="java.lang.String" map-class="java.util.HashMap"
value-type="org.fcrepo.serialization.FedoraObjectSerializer">
Expand Down
@@ -0,0 +1,22 @@
package org.fcrepo.webhooks;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import org.fcrepo.FedoraResource;
import org.fcrepo.api.rdf.HttpComponentAppender;
import org.fcrepo.rdf.GraphSubjects;

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

public class WebhooksResources implements HttpComponentAppender {
@Override
public void addResponseInformationToDataset(FedoraResource resource, Model model, UriInfo uriInfo, GraphSubjects graphSubjects) throws RepositoryException {

final Resource s = graphSubjects.getGraphSubject(resource.getNode());

if (resource.getNode().getPrimaryNodeType().isNodeType("mode:root")) {
model.add(s, model.createProperty("http://microformats.org/wiki/rel-subscription"), model.createResource(uriInfo.getBaseUriBuilder().path(FedoraWebhooks.class).build().toASCIIString()));
}
}
}

0 comments on commit 070d7c4

Please sign in to comment.