Commit 2736421a authored by acoburn's avatar acoburn
Browse files

working implementation of id-mapping service

parent 34d2a63e
......@@ -3,6 +3,16 @@ Amherst College ID Mapping Service
This service implements a way to map external IDs to internal fedora URIs.
Database Structure
------------------
The backing database is assumed to be accessed via SQL. It follows a very simple
format, using a single table (`uris`) with two columns (`public` and `fedora`):
CREATE TABLE uris (
public VARCHAR(1024) CONSTRAINT publicid PRIMARY KEY,
fedora VARCHAR(1024) CONSTRAINT fedoraid NOT NULL);
Building
--------
......
......@@ -15,3 +15,8 @@ id.property=dc:identifier
# The full namespace of the above property
id.namespace=http://purl.org/dc/elements/1.1/
# An external identifier may have a standard prefix, as it is stored
# in fedora. By setting this value, that prefix will be removed from
# the id-mapping layer (e.g. http://example.org/)
id.prefix=
......@@ -14,9 +14,12 @@
package edu.amherst.acdc.idiomatic;
import static org.apache.camel.builder.PredicateBuilder.not;
import static edu.amherst.acdc.idiomatic.IdiomaticHeaders.FEDORA;
import static edu.amherst.acdc.idiomatic.IdiomaticHeaders.ID;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.Processor;
import org.apache.camel.PropertyInject;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.builder.RouteBuilder;
......@@ -29,10 +32,6 @@ import org.apache.camel.builder.xml.Namespaces;
*/
public class EventRouter extends RouteBuilder {
private static final String CQL_INSERT = "insert into uris(fedora, public) values (?, ?)";
private static final String CQL_GET = "";
private static final String CQL_DELETE = "";
@PropertyInject(value = "rest.port", defaultValue = "9081")
private String port;
......@@ -41,9 +40,11 @@ public class EventRouter extends RouteBuilder {
*/
public void configure() throws Exception {
final String idPrefix;
final Namespaces ns = new Namespaces("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
try {
idPrefix = getContext().resolvePropertyPlaceholders("{{id.prefix}}");
final String property = getContext().resolvePropertyPlaceholders("{{id.property}}");
final String namespace = getContext().resolvePropertyPlaceholders("{{id.namespace}}");
final String prefix = property.substring(0, property.indexOf(":"));
......@@ -69,44 +70,55 @@ public class EventRouter extends RouteBuilder {
from("direct:event")
.routeId("IdMappingEventRouter")
.log(LoggingLevel.INFO, "edu.amherst.acdc.idiomatic",
"IdMapping Event: ${headers[org.fcrepo.jms.identifier]}")
.log(LoggingLevel.INFO, "IdMapping Event: ${headers[org.fcrepo.jms.identifier]}")
.to("fcrepo:{{fcrepo.baseUrl}}?preferOmit=PreferContainment")
.split().xtokenize("//{{id.property}}", 'i', ns)
.transform().xpath("/{{id.property}}/@rdf:resource | /{{id.property}}/text()", String.class, ns)
.process(new IdProcessor())
.setHeader(ID).xpath("/{{id.property}}/@rdf:resource | /{{id.property}}/text()", String.class, ns)
.process(new Processor() {
public void process(final Exchange ex) throws Exception {
ex.getIn().setHeader(ID, ex.getIn().getHeader(ID, String.class).replaceAll("^" + idPrefix, ""));
}})
.transform().simple("${header[org.fcrepo.jms.identifier]}")
.to("direct:update");
/**
* REST routing
*/
rest("{{rest.prefix}}")
.get("/{id}").to("direct:get")
.get("/{" + ID + "}").to("direct:get")
.post("/").to("direct:minter")
.put("/{id}").to("direct:update")
.delete("/{id}").to("direct:delete");
.put("/{" + ID + "}").to("direct:update")
.delete("/{" + ID + "}").to("direct:delete");
/**
* Handle CRUD operations
*/
from("direct:update")
.routeId("IdMappingUpdateRouter")
.to("sql:UPDATE uris SET fedora=:#fedora WHERE public=:#public")
.setHeader(Exchange.HTTP_RESPONSE_CODE).constant(204)
.filter(simple("${body} == 0"))
.to("sql:INSERT INTO uris (fedora, public) VALUES (:#fedora, :#public)");
.setHeader(FEDORA).simple("${body}")
.setHeader(Exchange.HTTP_RESPONSE_CODE).constant(400)
.filter(header(FEDORA))
.log(LoggingLevel.INFO, "Updating ${headers[" + ID + "]} with ${headers[" + FEDORA + "]}")
.to("sql:UPDATE uris SET fedora=:#" + FEDORA + " WHERE public=:#" + ID)
.setHeader(Exchange.HTTP_RESPONSE_CODE).constant(204)
.choice()
.when(header("CamelSqlUpdateCount").isEqualTo("0"))
.to("sql:INSERT INTO uris (fedora, public) VALUES (:#" + FEDORA + ", :#" + ID + ")").end()
.removeHeader(ID)
.removeHeader(FEDORA);
from("direct:get")
.routeId("IdMappingFetchRouter")
.to("sql:SELECT fedora FROM uris WHERE public=:#id?outputType=SelectOne")
.removeHeader("id")
.to("sql:SELECT fedora FROM uris WHERE public=:#" + ID + "?outputType=SelectOne")
.removeHeader(ID)
.filter(not(header("CamelSqlRowCount").isEqualTo(1)))
.setHeader(Exchange.HTTP_RESPONSE_CODE).constant(404);
from("direct:delete")
.routeId("IdMappingDeleteRouter")
.to("sql:DELETE FROM uris WHERE public=:#id")
.removeHeader("id")
.log(LoggingLevel.INFO, "Deleting ${headers[" + ID + "]}")
.to("sql:DELETE FROM uris WHERE public=:#" + ID)
.removeHeader(ID)
.setHeader(Exchange.HTTP_RESPONSE_CODE).constant(204);
}
......
/**
* Copyright 2015 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
......@@ -15,35 +13,18 @@
*/
package edu.amherst.acdc.idiomatic;
import static org.fcrepo.camel.JmsHeaders.IDENTIFIER;
import java.util.Arrays;
import java.util.List;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
/**
* @author Aaron Coburn
* Common Headers for this set of routes
*
* @author acoburn
*/
public final class IdiomaticHeaders {
public class IdProcessor implements Processor {
public static final String ID = "CamelIdiomaticId";
/**
* Define how a message should be processed.
*
* @param exchange the current camel message exchange */
public void process(final Exchange exchange) throws Exception {
final Message in = exchange.getIn();
final String id = in.getBody(String.class);
public static final String FEDORA = "CamelIdiomaticFedora";
// update exchange
final List<String> data = Arrays.asList(in.getHeader(IDENTIFIER, String.class), id);
if (data.size() == 2) {
in.setBody(data);
} else {
in.setBody(null);
}
private IdiomaticHeaders() {
// prevent instantiation
}
}
......@@ -17,6 +17,7 @@
<cm:property name="rest.port" value="9080"/>
<cm:property name="id.property" value="dc:identifier"/>
<cm:property name="id.namespace" value="http://purl.org/dc/elements/1.1/"/>
<cm:property name="id.prefix" value=""/>
<cm:property name="fcrepo.baseUrl" value="localhost:8080/fcrepo/rest"/>
</cm:default-properties>
</cm:property-placeholder>
......@@ -38,7 +39,7 @@
<package>edu.amherst.acdc.idiomatic</package>
<restConfiguration bindingMode="auto" component="restlet" port="{{rest.port}}"/>
<restConfiguration component="restlet" port="{{rest.port}}"/>
<route id="MinterRoute">
<description>Create a freshly minted ID</description>
......
......@@ -13,6 +13,7 @@
*/
package edu.amherst.acdc.idiomatic;
import static edu.amherst.acdc.idiomatic.IdiomaticHeaders.ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
......@@ -71,6 +72,7 @@ public class RouteTest extends CamelBlueprintTestSupport {
final Properties props = new Properties();
props.put("input.stream", "seda:foo");
props.put("rest.port", "9999");
props.put("id.prefix", "http://example.org/object/");
return props;
}
......@@ -125,6 +127,7 @@ public class RouteTest extends CamelBlueprintTestSupport {
@Override
public void configure() throws Exception {
mockEndpointsAndSkip("fcrepo*");
mockEndpoints("direct:update");
}
});
......@@ -140,6 +143,8 @@ public class RouteTest extends CamelBlueprintTestSupport {
context.start();
resultEndpoint.expectedMessageCount(2);
getMockEndpoint("mock:direct:update").expectedBodiesReceived("/foo/bar", "/foo/bar");
getMockEndpoint("mock:direct:update").expectedHeaderValuesReceivedInAnyOrder(ID, "1", "2");
final Map<String, Object> headers = new HashMap<>();
headers.put(JmsHeaders.IDENTIFIER, "/foo/bar");
template.sendBodyAndHeaders(
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment