Commit b5193831 authored by acoburn's avatar acoburn
Browse files

Implement an OSGi-based validation service

parent a0d6fcff
......@@ -14,6 +14,7 @@ Services
* `acrepo-jsonld-osgi`: This service creates expanded or compact JSON-LD representations of input documents
* `acrepo-jsonld-service`: This service exposes an HTTP endpoint for creating compact JSON-LD documents from a fedora repository using a pluggable context document
* `acrepo-mint-service`: This mints random (public) URIs for use with fedora resources
* `acrepo-services-validation`: An OSGi-based validation service
* `acrepo-xml-metadata`: This service translates Fedora RDF documents into MODS/XML or DC/XML
Building
......@@ -37,11 +38,9 @@ command from its shell:
feature:install acrepo-jsonld-osgi
feature:install acrepo-jsonld-service
feature:install acrepo-mint-service
feature:install acrepo-services-validation
feature:install acrepo-xml-metadata
Or by copying any of the compiled bundles into `$KARAF_HOME/deploy`.
More information
----------------
......
......@@ -78,6 +78,7 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
features(maven().groupId("edu.amherst.acdc").artifactId("acrepo-karaf")
.type("xml").classifier("features").versionAsInProject(), "acrepo-idiomatic",
"acrepo-idiomatic-pgsql", "acrepo-mint-service", "acrepo-xml-metadata",
"acrepo-services-validation",
"acrepo-jsonld-service", "acrepo-jsonld-osgi", "acrepo-template-mustache",
"acrepo-image-service"),
......@@ -100,6 +101,7 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-idiomatic")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-idiomatic-pgsql")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-mint-service")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-services-validation")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-xml-metadata")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-jsonld-service")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-jsonld-osgi")));
......
......@@ -53,6 +53,13 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>edu.amherst.acdc</groupId>
<artifactId>acrepo-services-validation</artifactId>
<scope>test</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>edu.amherst.acdc</groupId>
<artifactId>acrepo-mint-service</artifactId>
......@@ -113,6 +120,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.andrewoma.dexx</groupId>
<artifactId>collection</artifactId>
<version>${dexx.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
......
......@@ -63,6 +63,17 @@
<configfile finalname="/etc/edu.amherst.acdc.mint.cfg">mvn:edu.amherst.acdc/acrepo-mint-service/${project.version}/cfg/configuration</configfile>
</feature>
<feature name="acrepo-services-validation" version="${project.version}" resolver="(orb)" start-level="50">
<details>Installs the validation service</details>
<bundle>mvn:org.apache.jena/jena-osgi/${jena.version}</bundle>
<bundle>mvn:com.github.andrewoma.dexx/collection/${dexx.version}</bundle>
<bundle>mvn:edu.amherst.acdc/acrepo-services-validation/${project.version}</bundle>
<configfile finalname="/etc/edu.amherst.acdc.services.validation.cfg">mvn:edu.amherst.acdc/acrepo-services-validation/${project.version}/cfg/configuration</configfile>
</feature>
<feature name="acrepo-xml-metadata" version="${project.version}" resolver="(orb)" start-level="50">
<details>Installs the MODS/XML translation service</details>
......
......@@ -77,6 +77,7 @@ public class KarafIT {
final String version = cm.getProperty("project.version");
final String acrepoIdiomatic = getBundleUri("acrepo-idiomatic", version);
final String acrepoValidationSvc = getBundleUri("acrepo-services-validation", version);
final String acrepoJsonLd = getBundleUri("acrepo-jsonld-osgi", version);
final String acrepoJsonLdSvc = getBundleUri("acrepo-jsonld-service", version);
final String acrepoMint = getBundleUri("acrepo-mint-service", version);
......@@ -107,13 +108,17 @@ public class KarafIT {
mavenBundle().groupId("org.apache.httpcomponents").artifactId("httpcore-osgi").versionAsInProject(),
mavenBundle().groupId("commons-io").artifactId("commons-io").versionAsInProject(),
mavenBundle().groupId("commons-codec").artifactId("commons-codec").versionAsInProject(),
mavenBundle().groupId("org.apache.jena").artifactId("jena-osgi").versionAsInProject(),
mavenBundle().groupId("com.github.andrewoma.dexx").artifactId("collection").versionAsInProject(),
CoreOptions.systemProperty("acdc.idiomatic-bundle").value(acrepoIdiomatic),
CoreOptions.systemProperty("acdc.validation-svc-bundle").value(acrepoValidationSvc),
CoreOptions.systemProperty("acdc.jsonld-bundle").value(acrepoJsonLd),
CoreOptions.systemProperty("acdc.jsonld-svc-bundle").value(acrepoJsonLdSvc),
CoreOptions.systemProperty("acdc.mint-bundle").value(acrepoMint),
bundle(acrepoIdiomatic).start(),
bundle(acrepoValidationSvc).start(),
bundle(acrepoJsonLd).start(),
bundle(acrepoJsonLdSvc).start(),
bundle(acrepoMint).start(),
......@@ -136,6 +141,7 @@ public class KarafIT {
assertNotNull(bundleContext);
assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.idiomatic-bundle")).getState());
assertEquals(ACTIVE, bundleContext.getBundle(System.getProperty("acdc.validation-svc-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.mint-bundle")).getState());
......
Repository Validation Service
=============================
This OSGi service validates RDF documents using OWL restrictions.
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 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-services-validation
<?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-validation</artifactId>
<packaging>bundle</packaging>
<name>RDF Validation service bundle</name>
<description>An OSGi service that validates an RDF document based on a set of OWL restrictions.</description>
<properties>
<osgi.export.packages>edu.amherst.acdc.services.validation;version=${project.version}</osgi.export.packages>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-osgi</artifactId>
</dependency>
<!-- logging and testing -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>${xerces.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</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>
<!-- 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.services.validation.cfg</file>
<type>cfg</type>
<classifier>configuration</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
# The URI for defining a valid type
validation.type=urn:validation#Valid
/*
* 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.validation;
import java.io.InputStream;
/**
* @author acoburn
* @since 6/16/16
*/
public interface ValidationService {
/**
* Determine whether an RDF graph is valid according to a set of OWL restrictions
*
* @param subject The subject of the RDF graph
* @param input The input RDF graph
* @param contentType The mimeType of the input
* @param restrictions any OWL restrictions
* @param restrictionFormat the mimeType of the restriction graph
* @return whether the input document is valid
*/
boolean validate(final String subject, final InputStream input, final String contentType,
final InputStream restrictions, final String restrictionFormat);
}
/*
* 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.validation;
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.createResource;
import static org.apache.jena.reasoner.ReasonerRegistry.getOWLReasoner;
import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
import static org.apache.jena.vocabulary.RDF.type;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.InputStream;
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.reasoner.ValidityReport;
import org.slf4j.Logger;
/**
* @author acoburn
* @since 6/16/16
*/
public class ValidationServiceImpl implements ValidationService {
private static final Logger LOGGER = getLogger(ValidationServiceImpl.class);
private final Resource validationType;
/**
* Instantiate a ValidationService object
* @param validType a URI for the RDF type used for validation.
*/
public ValidationServiceImpl(final String validType) {
this.validationType = createResource(validType);
}
/**
* Generate a compact representation of the input stream
*
* @param input The input JSON document
* @param contextUrl the location of a context URL
* @return the compacted JSON Object
*/
@Override
public boolean validate(final String subject, final InputStream input, final String contentType,
final InputStream restrictions, final String restrictionFormat) {
final Resource subjectResource = createResource(subject);
final Model model = createDefaultModel();
model.read(input, subject, contentTypeToLang(contentType).getName());
final Model schema = createDefaultModel();
schema.read(restrictions, null,
contentTypeToLang(restrictionFormat).getName());
final InfModel infmodel = createInfModel(getOWLReasoner(), model);
infmodel.add(schema);
final ValidityReport validity = infmodel.validate();
if (validity.isValid()) {
return infmodel.contains(subjectResource, type, validationType);
}
validity.getReports().forEachRemaining(report -> {
LOGGER.warn(report.toString());
});
return false;
}
}
<?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">
<cm:property-placeholder persistent-id="edu.amherst.acdc.services.validation" update-strategy="reload" >
<cm:default-properties>
<cm:property name="validation.type" value="urn:validation#Valid"/>
</cm:default-properties>
</cm:property-placeholder>
<bean id="validationServiceBean" class="edu.amherst.acdc.services.validation.ValidationServiceImpl">
<argument value="${validation.type}"/>
</bean>
<service ref="validationServiceBean" interface="edu.amherst.acdc.services.validation.ValidationService">
<service-properties>
<entry key="osgi.jndi.service.name" value="validation"/>
</service-properties>
</service>
</blueprint>
/*
* 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.validation;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/**
* @author acoburn
* @since 6/16/16
*/
public class ValidationServiceTest {
private final String TURTLE = "text/turtle";
private final String subject = "http://localhost:8080/fcrepo/rest/test";
private final ValidationService svc = new ValidationServiceImpl("urn:validation#Valid");
@Test
public void testValidateIsEquivalentClass() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/isResource.ttl"), TURTLE));
}
@Test
public void testValidateIsNotEquivalentClass() {
assertFalse(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/isBinary.ttl"), TURTLE));
}
@Test
public void testValidateHasValue() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/hasValue.ttl"), TURTLE));
}
@Test
public void testValidateCardinality() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/hasPrefLabel.ttl"), TURTLE));
}
@Test
public void testValidateResourceSubClass() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/isResourceSubClass.ttl"), TURTLE));
}
@Test
public void testValidateIntersectionOf() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/intersectionOf.ttl"), TURTLE));
}
@Test
public void testValidateUnionOf() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/unionOf.ttl"), TURTLE));
}
@Test
public void testValidateApplicationProfile() {
assertTrue(svc.validate(subject,
getClass().getResourceAsStream("/resource.ttl"), TURTLE,
getClass().getResourceAsStream("/applicationProfile.ttl"), TURTLE));
}
}
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix validation: <urn:validation#> .
@prefix pcdm: <http://pcdm.org/models#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
validation:Valid owl:equivalentClass [
owl:intersectionOf (
pcdm:Object
[ a owl:Restriction ;
owl:onProperty skos:prefLabel ;
owl:minCardinality "1"^^xsd:nonNegativeInteger ]
[ a owl:Restriction ;
owl:onProperty dcterms:description ;
owl:minCardinality "1"^^xsd:nonNegativeInteger ] ) ] .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix validation: <urn:validation#> .
validation:Valid owl:equivalentClass [ a owl:Restriction ;
owl:onProperty skos:prefLabel ;
owl:minCardinality "1"^^xsd:nonNegativeInteger ] .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix validation: <urn:validation#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
validation:Valid owl:equivalentClass [ a owl:Restriction ;
owl:onProperty fedora:numberOfChildren ;
owl:hasValue "6"^^xsd:long ] .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix validation: <urn:validation#> .
validation:Valid owl:equivalentClass [
owl:intersectionOf (
[ a owl:Restriction ;
owl:onProperty skos:prefLabel ;
owl:minCardinality "1"^^xsd:nonNegativeInteger ]
[ a owl:Restriction ;
owl:onProperty fedora:numberOfChildren ;
owl:hasValue "6"^^xsd:long ] ) ].
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix validation: <urn:validation#> .
validation:Valid owl:equivalentClass fedora:Binary .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix validation: <urn:validation#> .
validation:Valid owl:equivalentClass fedora:Resource .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix validation: <urn:validation#> .
fedora:Resource rdfs:subClassOf validation:Valid .