Commit 423b1cbb authored by acoburn's avatar acoburn
Browse files

Create a PCDM Object extension

parent 8f848bfe
......@@ -15,6 +15,7 @@ by making available a REST-based HTTP interface. The intention is that these ext
* `acrepo-exts-fits`: This service will return FITS information associated with a Fedora Binary, in XML format
* `acrepo-exts-image`: An image manipulation service
* `acrepo-exts-jsonld`: This module exposes an HTTP endpoint for creating compact JSON-LD documents from a Fedora repository using a pluggable context document
* `acrepo-exts-pcdm`: This constructs a complete PCDM object graph for Fedora resources
* `acrepo-exts-serialize-xml`: This service translates Fedora RDF documents into MODS/XML or DC/XML
* `acrepo-exts-template`: A module for converting Fedora resources into some other form, using a [mustache](https://mustache.github.io/) template.
......@@ -66,14 +67,18 @@ Each of these projects can be deployed in an OSGi container. For example using
command from its shell:
feature:repo-add mvn:edu.amherst.acdc/acrepo-karaf/LATEST/xml/features
feature:install acrepo-connector-broadcast
feature:install acrepo-connector-idiomatic
feature:install acrepo-connector-idiomatic-pgsql
feature:install acrepo-exts-fits
feature:install acrepo-exts-image
feature:install acrepo-exts-jsonld
feature:install acrepo-exts-pcdm
feature:install acrepo-exts-serialize-xml
feature:install acrepo-exts-template
feature:install acrepo-services-jsonld
feature:install acrepo-services-ldcache
feature:install acrepo-services-ldcache-file
......
Repository PCDM object extension
================================
This extension operates on `pcdm:Object` resources, building
an RDF graph of the complete object (following `pcdm:hasMember`,
`pcdm:hasRelatedObject` and `pcdm:hasFile` links). The complete
graph is returned in the requested serialization, using an `Accept` header.
Building
--------
To build this project use
mvn install
Deploying in OSGi
-----------------
This projects can be deployed in an OSGi container. For example using
[Apache Karaf](http://karaf.apache.org) version 4.x or better, you can run the following
command from its shell:
feature:repo-add mvn:edu.amherst.acdc/acrepo-karaf/LATEST/xml/features
feature:install acrepo-exts-pcdm
feature:install acrepo-services-pcdm
Configuration
-------------
The application can be configured by creating the following configuration
file `$KARAF_HOME/etc/edu.amherst.acdc.exts.pcdm.cfg`. The following values
are available for configuration:
The base url of the fedora repository
fcrepo.baseUrl=localhost:8080/fcrepo/rest
The port on which the service is made availalbe
rest.port=9107
The hostname for the service
rest.host=localhost
The REST prefix
rest.prefix=/pcdm
By editing this file, any currently running routes will be immediately redeployed
with the new values.
For more help see the [Apache Camel](http://camel.apache.org) documentation
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>repository-services</artifactId>
<groupId>edu.amherst.acdc</groupId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>acrepo-exts-pcdm</artifactId>
<packaging>bundle</packaging>
<name>PCDM Object builder</name>
<properties>
<osgi.export.packages>edu.amherst.acdc.exts.pcdm;version=${project.version}</osgi.export.packages>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-blueprint</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty9</artifactId>
</dependency>
<dependency>
<groupId>org.fcrepo.camel</groupId>
<artifactId>fcrepo-camel</artifactId>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-osgi</artifactId>
</dependency>
<dependency>
<groupId>edu.amherst.acdc</groupId>
<artifactId>acrepo-services-pcdm</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Testing & Camel Plugin -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-blueprint</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<!-- add configuration file to artifact set for OSGi deployment -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>src/main/cfg/edu.amherst.acdc.exts.pcdm.cfg</file>
<type>cfg</type>
<classifier>configuration</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
# HTTP Port
rest.host=localhost
rest.port=9107
rest.prefix=/pcdm
# Concurrency level (this MUST be > 1)
pcdm.concurrency=10
# Repository Base URL (it MUST start with http:// or https://)
fcrepo.baseUrl=http://localhost:8080/fcrepo/rest
/*
* Copyright 2016 Amherst College
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.amherst.acdc.exts.pcdm;
import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
import static edu.amherst.acdc.exts.pcdm.PcdmHeaders.PCDM_MODEL;
import static org.slf4j.LoggerFactory.getLogger;
import org.apache.camel.Exchange;
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.apache.jena.rdf.model.Model;
import org.slf4j.Logger;
/**
* Aggregate the CamelPcdmModel header values across exchanges
*
* @author acoburn
* @since 6/29/16
*/
class ModelAggregator implements AggregationStrategy {
private static final Logger LOGGER = getLogger(ModelAggregator.class);
@Override
public Exchange aggregate(final Exchange a, final Exchange b) {
if (a == null) {
return b;
}
final Model modelA = a.getIn().getHeader(PCDM_MODEL, Model.class);
final Model modelB = b.getIn().getHeader(PCDM_MODEL, Model.class);
if (modelA == null && modelB == null) {
a.getIn().setHeader(PCDM_MODEL, createDefaultModel());
} else if (modelA == null) {
a.getIn().setHeader(PCDM_MODEL, modelB);
} else if (modelB != null) {
modelA.add(modelB);
a.getIn().setHeader(PCDM_MODEL, modelA);
}
return a;
}
}
/*
* Copyright 2016 Amherst College
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.amherst.acdc.exts.pcdm;
/**
* Some header field definitions
*
* @author acoburn
*/
class PcdmHeaders {
public final static String PCDM_MODEL = "CamelPcdmModel";
public final static String PCDM_SUBJECT = "CamelPcdmSubject";
public final static String PCDM_MEMBERS = "CamelPcdmMembers";
public final static String PCDM_FILES = "CamelPcdmFiles";
public final static String PCDM_RELATED_OBJECTS = "CamelPcdmRelatedObjects";
private PcdmHeaders() {
// prevent instantiation
}
}
/*
* Copyright 2016 Amherst College
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.amherst.acdc.exts.pcdm;
import static org.apache.camel.Exchange.CONTENT_TYPE;
import static org.apache.camel.Exchange.HTTP_METHOD;
import static org.apache.camel.Exchange.HTTP_PATH;
import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.fcrepo.camel.FcrepoHeaders.FCREPO_IDENTIFIER;
import static org.fcrepo.camel.FcrepoHeaders.FCREPO_BASE_URL;
import static edu.amherst.acdc.exts.pcdm.PcdmHeaders.PCDM_SUBJECT;
import org.apache.camel.builder.RouteBuilder;
/**
* A content router for handling PCDM extension requests
*
* @author Aaron Coburn
*/
public class PcdmRouter extends RouteBuilder {
/**
* Configure the message route workflow.
*/
public void configure() throws Exception {
from("jetty:http://{{rest.host}}:{{rest.port}}{{rest.prefix}}?" +
"matchOnUriPrefix=true&sendServerVersion=false&httpMethodRestrict=GET,OPTIONS")
.routeId("PcdmRouter")
.choice()
.when(header(HTTP_METHOD).isEqualTo("GET"))
.log("Building PCDM Object ${headers[CamelHttpPath]}")
.setBody(header(HTTP_PATH))
.setHeader(FCREPO_BASE_URL).simple("{{fcrepo.baseUrl}}")
.to("seda:get")
.to("direct:write")
.when(header(HTTP_METHOD).isEqualTo("OPTIONS"))
.setHeader(CONTENT_TYPE).constant("text/turtle")
.setHeader("Allow").constant("GET,OPTIONS")
.to("language:simple:resource:classpath:options.ttl");
from("seda:get?concurrentConsumers={{pcdm.concurrency}}")
.routeId("PcdmGetChild")
.setHeader(FCREPO_IDENTIFIER, body())
.to("direct:getResource")
.filter(header(HTTP_RESPONSE_CODE).isEqualTo(200))
.log("Getting related resources for ${headers[CamelFcrepoIdentifier]}")
.to("direct:parse")
.setHeader(PCDM_SUBJECT).simple("${headers.CamelFcrepoBaseUrl}${headers.CamelFcrepoIdentifier}")
.to("direct:members")
.to("direct:files")
.to("direct:relatedObjects")
.process(new RelatedProcessor())
.split(body(), new ModelAggregator())
.to("seda:get");
from("direct:getResource")
.routeId("PcdmResource")
.removeHeader("breadcrumId")
.setHeader(FCREPO_BASE_URL).simple("{{fcrepo.baseUrl}}")
.to("fcrepo:{{fcrepo.baseUrl}}?throwExceptionOnFailure=false");
}
}
/*
* Copyright 2016 Amherst College
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.amherst.acdc.exts.pcdm;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toSet;
import static edu.amherst.acdc.exts.pcdm.PcdmHeaders.PCDM_FILES;
import static edu.amherst.acdc.exts.pcdm.PcdmHeaders.PCDM_MEMBERS;
import static edu.amherst.acdc.exts.pcdm.PcdmHeaders.PCDM_RELATED_OBJECTS;
import static org.fcrepo.camel.FcrepoHeaders.FCREPO_BASE_URL;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.slf4j.Logger;
/**
* Aggregate all of the pcdm:hasMember, pcdm:hasFile and pcdm:hasRelatedObject values
* into a single Collection in the body.
*
* @author acoburn
* @since 6/29/16
*/
class RelatedProcessor implements Processor {
private static final Logger LOGGER = getLogger(RelatedProcessor.class);
@Override
public void process(final Exchange exchange) throws Exception {
final Set<String> ids = new HashSet<>();
@SuppressWarnings("unchecked")
final List<String> members = exchange.getIn().getHeader(PCDM_MEMBERS, emptyList(), List.class);
@SuppressWarnings("unchecked")
final List<String> files = exchange.getIn().getHeader(PCDM_FILES, emptyList(), List.class);
@SuppressWarnings("unchecked")
final List<String> relatedObjects = exchange.getIn().getHeader(PCDM_RELATED_OBJECTS, emptyList(), List.class);
ids.addAll(members);
ids.addAll(files);
ids.addAll(relatedObjects);
exchange.getIn().setBody(ids.stream()
.map(id -> id.replace(exchange.getIn().getHeader(FCREPO_BASE_URL, "", String.class), ""))
.collect(toSet()));
}
}
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
<!-- OSGI blueprint property placeholder -->
<cm:property-placeholder persistent-id="edu.amherst.acdc.exts.jsonld" update-strategy="reload">
<cm:default-properties>
<cm:property name="rest.port" value="9107"/>
<cm:property name="rest.prefix" value="/pcdm"/>
<cm:property name="rest.host" value="localhost"/>
<cm:property name="pcdm.concurrency" value="10"/>
<cm:property name="fcrepo.baseUrl" value="http://localhost:8080/fcrepo/rest"/>
</cm:default-properties>
</cm:property-placeholder>
<reference id="pcdmService" interface="edu.amherst.acdc.services.pcdm.PcdmService" filter="(osgi.jndi.service.name=pcdm)" />
<camelContext id="AcrepoExtPcdm" xmlns="http://camel.apache.org/schema/blueprint">
<package>edu.amherst.acdc.exts.pcdm</package>
<route id="PcdmParser">
<from uri="direct:parse"/>
<setHeader headerName="CamelPcdmModel">
<method ref="pcdmService" method="parseInto(${header[CamelPcdmModel]}, ${body}, ${header[Content-Type]})"/>
</setHeader>
</route>
<route id="PcdmMembers">
<from uri="direct:members"/>
<setHeader headerName="CamelPcdmMembers">
<method ref="pcdmService" method="hasMember(${header[CamelPcdmModel]}, ${header[CamelPcdmSubject]})"/>
</setHeader>
</route>
<route id="PcdmFiles">
<from uri="direct:files"/>
<setHeader headerName="CamelPcdmFiles">
<method ref="pcdmService" method="hasFile(${header[CamelPcdmModel]}, ${header[CamelPcdmSubject]})"/>
</setHeader>
</route>
<route id="PcdmRelatedObject">
<from uri="direct:relatedObjects"/>
<setHeader headerName="CamelPcdmRelatedObjects">
<method ref="pcdmService" method="hasRelatedObject(${header[CamelPcdmModel]}, ${header[CamelPcdmSubject]})"/>
</setHeader>
</route>
<route id="PcdmWrite">
<from uri="direct:write"/>
<setBody>
<method ref="pcdmService" method="write(${header[CamelPcdmModel]}, ${header[Accept]})"/>
</setBody>
</route>
</camelContext>
</blueprint>
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix registry: <http://acdc.amherst.edu/extensions#> .
@prefix pcdm: <http://pcdm.org/models#> .
@prefix apix: <urn:apix#> .
<#Extension> a apix:Extension ;
rdfs:label "PCDM Object extension" ;
rdfs:comment "An extension that builds an entire PCDM object" ;
apix:exposesService registry:PcdmService ;
apix:exposesServiceAtURI "svc:pcdm" ;
apix:bindsTo pcdm:Object .
/*
* Copyright 2016 Amherst College
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.amherst.acdc.exts.pcdm;
import java.util.Dictionary;
import java.util.Map;
import edu.amherst.acdc.services.pcdm.PcdmService;
import edu.amherst.acdc.services.pcdm.PcdmServiceImpl;
import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;
import org.apache.camel.util.KeyValueHolder;
import org.junit.Test;
/**
* @author acoburn
* @since 9/21/15
*/
public class RouteTest extends CamelBlueprintTestSupport {
@Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
@Override
protected void addServicesOnStartup(final Map<String, KeyValueHolder<Object, Dictionary>> services) {
services.put(PcdmService.class.getName(), asService(new PcdmServiceImpl(), "name", "pcdm"));
}
@Test
public void testRoute() throws Exception {
// the route is timer based, so every 5th second a message is send
// we should then expect at least one message
getMockEndpoint("mock:result").expectedMinimumMessageCount(0);
// assert expectations
assertMockEndpointsSatisfied();
}
}
@prefix premis: <http://www.loc.gov/premis/rdf/v1#> .
@prefix pcdm: <http://pcdm.org/models#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix ebucore: <http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#> .
@prefix ldp: <http://www.w3.org/ns/ldp#> .
@prefix iana: <http://www.iana.org/assignments/relation/> .