Commit 9dd75653 authored by acoburn's avatar acoburn
Browse files

implement a PCDM object service

parent b504d1d0
...@@ -25,6 +25,7 @@ These modules provide particular services, independent of Fedora Resources. ...@@ -25,6 +25,7 @@ These modules provide particular services, independent of Fedora Resources.
* `acrepo-services-ldcache`: This service dereferences and caches URIs, retrieving the `object` of particular triples on demand * `acrepo-services-ldcache`: This service dereferences and caches URIs, retrieving the `object` of particular triples on demand
* `acrepo-services-ldcache-file`: A file-based backend for the `acrepo-services-ldcache` service * `acrepo-services-ldcache-file`: A file-based backend for the `acrepo-services-ldcache` service
* `acrepo-services-mint`: This mints random (public) URIs for use with fedora resources * `acrepo-services-mint`: This mints random (public) URIs for use with fedora resources
* `acrepo-services-pcdm`: This makes it easy to work with PCDM objects
* `acrepo-services-validation`: An OSGi-based validation service * `acrepo-services-validation`: An OSGi-based validation service
Connectors Connectors
...@@ -72,6 +73,7 @@ command from its shell: ...@@ -72,6 +73,7 @@ command from its shell:
feature:install acrepo-services-ldcache feature:install acrepo-services-ldcache
feature:install acrepo-services-ldcache-file feature:install acrepo-services-ldcache-file
feature:install acrepo-services-mint feature:install acrepo-services-mint
feature:install acrepo-services-pcdm
feature:install acrepo-services-validation feature:install acrepo-services-validation
More information More information
......
...@@ -82,7 +82,8 @@ public class AcrepoServicesIT extends AbstractOSGiIT { ...@@ -82,7 +82,8 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
"acrepo-services-validation", "acrepo-services-jsonld", "acrepo-services-mint", "acrepo-services-validation", "acrepo-services-jsonld", "acrepo-services-mint",
"acrepo-exts-jsonld", "acrepo-template-mustache", "acrepo-exts-fits", "acrepo-exts-jsonld", "acrepo-template-mustache", "acrepo-exts-fits",
"acrepo-libs-jena", "acrepo-libs-sesame", "acrepo-libs-jsonld", "acrepo-libs-jena", "acrepo-libs-sesame", "acrepo-libs-jsonld",
"acrepo-libs-jackson", "acrepo-libs-marmotta", "acrepo-services-ldcache"), "acrepo-libs-jackson", "acrepo-libs-marmotta", "acrepo-services-ldcache",
"acrepo-services-pcdm"),
editConfigurationFilePut("etc/edu.amherst.acdc.exts.fits.cfg", "rest.port", fitsPort), editConfigurationFilePut("etc/edu.amherst.acdc.exts.fits.cfg", "rest.port", fitsPort),
editConfigurationFilePut("etc/edu.amherst.acdc.exts.jsonld.cfg", "rest.port", jsonldPort), editConfigurationFilePut("etc/edu.amherst.acdc.exts.jsonld.cfg", "rest.port", jsonldPort),
...@@ -105,6 +106,7 @@ public class AcrepoServicesIT extends AbstractOSGiIT { ...@@ -105,6 +106,7 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-jsonld"))); assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-jsonld")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-ldcache"))); assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-ldcache")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-mint"))); assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-mint")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-pcdm")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-validation"))); assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-validation")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-exts-fits"))); assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-exts-fits")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-exts-jsonld"))); assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-exts-jsonld")));
......
...@@ -85,6 +85,14 @@ ...@@ -85,6 +85,14 @@
<configfile finalname="/etc/edu.amherst.acdc.services.mint.cfg">mvn:edu.amherst.acdc/acrepo-services-mint/${project.version}/cfg/configuration</configfile> <configfile finalname="/etc/edu.amherst.acdc.services.mint.cfg">mvn:edu.amherst.acdc/acrepo-services-mint/${project.version}/cfg/configuration</configfile>
</feature> </feature>
<feature name="acrepo-services-pcdm" version="${project.version}">
<details>Installs the PCDM Resource service</details>
<feature version="${project.version}">acrepo-libs-jena</feature>
<bundle>mvn:edu.amherst.acdc/acrepo-services-pcdm/${project.version}</bundle>
</feature>
<feature name="acrepo-services-validation" version="${project.version}"> <feature name="acrepo-services-validation" version="${project.version}">
<details>Installs the validation service</details> <details>Installs the validation service</details>
......
...@@ -77,6 +77,7 @@ public class KarafIT { ...@@ -77,6 +77,7 @@ public class KarafIT {
final String version = cm.getProperty("project.version"); final String version = cm.getProperty("project.version");
final String acrepoIdiomatic = getBundleUri("acrepo-idiomatic", version); final String acrepoIdiomatic = getBundleUri("acrepo-idiomatic", version);
final String acrepoPcdmSvc = getBundleUri("acrepo-services-pcdm", version);
final String acrepoValidationSvc = getBundleUri("acrepo-services-validation", version); final String acrepoValidationSvc = getBundleUri("acrepo-services-validation", version);
final String acrepoJsonLdSvc = getBundleUri("acrepo-services-jsonld", version); final String acrepoJsonLdSvc = getBundleUri("acrepo-services-jsonld", version);
final String acrepoJsonLd = getBundleUri("acrepo-jsonld-service", version); final String acrepoJsonLd = getBundleUri("acrepo-jsonld-service", version);
...@@ -116,12 +117,14 @@ public class KarafIT { ...@@ -116,12 +117,14 @@ public class KarafIT {
CoreOptions.systemProperty("acdc.jsonld-bundle").value(acrepoJsonLd), CoreOptions.systemProperty("acdc.jsonld-bundle").value(acrepoJsonLd),
CoreOptions.systemProperty("acdc.jsonld-svc-bundle").value(acrepoJsonLdSvc), CoreOptions.systemProperty("acdc.jsonld-svc-bundle").value(acrepoJsonLdSvc),
CoreOptions.systemProperty("acdc.mint-svc-bundle").value(acrepoMintSvc), CoreOptions.systemProperty("acdc.mint-svc-bundle").value(acrepoMintSvc),
CoreOptions.systemProperty("acdc.pcdm-svc-bundle").value(acrepoPcdmSvc),
bundle(acrepoIdiomatic).start(), bundle(acrepoIdiomatic).start(),
bundle(acrepoValidationSvc).start(), bundle(acrepoValidationSvc).start(),
bundle(acrepoJsonLd).start(), bundle(acrepoJsonLd).start(),
bundle(acrepoJsonLdSvc).start(), bundle(acrepoJsonLdSvc).start(),
bundle(acrepoMintSvc).start(), bundle(acrepoMintSvc).start(),
bundle(acrepoPcdmSvc).start(),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", rmiRegistryPort), editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", rmiRegistryPort),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", rmiServerPort), editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", rmiServerPort),
...@@ -145,6 +148,7 @@ public class KarafIT { ...@@ -145,6 +148,7 @@ public class KarafIT {
assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.jsonld-bundle")).getState()); assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.jsonld-bundle")).getState());
assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.jsonld-svc-bundle")).getState()); assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.jsonld-svc-bundle")).getState());
assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.mint-svc-bundle")).getState()); assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.mint-svc-bundle")).getState());
assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.pcdm-svc-bundle")).getState());
} }
private <T> T getOsgiService(final Class<T> type, final String filter, final long timeout) { private <T> T getOsgiService(final Class<T> type, final String filter, final long timeout) {
......
Repository PCDM Object Handler
==============================
This module makes it easier to work with PCDM resources.
It uses simple OWL inference to handle inverse properties.
Building
--------
To build this project use
mvn install
Deploying in OSGi
-----------------
Each of these projects 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-services-pcdm
<?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/xsd/maven-4.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-services-pcdm</artifactId>
<packaging>bundle</packaging>
<name>PCDM Object Bundle</name>
<description>A Service for modeling PCDM objects</description>
<properties>
<osgi.export.packages>edu.amherst.acdc.services.pcdm;version=${project.version}</osgi.export.packages>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-osgi</artifactId>
</dependency>
<!-- logging and testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>${xerces.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>install</defaultGoal>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
<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>
<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>
/*
* 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.services.pcdm;
import java.io.InputStream;
import java.util.Set;
import org.apache.jena.rdf.model.Model;
/**
* @author acoburn
* @since 6/16/16
*/
public interface PcdmService {
/**
* Parse an InputStream whether an RDF graph is valid according to a set of OWL restrictions
*
* @param input The input RDF graph
* @param contentType The mimeType of the input
* @return the RDF Model
*/
Model parse(final InputStream input, final String contentType);
/**
* Parse an InputStream into an existing model
*
* @param model the existing model
* @param input the input RDF graph
* @param contentType the mimeType of the input
* @return the RDF Model
*/
Model parseInto(final Model model, final InputStream input, final String contentType);
/**
* Is this a pcdm:Object
*
* @param model the model
* @param subject the subject
* @return whether the subject is a pcdm:Object
*/
boolean isObject(final Model model, final String subject);
/**
* Is this a pcdm:File
*
* @param model the model
* @param subject the subject
* @return whether the subject is a pcdm:File
*/
boolean isFile(final Model model, final String subject);
/**
* Is this a pcdm:Collection
*
* @param model the model
* @param subject the subject
* @return whether the subject is a pcdm:Collection
*/
boolean isCollection(final Model model, final String subject);
/**
* Get the object values for the pcdm:memberOf triples
*
* @param model The model
* @param subject the subject
* @return a set of object values
*/
Set<String> getMemberOf(final Model model, final String subject);
/**
* Get the object values for the pcdm:hasMember triples
*
* @param model The model
* @param subject the subject
* @return a set of object values
*/
Set<String> getHasMember(final Model model, final String subject);
/**
* Get the object values for the pcdm:fileOf triples
*
* @param model The model
* @param subject the subject
* @return a set of object values
*/
Set<String> getFileOf(final Model model, final String subject);
/**
* Get the object values for the pcdm:hasFile triples
*
* @param model The model
* @param subject the subject
* @return a set of object values
*/
Set<String> getHasFile(final Model model, final String subject);
/**
* Get the object values for the pcdm:relatedObjectOf triples
*
* @param model The model
* @param subject the subject
* @return a set of object values
*/
Set<String> getRelatedObjectOf(final Model model, final String subject);
/**
* Get the object values for the pcdm:hasRelatedObject triples
*
* @param model The model
* @param subject the subject
* @return a set of object values
*/
Set<String> getHasRelatedObject(final Model model, final String subject);
/**
* Get the triples from this model
*
* @param model the model
* @param contentType the contentType
* @return the RDF serialization
*/
InputStream getTriples(final Model model, final String contentType);
}
/*
* 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.services.pcdm;
import static java.util.stream.Collectors.toSet;
import static org.apache.jena.atlas.iterator.Iter.asStream;
import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
import static org.apache.jena.rdf.model.ModelFactory.createInfModel;
import static org.apache.jena.rdf.model.ResourceFactory.createProperty;
import static org.apache.jena.rdf.model.ResourceFactory.createResource;
import static org.apache.jena.reasoner.ReasonerRegistry.getOWLMicroReasoner;
import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
import static org.apache.jena.vocabulary.RDF.type;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Set;
import org.apache.jena.rdf.model.InfModel;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.RDFNode;
/**
* @author acoburn
* @since 6/16/16
*/
public class PcdmServiceImpl implements PcdmService {
private static final String PCDM_NAMESPACE = "http://pcdm.org/models#";
private final Model pcdmModel = createDefaultModel();
/**
* Create a PCDM Service object
*/
public PcdmServiceImpl() {
pcdmModel.read(getClass().getResourceAsStream("/pcdm.owl"), null, "TTL");
}
@Override
public Model parse(final InputStream input, final String contentType) {
return parseInto(createDefaultModel(), input, contentType);
}
@Override
public Model parseInto(final Model model, final InputStream input, final String contentType) {
model.read(input, null, contentTypeToLang(contentType).getName());
return model;
}
@Override
public boolean isObject(final Model model, final String subject) {
return model.contains(createResource(subject), type,
createResource(PCDM_NAMESPACE + "Object"));
}
@Override
public boolean isFile(final Model model, final String subject) {
return model.contains(createResource(subject), type,
createResource(PCDM_NAMESPACE + "File"));
}
@Override
public boolean isCollection(final Model model, final String subject) {
return model.contains(createResource(subject), type,
createResource(PCDM_NAMESPACE + "Collection"));
}
@Override
public Set<String> getMemberOf(final Model model, final String subject) {
return getObjectsOfProperty(model, subject, PCDM_NAMESPACE + "memberOf");
}
@Override
public Set<String> getHasMember(final Model model, final String subject) {
return getObjectsOfProperty(model, subject, PCDM_NAMESPACE + "hasMember");
}
@Override
public Set<String> getFileOf(final Model model, final String subject) {
return getObjectsOfProperty(model, subject, PCDM_NAMESPACE + "fileOf");
}
@Override
public Set<String> getHasFile(final Model model, final String subject) {
return getObjectsOfProperty(model, subject, PCDM_NAMESPACE + "hasFile");
}
@Override
public Set<String> getRelatedObjectOf(final Model model, final String subject) {
return getObjectsOfProperty(model, subject, PCDM_NAMESPACE + "relatedObjectOf");
}
@Override
public Set<String> getHasRelatedObject(final Model model, final String subject) {
return getObjectsOfProperty(model, subject, PCDM_NAMESPACE + "hasRelatedObject");
}
@Override
public InputStream getTriples(final Model model, final String contentType) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
model.write(os, contentTypeToLang(contentType).getName());
return new ByteArrayInputStream(os.toByteArray());
}
private Set<String> getObjectsOfProperty(final Model model, final String subject, final String property) {
final InfModel infModel = createInfModel(getOWLMicroReasoner(), model);
infModel.add(pcdmModel);
return asStream(infModel.listObjectsOfProperty(createResource(subject), createProperty(property)))
.filter(RDFNode::isURIResource).map(RDFNode::asResource).map(Resource::getURI).collect(toSet());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"
default-activation="lazy">
<bean id="pcdmServiceBean" class="edu.amherst.acdc.services.pcdm.PcdmServiceImpl"/>
<service ref="pcdmServiceBean" interface="edu.amherst.acdc.services.pcdm.PcdmService">
<service-properties>
<entry key="osgi.jndi.service.name" value="pcdm"/>
</service-properties>
</service>
</blueprint>
@prefix dc: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix ore: <http://www.openarchives.org/ore/terms/> .
@prefix pcdm: <http://pcdm.org/models#> .
<http://pcdm.org/models#>
a owl:Ontology ;
dc:title "Portland Common Data Model"@en ;
dc:modified "2015-09-28"^^xsd:date ;
dc:publisher <http://www.duraspace.org/> ;
rdfs:seeAlso <https://github.com/duraspace/pcdm/wiki> ;
rdfs:comment "Ontology for the Portland Common Data Model, intended to underlie a wide array of repository and DAMS applications."@en ;
owl:versionInfo "2015/09/28" ;
owl:priorVersion <http://pcdm.org/2015/03/16/models> .
pcdm:Collection
a rdfs:Class ;
rdfs:label "Collection"@en ;
rdfs:comment """
A Collection is a group of resources. Collections have descriptive metadata, access metadata,
and may links to works and/or collections. By default, member works and collections are an
unordered set, but can be ordered using the ORE Proxy class.
"""@en ;
rdfs:subClassOf ore:Aggregation ;
rdfs:isDefinedBy <http://pcdm.org/models#> .
pcdm:Object
a rdfs:Class ;
rdfs:label "Object"@en ;
rdfs:comment """
An Object is an intellectual entity, sometimes called a "work", "digital object", etc.
Objects have descriptive metadata, access metadata, may contain files and other Objects as
member "components". Each level of a work is therefore represented by an Object instance,
and is capable of standing on its own, being linked to from Collections and other Objects.
Member Objects can be ordered using the ORE Proxy class.
"""@en ;
rdfs:subClassOf ore:Aggregation ;
rdfs:isDefinedBy <http://pcdm.org/models#> .
pcdm:File
a rdfs:Class ;
rdfs:label "File"@en ;
rdfs:comment """
A File is a sequence of binary data and is described by some accompanying metadata.
The metadata typically includes at least basic technical metadata (size, content type,
modification date, etc.), but can also include properties related to preservation,
digitization process, provenance, etc. Files MUST be contained by exactly one Object.
"""@en ;
rdfs:isDefinedBy <http://pcdm.org/models#> .
pcdm:hasFile
a rdf:Property ;
rdfs:label "has file"@en ;
rdfs:comment "Links to a File contained by this Object."@en ;
rdfs:domain pcdm:Object ;
rdfs:range pcdm:File ;
rdfs:subPropertyOf ore:aggregates ;
rdfs:isDefinedBy <http://pcdm.org/models#> .
pcdm:fileOf
a rdf:Property ;
rdfs:label "is file of"@en ;
rdfs:comment "Links from a File to its containing Object."@en ;
rdfs:range pcdm:Object ;
rdfs:domain pcdm:File ;
rdfs:subPropertyOf ore:isAggregatedBy ;
rdfs:isDefinedBy <http://pcdm.org/models#> ;