Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add support for prefer headers along with unit and integration tests
  • Loading branch information
acoburn authored and Andrew Woods committed Jan 7, 2015
1 parent 94a936d commit 3ad67c8
Show file tree
Hide file tree
Showing 16 changed files with 604 additions and 47 deletions.
29 changes: 21 additions & 8 deletions README.md
Expand Up @@ -3,7 +3,7 @@ Fcrepo Component

The **fcrepo:** component provides access to an external
[Fedora4](http://fcrepo.org) Object
[API](https://wiki.duraspace.org/display/FF/RESTful+HTTP+API+-+Containers)
[API](https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API+-+Containers)
for use with [Apache Camel](https://camel.apache.org).

[![Build Status](https://travis-ci.org/fcrepo4-labs/fcrepo-camel.png?branch=master)](https://travis-ci.org/fcrepo4-labs/fcrepo-camel)
Expand All @@ -25,15 +25,16 @@ FcrepoEndpoint options
| `accept` | `null` | Set the `Accept` header for content negotiation |
| `metadata` | `true` | Whether GET requests should retrieve RDF descriptions of non-RDF content |
| `transform` | `null` | If set, this defines the transform used for the given object. This should be used in the context of GET or POST. For GET requests, the value should be the name of the transform (e.g. `default`). For POST requests, the value can simply be `true`. Using this causes the `Accept` header to be set as `application/json`. |
| `preferOmit` | `null` | If set, this populates the `Prefer:` HTTP header with omitted values. For single values, the standard [LDP values](http://www.w3.org/TR/ldp/#prefer-parameters) and the corresponding [Fcrepo extensions](https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API+-+Containers#RESTfulHTTPAPI-Containers-GETRetrievethecontentoftheresource) can be provided in short form (without the namespace). |
| `preferInclude` | `null` | If set, this populates the `Prefer:` HTTP header with included values. For single values, the standard [LDP values](http://www.w3.org/TR/ldp/#prefer-parameters) and the corresponding [Fcrepo extensions](https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API+-+Containers#RESTfulHTTPAPI-Containers-GETRetrievethecontentoftheresource) can be provided in short form (without the namespace). |
| `throwExceptionOnFailure` | `true` | Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server. This allows you to get all responses regardless of the HTTP status code. |


Examples
--------

A simple example for sending messages to an external Solr service:

XPathBuilder xpath = new XPathBuilder("/rdf:RDF/rdf:Description/rdf:type[@rdf:resource='http://fedora.info/definitions/v4/repository#Indexable']");
XPathBuilder xpath = new XPathBuilder("/rdf:RDF/rdf:Description/rdf:type[@rdf:resource='http://fedora.info/definitions/v4/indexing#indexable']");
xpath.namespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");

from("activemq:topic:fedora")
Expand All @@ -48,7 +49,7 @@ Or, using the Spring DSL:
<from uri="activemq:topic:fedora"/>
<to uri="fcrepo:localhost:8080/rest"/>
<filter>
<xpath>/rdf:RDF/rdf:Description/rdf:type[@rdf:resource='http://fedora.info/definitions/v4/repository#Indexable']</xpath>
<xpath>/rdf:RDF/rdf:Description/rdf:type[@rdf:resource='http://fedora.info/definitions/v4/indexing#indexable']</xpath>
<to uri="fcrepo:localhost:8080/rest?transform=mytransform"/>
<to uri="http4:solr-host:8080/solr/core/update"/>
</filter>
Expand All @@ -73,11 +74,23 @@ Message headers
| `Exchange.HTTP_METHOD` | `String` | The HTTP method to use |
| `Exchange.CONTENT_TYPE` | `String` | The ContentType of the resource. This sets the `Content-Type` header, but this value can be overridden directly on the endpoint. |
| `Exchange.ACCEPT_CONTENT_TYPE` | `String` | This sets the `Accept` header, but this value can be overridden directly on the endpoint. |
| `FCREPO_IDENTIFIER` | `String` | The resource path, appended to the endpoint uri. |
| `FCREPO_BASE_URL` | `String` | The base url used for accessing Fedora. |
| `FCREPO_TRANSFORM` | `String` | The named `fcr:transform` method to use. This value overrides any value set explicitly on the endpoint. |
| `FcrepoHeaders.FCREPO_PREFER` | `String` | This sets the `Prefer` header on a repository request. The full header value should be declared here, and it will override any value set directly on an endpoint. |
| `FcrepoHeaders.FCREPO_IDENTIFIER` | `String` | The resource path, appended to the endpoint uri. |
| `FcrepoHeaders.FCREPO_BASE_URL` | `String` | The base url used for accessing Fedora. |
| `FcrepoHeaders.FCREPO_TRANSFORM` | `String` | The named `fcr:transform` method to use. This value overrides any value set explicitly on the endpoint. |

The `fcrepo` component will also accept message headers produced directly by fedora, particularly the `org.fcrepo.jms.identifier` header. It will use that header only when `CamelFcrepoIdentifier` is not defined.

If these headers are used with the Spring DSL or with the Simple language, the header values can be used directly with the following values:

| Name | Value |
| ------- | ----- |
| `FcrepoHeaders.FCREPO_BASE_URL` | `CamelFcrepoBaseUrl` |
| `FcrepoHeaders.FCREPO_IDENTIFIER` | `CamelFcrepoIdentifier` |
| `FcrepoHeaders.FCREPO_TRANSFORM` | `CamelFcrepoTransform` |
| `FcrepoHeaders.FCREPO_PREFER` | `CamelFcrepoPrefer` |

The `fcrepo` component will also accept message headers produced directly by fedora, particularly the `org.fcrepo.jms.identifier` header. It will use that header only when `FEDORA_IDENTIFIER` is not defined.
These headers can be removed as a group like this in the Java DSL: `removeHeaders("CamelFcrepo*")`

Message body
------------
Expand Down
16 changes: 15 additions & 1 deletion pom.xml
Expand Up @@ -42,6 +42,7 @@
<clerezza.rdf.jena.serializer.version>0.11</clerezza.rdf.jena.serializer.version>
<clerezza.rdf.jena.parser.version>0.12</clerezza.rdf.jena.parser.version>
<clerezza.rdf.core.version>0.14</clerezza.rdf.core.version>
<guava.version>18.0</guava.version>
<!-- plugins -->
<checkstyle.plugin.version>2.12.1</checkstyle.plugin.version>
<!-- values -->
Expand Down Expand Up @@ -141,6 +142,12 @@
<version>${commons.io.version}</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>

<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
Expand Down Expand Up @@ -615,16 +622,23 @@
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>
org.fcrepo.camel;version=${project.version},
org.fcrepo.camel.processor;version=${project.version}
</Export-Package>
<Private-Package>
com.google.common.*,
com.google.thirdparty.publicsuffix.*,
com.hp.hpl.jena.*,
com.ibm.icu.*,
javax.ws.rs.*,
org.apache.clerezza.rdf.*,
org.apache.clerezza.utils.*,
org.apache.commons.codec.*,
org.apache.commons.lang3.*,
org.apache.jena.*,
org.osgi.service.component,
org.wymiwyg.commons.util.*,
</Export-Package>
</Private-Package>
</instructions>
</configuration>
</plugin>
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/fcrepo/camel/FcrepoClient.java
Expand Up @@ -235,7 +235,7 @@ public FcrepoResponse delete(final URI url)
* @param url the URL of the resource to fetch
* @param accept the requested MIMEType of the resource to be retrieved
*/
public FcrepoResponse get(final URI url, final String accept)
public FcrepoResponse get(final URI url, final String accept, final String prefer)
throws IOException, HttpOperationFailedException {

final HttpGet request = new HttpGet(url);
Expand All @@ -244,6 +244,10 @@ public FcrepoResponse get(final URI url, final String accept)
request.setHeader("Accept", accept);
}

if (prefer != null) {
request.setHeader("Prefer", prefer);
}

final HttpResponse response = httpclient.execute(request);
final int status = response.getStatusLine().getStatusCode();
final String contentType = getContentTypeHeader(response);
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/org/fcrepo/camel/FcrepoEndpoint.java
Expand Up @@ -63,6 +63,12 @@ public class FcrepoEndpoint extends DefaultEndpoint {
@UriParam
private Boolean throwExceptionOnFailure = true;

@UriParam
private String preferInclude = null;

@UriParam
private String preferOmit = null;

/**
* Create a FcrepoEndpoint with a uri, path and component
* @param uri the endpoint uri (without path values)
Expand Down Expand Up @@ -265,4 +271,36 @@ public void setTombstone(final Boolean tombstone) {
public Boolean getTombstone() {
return tombstone;
}

/**
* preferInclude setter
*/
@ManagedAttribute(description = "Whether to include a Prefer: return=representation; include=\"URI\" header")
public void setPreferInclude(final String include) {
this.preferInclude = include;
}

/**
* preferOmit getter
*/
@ManagedAttribute(description = "Whether to include a Prefer: return=representation; include=\"URI\" header")
public String getPreferInclude() {
return preferInclude;
}

/**
* preferOmit setter
*/
@ManagedAttribute(description = "Whether to include a Prefer: return=representation; omit=\"URI\" header")
public void setPreferOmit(final String omit) {
this.preferOmit = omit;
}

/**
* preferOmit getter
*/
@ManagedAttribute(description = "Whether to include a Prefer: return=representation; omit=\"URI\" header")
public String getPreferOmit() {
return preferOmit;
}
}
8 changes: 5 additions & 3 deletions src/main/java/org/fcrepo/camel/FcrepoHeaders.java
Expand Up @@ -20,11 +20,13 @@
*/
public final class FcrepoHeaders {

public static final String FCREPO_BASE_URL = "FCREPO_BASE_URL";
public static final String FCREPO_BASE_URL = "CamelFcrepoBaseUrl";

public static final String FCREPO_IDENTIFIER = "FCREPO_IDENTIFIER";
public static final String FCREPO_IDENTIFIER = "CamelFcrepoIdentifier";

public static final String FCREPO_TRANSFORM = "FCREPO_TRANSFORM";
public static final String FCREPO_TRANSFORM = "CamelFcrepoTransform";

public static final String FCREPO_PREFER = "CamelFcrepoPrefer";

private FcrepoHeaders() {
// prevent instantiation
Expand Down
72 changes: 63 additions & 9 deletions src/main/java/org/fcrepo/camel/FcrepoProducer.java
Expand Up @@ -77,6 +77,7 @@ public void process(final Exchange exchange) throws HttpOperationFailedException
final String contentType = getContentType(exchange);
final String accept = getAccept(exchange);
final String url = getUrl(exchange);
final String prefer = getPrefer(exchange);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Fcrepo Request [{}] with method [{}]", url, method);
Expand Down Expand Up @@ -107,17 +108,17 @@ public void process(final Exchange exchange) throws HttpOperationFailedException
break;
case GET:
default:
response = client.get(endpoint.getMetadata() ? getMetadataUri(url) : URI.create(url), accept);
response = client.get(endpoint.getMetadata() ? getMetadataUri(url) : URI.create(url), accept, prefer);
exchange.getIn().setBody(extractResponseBodyAsStream(response.getBody(), exchange));
}
exchange.getIn().setHeader(Exchange.CONTENT_TYPE, response.getContentType());
exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, response.getStatusCode());
}

/**
*
* Retrieve the resource location from a HEAD request.
*/
protected URI getMetadataUri(final String url)
private URI getMetadataUri(final String url)
throws HttpOperationFailedException, IOException {
final FcrepoResponse headResponse = client.head(URI.create(url));
if (headResponse.getLocation() != null) {
Expand All @@ -134,7 +135,7 @@ protected URI getMetadataUri(final String url)
*
* @param exchange the incoming message exchange
*/
protected HttpMethods getMethod(final Exchange exchange) {
private HttpMethods getMethod(final Exchange exchange) {
final HttpMethods method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, HttpMethods.class);
if (method == null) {
return HttpMethods.GET;
Expand All @@ -149,7 +150,7 @@ protected HttpMethods getMethod(final Exchange exchange) {
*
* @param exchange the incoming message exchange
*/
protected String getContentType(final Exchange exchange) {
private String getContentType(final Exchange exchange) {
final String contentTypeString = ExchangeHelper.getContentType(exchange);
if (!isBlank(endpoint.getContentType())) {
return endpoint.getContentType();
Expand All @@ -168,7 +169,7 @@ protected String getContentType(final Exchange exchange) {
*
* @param exchange the incoming message exchange
*/
protected String getAccept(final Exchange exchange) {
private String getAccept(final Exchange exchange) {
final Message in = exchange.getIn();
final String fcrepoTransform = in.getHeader(FcrepoHeaders.FCREPO_TRANSFORM, String.class);

Expand All @@ -192,21 +193,23 @@ protected String getAccept(final Exchange exchange) {
*
* @param exchange the incoming message exchange
*/
protected String getUrl(final Exchange exchange) {
private String getUrl(final Exchange exchange) {
final Message in = exchange.getIn();
final HttpMethods method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, HttpMethods.class);
final HttpMethods method = getMethod(exchange);
final URI baseUri = URI.create(endpoint.getBaseUrl());
final String fcrepoTransform = in.getHeader(FcrepoHeaders.FCREPO_TRANSFORM, String.class);
final StringBuilder url = new StringBuilder("http://" + baseUri);

if (!isBlank(in.getHeader(FcrepoHeaders.FCREPO_IDENTIFIER, String.class))) {
url.append(in.getHeader(FcrepoHeaders.FCREPO_IDENTIFIER, String.class));
} else if (!isBlank(in.getHeader(JmsHeaders.IDENTIFIER, String.class))) {
url.append(in.getHeader(JmsHeaders.IDENTIFIER, String.class));
}

if (!isBlank(endpoint.getTransform()) || !isBlank(fcrepoTransform)) {
if (method == HttpMethods.POST) {
url.append("/fcr:transform");
} else if (method == null || method == HttpMethods.GET) {
} else if (method == HttpMethods.GET) {
if (!isBlank(fcrepoTransform)) {
url.append("/fcr:transform/" + fcrepoTransform);
} else {
Expand All @@ -216,9 +219,60 @@ protected String getUrl(final Exchange exchange) {
} else if (method == HttpMethods.DELETE && endpoint.getTombstone()) {
url.append("/fcr:tombstone");
}

return url.toString();
}

/**
* Given an exchange, extract the Prefer headers, if any.
*
* @param exchange the incoming message exchange
*/
private String getPrefer(final Exchange exchange) {
final Message in = exchange.getIn();

if (getMethod(exchange) == HttpMethods.GET) {
if (!isBlank(in.getHeader(FcrepoHeaders.FCREPO_PREFER, String.class))) {
return in.getHeader(FcrepoHeaders.FCREPO_PREFER, String.class);
} else {
return buildPreferHeader(endpoint.getPreferInclude(), endpoint.getPreferOmit());
}
} else {
return null;
}
}

/**
* Build the prefer header from include and/or omit endpoint values
*/
private String buildPreferHeader(final String include, final String omit) {
if (isBlank(include) && isBlank(omit)) {
return null;
} else {
String prefer = "return=representation;";
if (!isBlank(include)) {
prefer += " include=\"" + addPreferNamespace(include) + "\";";
}
if (!isBlank(omit)) {
prefer += " omit=\"" + addPreferNamespace(omit) + "\";";
}
return prefer;
}
}

/**
* Add the appropriate namespace to the prefer header in case the
* short form was supplied.
*/
private String addPreferNamespace(final String property) {
final String prefer = RdfNamespaces.PREFER_PROPERTIES.get(property);
if (!isBlank(prefer)) {
return prefer;
} else {
return property;
}
}

private static InputStream extractResponseBodyAsStream(final InputStream is, final Exchange exchange)
throws IOException {
// As httpclient is using a AutoCloseInputStream, it will be closed when the connection is closed
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/fcrepo/camel/RdfNamespaces.java
Expand Up @@ -16,6 +16,10 @@

package org.fcrepo.camel;

import com.google.common.collect.ImmutableMap;

import java.util.Map;

/**
* @author acoburn
*/
Expand All @@ -29,6 +33,14 @@ public final class RdfNamespaces {

public static final String LDP = "http://www.w3.org/ns/ldp#";

public static final Map<String, String> PREFER_PROPERTIES = ImmutableMap.<String, String>builder()
.put("PreferContainment", LDP + "PreferContainment")
.put("PreferMembership", LDP + "PreferMembership")
.put("PreferMinimalContainer", LDP + "PreferMinimalContainer")
.put("ServerManaged", REPOSITORY + "ServerManaged")
.put("EmbedResources", REPOSITORY + "EmbedResources")
.put("InboundReferences", REPOSITORY + "InboundReferences").build();

private RdfNamespaces() {
// Prevent instantiation
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/java/org/fcrepo/camel/FcrepoClientAuthTest.java
Expand Up @@ -70,7 +70,7 @@ public void testAuthNoHost() throws IOException, HttpOperationFailedException {
entity.setContentType(RDF_XML);
doSetupMockRequest(RDF_XML, entity, status);

final FcrepoResponse response = testClient.get(uri, RDF_XML);
final FcrepoResponse response = testClient.get(uri, RDF_XML, null);

assertEquals(response.getUrl(), uri);
assertEquals(response.getStatusCode(), status);
Expand All @@ -90,7 +90,7 @@ public void testAuthWithHost() throws IOException, HttpOperationFailedException
entity.setContentType(RDF_XML);
doSetupMockRequest(RDF_XML, entity, status);

final FcrepoResponse response = testClient.get(uri, RDF_XML);
final FcrepoResponse response = testClient.get(uri, RDF_XML, null);

assertEquals(response.getUrl(), uri);
assertEquals(response.getStatusCode(), status);
Expand All @@ -110,7 +110,7 @@ public void testAuthNoPassword() throws IOException, HttpOperationFailedExceptio
entity.setContentType(RDF_XML);
doSetupMockRequest(RDF_XML, entity, status);

final FcrepoResponse response = testClient.get(uri, RDF_XML);
final FcrepoResponse response = testClient.get(uri, RDF_XML, null);

assertEquals(response.getUrl(), uri);
assertEquals(response.getStatusCode(), status);
Expand Down

0 comments on commit 3ad67c8

Please sign in to comment.