Commit 83bbf55d authored by bseeger's avatar bseeger
Browse files

Merge branch 'remove-xml-stuff' into 'master'

Remove acrepo-exts-serialize-xml service

Closes #33

See merge request !108
parents 5381085f 3f6ce876
......@@ -18,7 +18,6 @@ by making available a REST-based HTTP interface. The intention is that these ext
* [`acrepo-exts-ldpath`](acrepo-exts-ldpath): This module extends `fcrepo-ldpath` to support additional Linked Data endpoints (e.g. Getty)
* [`acrepo-exts-ore`](acrepo-exts-ore): This constructs a complete ORE Aggregation graph for LDP resources
* [`acrepo-exts-pcdm`](acrepo-exts-pcdm): This constructs a complete PCDM object graph for LDP resources
* [`acrepo-exts-serialize-xml`](acrepo-exts-serialize-xml): This service translates LDP-RS (RDF) documents into MODS/XML or DC/XML
* [`acrepo-exts-template`](acrepo-exts-template): A module for converting LDP resources into some other form, using a [mustache](https://mustache.github.io/) template.
Services
......@@ -75,7 +74,6 @@ command from its shell:
feature:install acrepo-exts-ldpath
feature:install acrepo-exts-ore
feature:install acrepo-exts-pcdm
feature:install acrepo-exts-serialize-xml
feature:install acrepo-exts-template
feature:install acrepo-services-entailment
......
Amherst College XML-based metadata serialization extension
=========================================================
This service implements a translation service from LDP RDF-based metadata
to either a DC/XML or MODS/XML serialization. This translation relies on
pluggable XSLT 2.0 documents.
The service becomes available over HTTP on the configured port. For example,
in order to retrieve a MODS version of the resource `http://localhost:8080/fcrepo/rest/a/b/c`:
curl localhost:9104/xml?format=mods&context=http://localhost:8080/fcrepo/rest/a/b/c
And the DC version:
curl localhost:9104/xml?format=dc&context=http://localhost:8080/fcrepo/rest/a/b/c
Building
--------
To build this project use
gradle install
Deploying in OSGi
-----------------
This project can be deployed in an OSGi container. For example using
[Apache Karaf](http://karaf.apache.org) version 4.x and above, 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-serialize-xml
Configuration
-------------
The application can be configured by creating the following configuration
file `$KARAF_HOME/etc/edu.amherst.acdc.exts.serialize.xml.cfg`. The following values
are available for configuration:
The location of the XSLT document for MODS. This can be a file path (using the `file:` prefix)
or an external URL (e.g. using a `http:` scheme). Without a prefix, the XSL file will
be loaded from the classpath.
mods.xslt=edu/amherst/acdc/xml/metadata/rdf2mods.xsl
The location of the XSLT document for DC. This can be a file path (using the `file:` prefix)
or an external URL (e.g. using a `http:` scheme). Without a prefix, the XSL file will
be loaded from the classpath.
dc.xslt=edu/amherst/acdc/xml/metadata/rdf2dc.xsl
The prefix of the service
rest.prefix=/xml
The port on which the service is available
rest.port=9104
The hostname for the service
rest.host=localhost
The fedora baseUrl value
fcrepo.baseUrl=localhost:8080/fcrepo/rest
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
apply plugin: 'osgi'
description = 'RDF to XML (MODS and DC) serialization service'
ext {
moduleName = 'edu.amherst.acdc.exts.serialize.xml'
}
dependencies {
api("org.apache.camel:camel-core:${camelVersion}")
implementation("org.apache.camel:camel-http4:${camelVersion}")
implementation("org.apache.camel:camel-jetty9:${camelVersion}")
implementation("org.apache.camel:camel-blueprint:${camelVersion}")
implementation("org.apache.camel:camel-saxon:${camelVersion}")
implementation("org.slf4j:slf4j-api:${slf4jVersion}")
testImplementation("commons-io:commons-io:${commonsIoVersion}")
testImplementation("junit:junit:${junitVersion}")
testImplementation("org.apache.camel:camel-test-blueprint:${camelVersion}")
testRuntimeOnly("ch.qos.logback:logback-classic:${logbackVersion}")
testRuntimeOnly("javax.activation:javax.activation-api:${activationApiVersion}")
testRuntimeOnly("javax.xml.bind:jaxb-api:${jaxbVersion}")
}
jar {
manifest {
description project.description
docURL project.docURL
vendor project.vendor
license project.license
instruction 'Automatic-Module-Name', moduleName
instruction 'Import-Package', "org.apache.camel,${defaultOsgiImports}"
instruction 'Export-Package', "${moduleName};version=${projectOsgiVersion}"
}
}
publishing.publications {
maven(MavenPublication) {
artifact ('build/cfg/main/edu.amherst.acdc.exts.serialize.xml.cfg') {
classifier 'configuration'
extension 'cfg'
}
}
}
# The port on which the service is made available
rest.port=9104
rest.host=localhost
rest.prefix=/xml
# The fedora baseUrl
fcrepo.baseUrl=http://localhost:8080/fcrepo/rest
fcrepo.authUsername=
fcrepo.authPassword=
# The location of the XSLT to convert RDF/XML to MODS/XML and DC/XML
mods.xslt=edu/amherst/acdc/exts/serialize/xml/rdf2mods.xsl
dc.xslt=edu/amherst/acdc/exts/serialize/xml/rdf2dc.xsl
# API-X configuration
extension.load=false
extension.load.uri=http://apix/services//apix:load
extension.load.maximumRedeliveries=60
/*
* Copyright 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.serialize.xml;
import static org.apache.camel.Exchange.CONTENT_TYPE;
import static org.apache.camel.Exchange.HTTP_METHOD;
import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.apache.camel.Exchange.HTTP_URI;
import static org.apache.camel.builder.PredicateBuilder.and;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
/**
* @author Aaron Coburn
*/
public class XmlSerializationRouter extends RouteBuilder {
private static final String FCREPO_BASE_URL = "CamelFcrepoBaseUrl";
private static final String FCREPO_URI = "CamelFcrepoUri";
private static final String HTTP_QUERY_CONTEXT = "context";
private static final String FORMAT = "format";
/**
* Configure the message route workflow.
*/
public void configure() throws Exception {
from("jetty:http://{{rest.host}}:{{rest.port}}{{rest.prefix}}?" +
"httpMethodRestrict=GET,OPTIONS&optionsEnabled=true&sendServerVersion=false")
.routeId("XmlAccept")
.process(e -> e.getIn().setHeader(FCREPO_URI,
e.getIn().getHeader(HTTP_QUERY_CONTEXT,
e.getIn().getHeader("Apix-Ldp-Resource"))))
.setHeader(FCREPO_BASE_URL).simple("{{fcrepo.baseUrl}}")
.choice()
.when(header(HTTP_METHOD).isEqualTo("OPTIONS"))
.to("direct:options")
.when(and(header(FORMAT).isEqualTo("dc"), header(FCREPO_URI).startsWith(header(FCREPO_BASE_URL))))
.to("direct:dc")
.when(header(FORMAT).isEqualTo("mods"))
.to("direct:mods")
.otherwise()
.to("direct:choice");
from("direct:dc")
.routeId("XmlDcXslt")
.to("direct:getResource")
.filter(header(HTTP_RESPONSE_CODE).isEqualTo(200))
.setHeader(CONTENT_TYPE).constant("application/xml")
.convertBodyTo(org.w3c.dom.Document.class)
.log(LoggingLevel.INFO, "Converting resource to DC/XML: ${headers[CamelFcrepoUri]}")
.to("xslt:{{dc.xslt}}?saxon=true");
from("direct:mods")
.routeId("XmlModsXslt")
.to("direct:getResource")
.filter(header(HTTP_RESPONSE_CODE).isEqualTo(200))
.setHeader(CONTENT_TYPE).constant("application/xml")
.convertBodyTo(org.w3c.dom.Document.class)
.log(LoggingLevel.INFO, "Converting resource to MODS/XML: ${headers[CamelFcrepoUri]}")
.to("xslt:{{mods.xslt}}?saxon=true");
from("direct:options")
.routeId("XmlOptions")
.setHeader(CONTENT_TYPE).constant("text/turtle")
.setHeader("Allow").constant("GET,OPTIONS")
.to("language:simple:resource:classpath:options.ttl");
from("direct:choice")
.routeId("ChooseFormat")
.setHeader(CONTENT_TYPE).constant("text/html")
.setHeader("Allow").constant("GET,OPTIONS")
.to("language:simple:resource:classpath:index.html");
from("direct:getResource")
.routeId("XmlTransformationCommon")
.removeHeader("CamelHttp*")
.removeHeader("breadcrumbId")
.removeHeader("User-Agent")
.setHeader(HTTP_URI).header(FCREPO_URI)
.setHeader("Accept").constant("application/rdf+xml")
.to("http4:localhost?throwExceptionOnFailure=false&"
+ "username={{fcrepo.authUsername}}&password={{fcrepo.authPassword}}");
}
}
<?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.serialize.xml" update-strategy="reload">
<cm:default-properties>
<cm:property name="rest.port" value="9104"/>
<cm:property name="rest.host" value="localhost"/>
<cm:property name="rest.prefix" value="/xml"/>
<cm:property name="mods.xslt" value="edu/amherst/acdc/exts/serialize/xml/rdf2mods.xsl"/>
<cm:property name="dc.xslt" value="edu/amherst/acdc/exts/serialize/xml/rdf2dc.xsl"/>
<cm:property name="fcrepo.baseUrl" value="http://localhost:8080/fcrepo/rest"/>
<cm:property name="fcrepo.authUsername" value=""/>
<cm:property name="fcrepo.authPassword" value=""/>
<cm:property name="extension.load" value="false" />
<cm:property name="extension.load.uri" value="http://apix/services//apix:load" />
<cm:property name="extension.load.maximumRedeliveries" value="60" />
</cm:default-properties>
</cm:property-placeholder>
<camelContext id="AcrepoExtsSerializeXml" xmlns="http://camel.apache.org/schema/blueprint">
<package>edu.amherst.acdc.exts.serialize.xml</package>
<!-- Self-register the loader service as an extension -->
<route id="load-extension">
<from uri="timer:register?repeatCount=1" />
<onException>
<exception>java.lang.Exception</exception>
<redeliveryPolicy maximumRedeliveries="{{extension.load.maximumRedeliveries}}"
logRetryAttempted="true" retryAttemptedLogLevel="INFO" />
</onException>
<setHeader headerName="Content-Type">
<constant>text/plain</constant>
</setHeader>
<setHeader headerName="CamelHttpMethod">
<constant>POST</constant>
</setHeader>
<setBody>
<simple>http://{{rest.host}}:{{rest.port}}{{rest.prefix}}</simple>
</setBody>
<choice>
<when>
<simple>{{extension.load}}</simple>
<to
uri="jetty:{{extension.load.uri}}?okStatusCodeRange=200-399" />
</when>
</choice>
</route>
</camelContext>
</blueprint>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'
xmlns:skos="http://www.w3.org/2004/02/skos/core#"
xmlns:dcterms='http://purl.org/dc/terms/'
xmlns:dc='http://purl.org/dc/elements/1.1/'
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:bf="http://id.loc.gov/ontologies/bibframe/"
exclude-result-prefixes="rdf rdfs bf skos"
version="2.0">
<xsl:output indent="yes" method="xml"/>
<xsl:strip-space elements="dc:*"/>
<xsl:param name="CamelFcrepoUri"/>
<xsl:template name="getValue">
<xsl:choose>
<xsl:when test="./@rdf:resource">
<xsl:attribute name="xsi:type">dcterms:URI</xsl:attribute>
<xsl:value-of select="@rdf:resource"/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="@rdf:datatype">
<xsl:attribute name="xsi:type"><xsl:value-of select="@rdf:datatype"/></xsl:attribute>
</xsl:if>
<xsl:value-of select="normalize-space(text())"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="node()|@*">
<xsl:apply-templates select="node()|@*"/>
</xsl:template>
<xsl:template match="dc:contributor | dc:coverage | dc:creator | dc:description | dc:format | dc:identifier | dc:language
| dc:publisher | dc:relation | dc:rights | dc:source | dc:subject | dc:title | dc:type">
<xsl:element name="{name()}">
<xsl:call-template name="getValue"/>
</xsl:element>
</xsl:template>
<xsl:template match="dcterms:contributor | dcterms:coverage | dcterms:creator | dcterms:description | dcterms:format
| dcterms:identifier | dcterms:language | dcterms:publisher | dcterms:relation | dcterms:rights | dcterms:source
| dcterms:subject | dcterms:title | dcterms:type">
<xsl:element name="{concat('dc:', local-name())}">
<xsl:call-template name="getValue"/>
</xsl:element>
</xsl:template>
<xsl:template match="dcterms:spatial | dcterms:temporal">
<dc:coverage>
<xsl:call-template name="getValue"/>
</dc:coverage>
</xsl:template>
<xsl:template match="dc:date | dcterms:date | dcterms:available | dcterms:created | dcterms:dateAccepted | dcterms:dateCopyrighted | dcterms:dateSubmitted
| dcterms:issued | dcterms:modified | dcterms:valid">
<dc:date>
<xsl:call-template name="getValue"/>
</dc:date>
</xsl:template>
<xsl:template match="dcterms:abstract | dcterms:tableOfContents | rdfs:comment">
<dc:description>
<xsl:call-template name="getValue"/>
</dc:description>
</xsl:template>
<xsl:template match="dcterms:extent | dcterms:medium | bf:extent">
<dc:format>
<xsl:call-template name="getValue"/>
</dc:format>
</xsl:template>
<xsl:template match="dcterms:bibliographicCitation">
<dc:identifier>
<xsl:call-template name="getValue"/>
</dc:identifier>
</xsl:template>
<xsl:template match="dcterms:conformsTo | dcterms:hasFormat | dcterms:hasPart | dcterms:hasVersion | dcterms:isFormatOf | dcterms:isPartOf
| dcterms:isReferencedBy | dcterms:isReplacedBy | dcterms:isRequiredBy | dcterms:isVersionOf | dcterms:references | dcterms:replaces
| dcterms:requires | bf:heldBy">
<dc:relation>
<xsl:call-template name="getValue"/>
</dc:relation>
</xsl:template>
<xsl:template match="dcterms:accessRights | dcterms:license">
<dc:rights>
<xsl:call-template name="getValue"/>
</dc:rights>
</xsl:template>
<xsl:template match="skos:prefLabel | skos:altLabel | rdfs:label">
<dc:title>
<xsl:call-template name="getValue"/>
</dc:title>
</xsl:template>
<xsl:template match="rdf:Description">
<xsl:if test="ends-with(@rdf:about, $CamelFcrepoUri)">
<xsl:apply-templates select="node()|@*"/>
</xsl:if>
</xsl:template>
<xsl:template match="/rdf:RDF">
<oai_dc:dc>
<xsl:apply-templates select="node()|@*"/>
</oai_dc:dc>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'
xmlns:dcterms='http://purl.org/dc/terms/'
xmlns:dc='http://purl.org/dc/elements/1.1/'
xmlns:mods="http://www.loc.gov/mods/v3"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs rdf rdfs dc dcterms"
version="2.0">
<xsl:output indent="yes" method="xml"></xsl:output>
<xsl:param name="CamelFcrepoUri"/>
<xsl:template match="/rdf:RDF/rdf:Description">
<xsl:if test="ends-with(@rdf:about, $CamelFcrepoUri)">
<mods:mods>
<xsl:for-each select="dc:title">
<mods:titleInfo>
<mods:title><xsl:value-of select="normalize-space(text())"/></mods:title>
</mods:titleInfo>
</xsl:for-each>
<xsl:for-each select="dc:subject">
<mods:subject authority="lcsh">
<mods:topic>
<xsl:value-of select="normalize-space(text())"/>
</mods:topic>
</mods:subject>
</xsl:for-each>
<xsl:for-each select="dcterms:abstract">
<xsl:if test="string-length(normalize-space(text()))">
<mods:abstract><xsl:value-of select="normalize-space(text())"/></mods:abstract>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="rdfs:comment">
<xsl:if test="string-length(normalize-space(text()))">
<mods:note><xsl:value-of select="normalize-space(text())"/></mods:note>
</xsl:if>
</xsl:for-each>
</mods:mods>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
<html>
<body>
<h2>Pick a format</h2>
<div id="formats">
<div class="format"><a href="dc">Dublin Core</a></dov>
<div class="format"><a href="mods">MODS</a></div>
</div>
</body>
</html>
\ No newline at end of file
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ldp: <http://www.w3.org/ns/ldp#> .
@prefix registry: <http://acdc.amherst.edu/ns/registry#> .
@prefix apix: <http://fedora.info/definitions/v4/api-extension#> .
<> a apix:Extension ;
rdfs:label "XML Metadata Service" ;
rdfs:comment "A service that transforms RDFSource documents to plain XML (not RDF/XML)" ;
apix:exposesService registry:XmlMetadataService ;
apix:exposesServiceAt "svc:xmlmetadata/" ;
apix:bindsTo ldp:Resource .
/*
* Copyright 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.serialize.xml;
import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.apache.camel.builder.PredicateBuilder.and;
import static org.apache.camel.util.ObjectHelper.loadResourceAsStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.model.language.XPathExpression;
import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;
import org.junit.Test;
/**
* @author acoburn
*/
public class RouteTest extends CamelBlueprintTestSupport {
@EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
@Produce(uri = "direct:start")
protected ProducerTemplate template;
@Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
@Override
protected Properties useOverridePropertiesWithPropertiesComponent() {
final Properties props = new Properties();
props.put("rest.port", "9999");
return props;
}
@Test
public void testDCRoute() throws Exception {
context.getRouteDefinition("XmlDcXslt").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
replaceFromWith("direct:start");
weaveAddLast().to("mock:result");
}
});
context.getRouteDefinition("XmlTransformationCommon").adviceWith(context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
mockEndpointsAndSkip("http*");
}
});
final Map<String, String> namespaces = new HashMap<>();
namespaces.put("oai_dc", "http://www.openarchives.org/OAI/2.0/oai_dc/");
namespaces.put("dc", "http://purl.org/dc/elements/1.1/");
namespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance");