package org.fcrepo.server.validation.ecm;
import org.fcrepo.server.Context;
import org.fcrepo.server.errors.LowlevelStorageException;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.storage.DOReader;
import org.fcrepo.server.storage.RepositoryReader;
import org.fcrepo.server.storage.types.Datastream;
import org.fcrepo.server.storage.types.RelationshipTuple;
import org.fcrepo.server.storage.types.Validation;
import org.fcrepo.server.validation.ecm.jaxb.DsCompositeModel;
import org.fcrepo.server.validation.ecm.jaxb.DsTypeModel;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.util.OWLClassExpressionVisitorAdapter;
import org.semanticweb.owlapi.util.OWLOntologyMerger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXB;
import java.io.InputStream;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: abr
* Date: Aug 4, 2010
* Time: 10:50:52 AM
* To change this template use File | Settings | File Templates.
*/
public class OwlValidator {
private RepositoryReader doMgr;
private static final Logger logger =
LoggerFactory.getLogger(OwlValidator.class);
public OwlValidator(RepositoryReader doMgr) {
//To change body of created methods use File | Settings | File Templates.
this.doMgr = doMgr;
}
/**
* This is one complex method.
* <p/>
* 1. retrieve the list of content models from the object.
* 2. retrieve the ontology datastreams from each of these content models
* 3.
*
* @param context
* @param asOfDateTime
* @param currentObjectReader
* @param validation
* @throws org.fcrepo.server.errors.ServerException
*
*/
public void validate(Context context, Date asOfDateTime, DOReader currentObjectReader,
Validation validation) throws ServerException {
OWLOntologyManager owlManager = OWLManager.createOWLOntologyManager();
List<String> contentmodels = currentObjectReader.getContentModels();
for (String contentmodel : contentmodels) {
contentmodel = contentmodel.substring("info:fedora/".length());
DOReader contentmodelReader;
try {
contentmodelReader = doMgr.getReader(false, context, contentmodel);
} catch (LowlevelStorageException e) {//content model could not be found
continue;
}
if (asOfDateTime != null) { //disregard content models created after the asOfDateTime
if (!contentmodelReader.getCreateDate().before(asOfDateTime)) {
continue;
}
}
Datastream ontologyDS = contentmodelReader.GetDatastream("ONTOLOGY", asOfDateTime);
if (ontologyDS == null) {//No ontology in the content model, continue
continue;
}
InputStream ontologyStream = ontologyDS.getContentStream();
try {
owlManager.loadOntologyFromOntologyDocument(ontologyStream);
} catch (OWLOntologyCreationException e) {
logger.debug("Failed to load ontology for object " + currentObjectReader.GetObjectPID(), e);
}
}
OWLOntologyMerger merger = new OWLOntologyMerger(owlManager);
IRI mergedOntologyIRI = IRI.create("http://www.semanticweb.com/mymergedont");
OWLOntology mergedOntology = null;
try {
mergedOntology = merger.createMergedOntology(owlManager, mergedOntologyIRI);
} catch (OWLOntologyCreationException e) {
logger.debug("Failed to load ontology for object " + currentObjectReader.GetObjectPID(), e);
}
//Make a new restrition visitor.
RestrictionVisitor restrictionVisitor =
new RestrictionVisitor(Collections.singleton(mergedOntology));
Set<RelationshipTuple> relations = currentObjectReader.getRelationships();
for (String contentmodel : contentmodels) {
List<String> datastreamNames = getDatastreamNames(context, contentmodel, asOfDateTime);
for (String datastreamName : datastreamNames) {
IRI datastreamDeclaration = toIRI(contentmodel, datastreamName);
OWLClass datastreamClass = owlManager.getOWLDataFactory().getOWLClass(datastreamDeclaration);
for (OWLSubClassOfAxiom ax : mergedOntology.getSubClassAxiomsForSubClass(datastreamClass)) {
OWLClassExpression superCls = ax.getSuperClass();
// Ask our superclass to accept a visit from the RestrictionVisitor - if it is an
// existential restiction then our restriction visitor will answer it - if not our
// visitor will ignore it
superCls.accept(restrictionVisitor);
}
String datastream = "info:fedora/" + currentObjectReader.GetObjectPID() + "/" + datastreamName;
/*
NodeList relationsNodes = xpathselector
.selectNodeList(relsint, "/rdf:RDF/rdf:Description[@rdf:about='" + datastream + "']*/
/*");
*/
Set<RelationshipTuple> relationsAbout = getRelationsSubjectTo(relations, datastream);
checkMinCardinality(datastream, relationsAbout, restrictionVisitor, validation);
checkMaxCardinality(datastream, relationsAbout, restrictionVisitor, validation);
checkExactCardinality(datastream, relationsAbout, restrictionVisitor, validation);
checkSomeValuesFrom(datastream, relationsAbout, restrictionVisitor, validation, context);
checkAllValuesFrom(datastream, relationsAbout, restrictionVisitor, validation, context);
restrictionVisitor.reset();
}
}
for (String contentmodel : contentmodels) {
IRI objectDeclaration = toIRI(contentmodel, null);
OWLClass objectClass = owlManager.getOWLDataFactory().getOWLClass(objectDeclaration);
for (OWLSubClassOfAxiom ax : mergedOntology.getSubClassAxiomsForSubClass(objectClass)) {
OWLClassExpression superCls = ax.getSuperClass();
// Ask our superclass to accept a visit from the RestrictionVisitor - if it is an
// existential restiction then our restriction visitor will answer it - if not our
// visitor will ignore it
superCls.accept(restrictionVisitor);
}
String pid = "info:fedora/" + currentObjectReader.GetObjectPID();
Set<RelationshipTuple> relationsAbout = getRelationsSubjectTo(relations, pid);
checkMinCardinality(pid, relationsAbout, restrictionVisitor, validation);
checkMaxCardinality(pid, relationsAbout, restrictionVisitor, validation);
checkExactCardinality(pid, relationsAbout, restrictionVisitor, validation);
checkSomeValuesFrom(pid, relationsAbout, restrictionVisitor, validation, context);
checkAllValuesFrom(pid, relationsAbout, restrictionVisitor, validation, context);
restrictionVisitor.reset();
}
}
private Set<RelationshipTuple> getRelationsSubjectTo(Set<RelationshipTuple> relations, String datastream) {
HashSet<RelationshipTuple> found = new HashSet<RelationshipTuple>();
for (RelationshipTuple relation : relations) {
if (relation.subject.equals(datastream)){
found.add(relation);
}
}
return found;
}
private void checkAllValuesFrom(String subject, Set<RelationshipTuple> relations,
RestrictionVisitor restrictionVisitor, Validation validation, Context context)
throws ServerException {
Map<OWLObjectProperty, OWLClass> map = restrictionVisitor.getAllValuesFrom();
for (OWLObjectProperty owlObjectProperty : map.keySet()) {
String ontologyrelation = owlObjectProperty.getIRI().toString();
OWLClass requiredclass = map.get(owlObjectProperty);
String requiredTarget = requiredclass.getIRI().toString();
for (RelationshipTuple relation : relations) {
String objectRelationName = relation.predicate;
if (objectRelationName.equals(ontologyrelation)) {//This is one of the restricted relations
String target = relation.object;
List<String> classes;
try {
classes = getClassesOfTarget(target, context);
} catch (ServerException e){
validation.setValid(false);
validation.getObjectProblems().add(Errors.missingObjectViolation(subject,objectRelationName,requiredTarget, target));
continue;
}
boolean found = false;
for (String aClass : classes) {
if (aClass.equals(requiredclass.getIRI().toString())){
found = true;
break;
}
}
if (!found) {
validation.setValid(false);
validation.getObjectProblems().add(Errors.allValuesFromViolation(subject, ontologyrelation,requiredTarget));
}
}
}
}
}
private List<String> getClassesOfTarget(String target, Context context) throws ServerException {
List<String> classes = new ArrayList<String>();
if (!target.startsWith("info:fedora/")) {
return new ArrayList<String>();
} else {
target = target.substring("info:fedora/".length());
}
int lastIndexOfSlash = target.lastIndexOf("/");
String targetPid;
String dsname = "";
if (lastIndexOfSlash > 0) {//target is a datastream
targetPid = target.substring(0, lastIndexOfSlash);
dsname = "datastreams/"+target.substring(lastIndexOfSlash+1)+"/";
} else { //target is an object
targetPid = target;
}
List<String> targetContentModels;
DOReader targetReader = doMgr.getReader(false, context, targetPid);
targetContentModels = targetReader.getContentModels();
for (String targetContentModel : targetContentModels) {
targetContentModel = targetContentModel+"#" +dsname+"class";
classes.add(targetContentModel);
}
return classes;
}
private void checkSomeValuesFrom(String subject, Set<RelationshipTuple> relations,
RestrictionVisitor restrictionVisitor, Validation validation, Context context)
throws ServerException {
Map<OWLObjectProperty, OWLClass> map = restrictionVisitor.getSomeValuesFrom();
for (OWLObjectProperty owlObjectProperty : map.keySet()) {
String ontologyrelation = owlObjectProperty.getIRI().toString();
OWLClass requiredclass = map.get(owlObjectProperty);
String requiredTarget = requiredclass.getIRI().toString();
int count = countRelations(ontologyrelation, relations);
if (count < 1) {
validation.setValid(false);
validation.getObjectProblems().add(Errors.someValuesFromViolationNoSuchRelation(subject, ontologyrelation,requiredTarget));
continue;
}
boolean found = false;
for (RelationshipTuple relation : relations) {
String objectRelationName = relation.predicate;
if (objectRelationName.equals(ontologyrelation)) {//This is one of the restricted relations
String target = relation.object;
List<String> classes;
try {
classes = getClassesOfTarget(target, context);
} catch (ServerException e){
//object not found. So, continue to next
continue;
}
for (String aClass : classes) {
if (aClass.equals(requiredclass.getIRI().toString())){
found = true;
break;
}
}
} else {
continue;
}
}
if (!found) {
validation.setValid(false);
validation.getObjectProblems().add(
Errors.someValuesFromViolationWrongClassOfTarget(subject,
ontologyrelation,
requiredTarget));
}
}
}
private void checkMinCardinality(String subject, Set<RelationshipTuple> relations,
RestrictionVisitor restrictionVisitor, Validation validation) {
Map<OWLObjectProperty, Integer> map = restrictionVisitor.getMinCardinality();
for (OWLObjectProperty owlObjectProperty : map.keySet()) {
String ontologyrelation = owlObjectProperty.getIRI().toString();
int count = countRelations(ontologyrelation, relations);
int min = map.get(owlObjectProperty);
if (count < min) {
validation.setValid(false);
validation.getObjectProblems()
.add(Errors.minCardinalityViolation(subject, ontologyrelation,min));
}
}
}
private void checkMaxCardinality(String subject, Set<RelationshipTuple> relations,
RestrictionVisitor restrictionVisitor, Validation validation) {
Map<OWLObjectProperty, Integer> map = restrictionVisitor.getMaxCardinality();
for (OWLObjectProperty owlObjectProperty : map.keySet()) {
String ontologyrelation = owlObjectProperty.getIRI().toString();
int count = countRelations(ontologyrelation, relations);
int max = map.get(owlObjectProperty);
if (count > max) {
validation.setValid(false);
validation.getObjectProblems()
.add(Errors.maxCardinalityViolation(subject, ontologyrelation,max));
}
}
}
private void checkExactCardinality(String subject, Set<RelationshipTuple> relations,
RestrictionVisitor restrictionVisitor, Validation validation) {
Map<OWLObjectProperty, Integer> map = restrictionVisitor.getCardinality();
for (OWLObjectProperty owlObjectProperty : map.keySet()) {
String ontologyrelation = owlObjectProperty.getIRI().toString();
int count;
count = countRelations(ontologyrelation, relations);
Integer exact = map.get(owlObjectProperty);
if (count != exact) {
validation.setValid(false);
validation.getObjectProblems()
.add(Errors.exactCardinalityViolation(subject, ontologyrelation,exact));
}
}
}
/**
* Private utility method. Counts the number of relations with a given name in a list of relatiosn
*
* @param relationName the relation name
* @param objectRelations the list of relations
* @return the number of relations with relationName in the list
*/
private int countRelations(String relationName, Set<RelationshipTuple> objectRelations) {
int count = 0;
if (objectRelations == null) {
return 0;
}
for (RelationshipTuple objectRelation : objectRelations) {
if (objectRelation.predicate.equals(relationName)) {//This is one of the restricted relations
count++;
}
}
return count;
}
private IRI toIRI(String contentmodel, String datastreamName) {
if (!contentmodel.startsWith("info:fedora/")) {
contentmodel = "info:fedora/" + contentmodel;
}
if (datastreamName != null) {
datastreamName = "datastreams/" + datastreamName+"/";
} else {
datastreamName = "";
}
return IRI.create(contentmodel + "#"+datastreamName+"class");
}
private List<String> getDatastreamNames(Context context, String contentmodel, Date asOfDateTime)
throws ServerException {
ArrayList<String> names = new ArrayList<String>();
if (contentmodel.startsWith("info:fedora/")) {
contentmodel = contentmodel.substring("info:fedora/".length());
}
DOReader reader = doMgr.getReader(false, context, contentmodel);
Datastream dscompmodelDS = reader.GetDatastream("DS-COMPOSITE-MODEL", asOfDateTime);
if (dscompmodelDS == null) {//NO ds composite model, thats okay, continue to next content model
return names;
}
DsCompositeModel dscompobject = JAXB.unmarshal(dscompmodelDS.getContentStream(context), DsCompositeModel.class);
for (DsTypeModel typeModel : dscompobject.getDsTypeModel()) {
names.add(typeModel.getID());
}
return names;
}
/**
* Visits restrictions and collects the properties which are restricted
*/
private static class RestrictionVisitor extends OWLClassExpressionVisitorAdapter {
private boolean processInherited = true;
private Set<OWLClass> processedClasses;
private Map<OWLObjectProperty, OWLClass> someValuesFrom;
private Map<OWLObjectProperty, OWLClass> allValuesFrom;
private Map<OWLObjectProperty, Integer> minCardinality;
private Map<OWLObjectProperty, Integer> cardinality;
private Map<OWLObjectProperty, Integer> maxCardinality;
private Set<OWLOntology> onts;
public RestrictionVisitor(Set<OWLOntology> onts) {
someValuesFrom = new HashMap<OWLObjectProperty, OWLClass>();
allValuesFrom = new HashMap<OWLObjectProperty, OWLClass>();
minCardinality = new HashMap<OWLObjectProperty, Integer>();
cardinality = new HashMap<OWLObjectProperty, Integer>();
maxCardinality = new HashMap<OWLObjectProperty, Integer>();
processedClasses = new HashSet<OWLClass>();
this.onts = onts;
}
public void setProcessInherited(boolean processInherited) {
this.processInherited = processInherited;
}
public void visit(OWLClass desc) {
if (processInherited && !processedClasses.contains(desc)) {
// If we are processing inherited restrictions then
// we recursively visit named supers. Note that we
// need to keep track of the classes that we have processed
// so that we don't get caught out by cycles in the taxonomy
processedClasses.add(desc);
for (OWLOntology ont : onts) {
for (OWLSubClassOfAxiom ax : ont.getSubClassAxiomsForSubClass(desc)) {
ax.getSuperClass().accept(this);
}
}
}
}
public void reset() {
processedClasses.clear();
someValuesFrom.clear();
allValuesFrom.clear();
minCardinality.clear();
cardinality.clear();
maxCardinality.clear();
}
public void visit(OWLObjectExactCardinality desc) {
cardinality.put(desc.getProperty().asOWLObjectProperty(), desc.getCardinality());
}
public void visit(OWLObjectMaxCardinality desc) {
maxCardinality.put(desc.getProperty().asOWLObjectProperty(), desc.getCardinality());
}
public void visit(OWLObjectMinCardinality desc) {
minCardinality.put(desc.getProperty().asOWLObjectProperty(), desc.getCardinality());
}
public void visit(OWLObjectAllValuesFrom desc) {
allValuesFrom.put(desc.getProperty().asOWLObjectProperty(), desc.getFiller().asOWLClass());
}
public void visit(OWLObjectSomeValuesFrom desc) {
// This method gets called when a class expression is an
// existential (someValuesFrom) restriction and it asks us to visit it
someValuesFrom.put(desc.getProperty().asOWLObjectProperty(), desc.getFiller().asOWLClass());
}
public void visit(OWLDataSomeValuesFrom desc) {
}
public void visit(OWLDataAllValuesFrom desc) {
}
public void visit(OWLDataMinCardinality desc) {
}
public void visit(OWLDataExactCardinality desc) {
}
public void visit(OWLDataMaxCardinality desc) {
}
public Map<OWLObjectProperty, OWLClass> getSomeValuesFrom() {
return someValuesFrom;
}
public Map<OWLObjectProperty, OWLClass> getAllValuesFrom() {
return allValuesFrom;
}
public Map<OWLObjectProperty, Integer> getMinCardinality() {
return minCardinality;
}
public Map<OWLObjectProperty, Integer> getCardinality() {
return cardinality;
}
public Map<OWLObjectProperty, Integer> getMaxCardinality() {
return maxCardinality;
}
}
}