/*******************************************************************************
* Copyright 2012 Pearson Education
*
* 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 org.semantictools.jsonld.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.semantictools.jsonld.LdAsset;
import org.semantictools.jsonld.LdAssetManager;
import org.semantictools.jsonld.LdClass;
import org.semantictools.jsonld.LdContext;
import org.semantictools.jsonld.LdContextEnhancer;
import org.semantictools.jsonld.LdProperty;
import org.semantictools.jsonld.LdQualifiedRestriction;
import org.semantictools.jsonld.LdRestriction;
import org.semantictools.jsonld.LdTerm;
import org.semantictools.jsonld.LdType;
import org.semantictools.jsonld.io.LdDatatypeReader;
import com.hp.hpl.jena.ontology.AllValuesFromRestriction;
import com.hp.hpl.jena.ontology.MaxCardinalityRestriction;
import com.hp.hpl.jena.ontology.MinCardinalityRestriction;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.ontology.Restriction;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFList;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.OWL2;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.jena.vocabulary.XSD;
public class LdContextEnhancerImpl implements LdContextEnhancer {
private LdAssetManager assetManager;
public LdContextEnhancerImpl(LdAssetManager assetManager) {
this.assetManager = assetManager;
}
@Override
public void enhance(LdContext context) throws LdContextEnhanceException {
Delegate delegate = new Delegate();
delegate.enhance(context);
}
private boolean isStandardNamespace(String namespaceURI) {
return
OWL.NS.equals(namespaceURI) ||
RDF.getURI().equals(namespaceURI) ||
XSD.getURI().equals(namespaceURI) ||
RDFS.getURI().equals(namespaceURI);
}
/**
* A delegate that actually performs the work of enhancement.
* This delegate is not threadsafe. Thus, the parent class
* creates a new Delegate for each request.
*
* @author Greg McFall
*
*/
class Delegate {
private Set<String> namespaceSet;
private List<LdAsset> datatypeList;
private Map<String, LdTerm> uri2Term;
private OntModel ontModel;
private StringBuilder errorBuilder;
private LdContext context;
private List<LdProperty> functionalPropertyList;
public void enhance(LdContext context) throws LdContextEnhanceException {
try {
this.context = context;
errorBuilder = new StringBuilder();
collectNamespaces(context);
if (uri2Term == null) return;
// buildOntModel();
computeTermTypes();
buildDatatypes();
buildRestrictions();
buildHierarchy();
addFunctionalPropertyRestrictions();
// if (errorBuilder.length()>0) {
// throw new LdContextEnhanceException(errorBuilder.toString());
// }
} finally {
release();
}
}
private void addFunctionalPropertyRestrictions() {
if (functionalPropertyList != null) {
for (LdProperty p : functionalPropertyList) {
addFunctionalPropertyRestrictions(p);
}
}
}
private void addFunctionalPropertyRestrictions(LdProperty p) {
String propertyURI = p.getURI();
List<String> domainList = p.getDomain();
if (domainList != null) {
for (String domainURI : domainList) {
LdClass rdfClass = context.getClass(domainURI);
if (rdfClass == null) continue;
LdRestriction r = rdfClass.findRestrictionByPropertyURI(propertyURI);
if (r == null) {
r = new LdRestriction();
r.setPropertyURI(propertyURI);
rdfClass.add(r);
}
// r.setMaxCardinality(1);
}
}
}
private void buildDatatypes() {
if (datatypeList != null) {
LdDatatypeReader reader = new LdDatatypeReader(context);
for (LdAsset ns : datatypeList) {
try {
reader.read(ns.getReader());
} catch (Exception e) {
// TODO: add an error to LdValidationReport.
}
}
}
}
private void computeTermTypes() {
List<LdTerm> termList = new ArrayList<LdTerm>(context.listTerms());
for (LdTerm term : termList) {
computeType(term);
}
}
private void computeType(LdTerm term) {
String propertyURI = term.getIRI();
OntResource resource = ontModel.getOntResource(propertyURI);
if (resource != null && resource.isProperty()) {
OntProperty property = resource.asProperty();
if (term.getTypeIRI() == null) {
String rangeURI = null;
OntResource range = property.getRange();
if (range == null) {
rangeURI = OWL.Thing.getURI();
} else {
rangeURI = range.getURI();
}
term.setTypeIRI(rangeURI);
}
String typeIRI = term.getTypeIRI();
if (typeIRI != null) {
// Ensure that a term exists for this datatype.
// This term will be populated when the XSD schemas are parsed.
context.ensureTerm(typeIRI);
}
buildDomain(term, property);
}
}
private void buildDomain(LdTerm term, OntProperty property) {
List<? extends OntResource> domainList = property.listDomain().toList();
for (OntResource resource : domainList) {
if (resource.isClass()) {
addDomain(property, term, resource.asClass());
}
}
}
private void addDomain(OntProperty ontProperty, LdTerm term, OntClass type) {
String uri = type.getURI();
if (uri != null) {
LdProperty property = term.ensureProperty();
property.addDomain(uri);
if (ontProperty.isFunctionalProperty()) {
addFunctionalProperty(property);
}
}
if (type.isUnionClass()) {
try {
// List<? extends OntClass> unionList = type.asUnionClass().listOperands().toList();
List<? extends OntClass> unionList = listUnion(type);
for (OntClass op : unionList) {
addDomain(ontProperty, term, op);
}
} catch (Throwable oops) {
System.out.println("Property: " + ontProperty.getURI());
}
}
}
private List<OntClass> listUnion(OntClass type) {
List<OntClass> list = new ArrayList<OntClass>();
RDFNode node = type.getPropertyValue(OWL.unionOf);
if (node !=null && node.canAs(RDFList.class)) {
RDFList rdfList = node.as(RDFList.class);
List<RDFNode> nodeList = rdfList.asJavaList();
for (RDFNode element : nodeList) {
if (element.canAs(OntClass.class)) {
list.add(element.as(OntClass.class));
} else {
if (element.isURIResource()) {
ontModel.add(element.asResource(), RDF.type, OWL.Class);
list.add(element.as(OntClass.class));
}
}
}
}
return list;
}
private void buildHierarchy() {
HashSet<String> memory = new HashSet<String>();
List<LdClass> drList = context.listClasses();
for (LdClass dr : drList) {
OntClass ontClass = ontModel.getOntClass(dr.getURI());
buildHierarchy(memory, ontClass, dr);
}
}
private void buildHierarchy(Set<String> memory, OntClass ontClass, LdClass ldClass) {
String uri = ldClass.getURI();
if (memory.contains(uri)) return;
memory.add(uri);
buildSubclasses(memory, ontClass, ldClass);
buildSuperclasses(memory, ontClass, ldClass);
}
private void buildSuperclasses(Set<String> memory, OntClass ontClass, LdClass subClass) {
List<OntClass> superList = ontClass.listSuperClasses(true).toList();
for (OntClass ontSuperClass : superList) {
if (ontSuperClass.equals(RDFS.Resource)) continue;
addSuperclass(memory, subClass, ontSuperClass);
}
}
private void addSuperclass(Set<String> memory, LdClass subClass, OntClass ontSuperClass) {
String superURI = ontSuperClass.getURI();
if (superURI == null) return;
LdTerm term = context.ensureTerm(superURI);
LdClass superClass = term.getRdfClass();
if (superClass == null) {
superClass = new LdClass(superURI);
term.setRdfClass(superClass);
}
subClass.addSupertype(superClass);
buildHierarchy(memory, ontModel.getOntClass(superURI), superClass);
}
private void buildSubclasses(Set<String> memory, OntClass ontClass, LdClass superClass) {
List<OntClass> sublist = ontClass.listSubClasses(true).toList();
for (OntClass ontSubdomain : sublist) {
addSubclass(memory, superClass, ontSubdomain);
}
}
private void addSubclass(Set<String> memory, LdClass superDomain, OntClass ontSubdomain) {
String subdomainURI = ontSubdomain.getURI();
if (subdomainURI == null) return;
LdTerm term = context.ensureTerm(subdomainURI);
LdClass subDomain = term.getRdfClass();
if (subDomain == null) {
subDomain = new LdClass(subdomainURI);
term.setRdfClass(subDomain);
}
subDomain.addSupertype(superDomain);
buildHierarchy(memory, ontModel.getOntClass(subdomainURI), subDomain);
}
/**
* Release internal data structures that were used during enhancement so that
* they may be reclaimed by the garbage collector.
*/
private void release() {
ontModel = null;
namespaceSet = null;
uri2Term = null;
errorBuilder = null;
context = null;
functionalPropertyList = null;
}
private void buildRestrictions() {
List<OntClass> classList = ontModel.listClasses().toList();
for (OntClass domain : classList) {
String uri = domain.getURI();
LdTerm term = context.getTerm(uri);
if (term == null) continue;
buildRestrictionsForDomain(domain);
}
}
private void addFunctionalProperty(LdProperty property) {
if (functionalPropertyList == null) {
functionalPropertyList = new ArrayList<LdProperty>();
}
functionalPropertyList.add(property);
}
private void buildRestrictionsForDomain(OntClass domain) {
String uri = domain.getURI();
if (uri == null) return;
LdClass rdfClass = context.ensureClass(uri);
List<OntClass> superList = domain.listSuperClasses(true).toList();
for (OntClass superType : superList) {
if (superType.canAs(Restriction.class)) {
buildRestriction(rdfClass, superType.asRestriction());
}
}
}
private void buildRestriction(LdClass rdfClass, Restriction restriction) {
try {
// String propertyURI = restriction.getOnProperty().getURI();
String propertyURI = getOnPropertyURI(restriction);
// We are only interested in restrictions on properties that
// are declared in the JSON-LD context. Bail out if a term
// for the specified property is not found.
//
LdTerm propertyTerm = getTerm(propertyURI);
if (propertyTerm == null) return;
Integer minCardinality = minCardinality(restriction);
Integer maxCardinality = maxCardinality(restriction);
String rangeURI = onClass(restriction);
String allValuesFrom = allValuesFrom(restriction);
LdRestriction r = new LdRestriction();
r.setDomain(rdfClass);
r.setPropertyURI(propertyURI);
r.setMaxCardinality(maxCardinality);
r.setMinCardinality(minCardinality);
r.setAllValuesFrom(allValuesFrom);
if (rangeURI != null) {
addQualifiedRestriction(restriction, r, rangeURI);
}
rdfClass.add(r);
} catch (RuntimeException oops) {
throw oops;
}
}
private String allValuesFrom(Restriction restriction) {
StmtIterator sequence = restriction.listProperties(OWL.allValuesFrom);
while (sequence.hasNext()) {
Statement s = sequence.next();
RDFNode object = s.getObject();
if (object.canAs(Resource.class)) {
Resource resource = object.asResource();
String uri = resource.getURI();
if (uri != null) {
return uri;
}
}
}
return null;
}
private String getOnPropertyURI(Restriction restriction) {
RDFNode node = restriction.getPropertyValue(OWL.onProperty);
return node.isResource() ? node.asResource().getURI() : null;
}
private void addQualifiedRestriction(Restriction restriction, LdRestriction r, String rangeURI) {
LdQualifiedRestriction q = new LdQualifiedRestriction();
q.setRangeURI(rangeURI);
q.setMinCardinality(minQualifiedCardinality(restriction));
q.setMaxCardinality(maxQualifiedCardinality(restriction));
r.add(q);
}
private Integer maxQualifiedCardinality(Restriction restriction) {
RDFNode node = restriction.getPropertyValue(OWL2.minQualifiedCardinality);
if (node==null || !node.canAs(Literal.class)) return null;
return node.asLiteral().getInt();
}
private Integer minQualifiedCardinality(Restriction restriction) {
RDFNode node = restriction.getPropertyValue(OWL2.minQualifiedCardinality);
if (node==null || !node.canAs(Literal.class)) return null;
return node.asLiteral().getInt();
}
private LdTerm getTerm(String propertyURI) {
return uri2Term.get(propertyURI);
}
private String onClass(Restriction restriction) {
Resource onClass = restriction.getPropertyResourceValue(OWL2.onClass);
return onClass==null ? null : onClass.getURI();
}
private Integer maxCardinality(Restriction restriction) {
OntProperty p = restriction.getOnProperty();
if (p.hasRDFType(OWL.FunctionalProperty)) {
return 1;
}
if (!restriction.canAs(MaxCardinalityRestriction.class)) return null;
return restriction.asMaxCardinalityRestriction().getMaxCardinality();
}
private Integer minCardinality(Restriction restriction) {
if (!restriction.canAs(MinCardinalityRestriction.class)) return null;
return restriction.asMinCardinalityRestriction().getMinCardinality();
}
//
// private void buildOntModel() throws LdContextEnhanceException {
// if (ontModel == null) {
// ontModel = ModelFactory.createOntologyModel();
// }
// for (String namespaceURI : namespaceSet) {
// LdAsset ns = assetManager.findAsset(namespaceURI);
// if (ns == null) {
// addError("Namespace not found: " + namespaceURI);
// continue;
// }
// loadNamespace(ns);
// }
//
//
// }
private void loadNamespace(LdAsset ns) throws LdContextEnhanceException {
switch (ns.getFormat()) {
case TURTLE:
try {
ontModel.read(ns.getReader(), null, "TTL");
} catch (IOException e) {
throw new LdContextEnhanceException(e);
}
break;
case XSD :
addDatatype(ns);
break;
}
}
private void addDatatype(LdAsset ns) {
if (datatypeList == null) {
datatypeList = new ArrayList<LdAsset>();
}
datatypeList.add(ns);
}
private void addError(String message) {
errorBuilder.append(message);
errorBuilder.append('\n');
}
/**
* Scan through the given context and collect the set of all namespaces
* that are used in that context.
* @throws LdContextEnhanceException
*/
private void collectNamespaces(LdContext context) throws LdContextEnhanceException {
if (ontModel == null) {
ontModel = ModelFactory.createOntologyModel();
}
if (context == null) return;
if (context.isEnhanced()) return;
List<LdContext> components = context.listComponents();
if (components != null) {
for (LdContext c : components) {
collectNamespaces(c);
}
}
List<LdTerm> termList = context.getTermList();
if (termList != null) {
for (LdTerm term : termList) {
identifyNamespace(term);
}
}
}
private void identifyNamespace(LdTerm term) throws LdContextEnhanceException {
String iri = term.getIRI();
if (isPropertyURI(iri)) {
putTerm(iri, term);
}
String propertyNamespace = getNamespace(iri);
String valueNamespace = getNamespace(term.getTypeIRI());
addNamespace(propertyNamespace);
addNamespace(valueNamespace);
}
/**
* Put the given term into the local hash map of terms that require
* enhancement.
*/
private void putTerm(String iri, LdTerm term) {
if (uri2Term == null) {
uri2Term = new HashMap<String, LdTerm>();
}
uri2Term.put(iri, term);
}
/**
* Returns true if the given URI is the URI for a property (as opposed to the URI for a namespace).
*/
private boolean isPropertyURI(String uri) {
return (uri!=null && !uri.endsWith("#") && !uri.endsWith("/"));
}
private void addNamespace(String namespace) throws LdContextEnhanceException {
if (namespace == null || isStandardNamespace(namespace)) return;
if (namespaceSet == null) {
namespaceSet = new HashSet<String>();
}
if (!namespaceSet.contains(namespace)) {
namespaceSet.add(namespace);
LdAsset ns = assetManager.findAsset(namespace);
if (ns == null) {
addError("Namespace not found: " + namespace);
} else {
loadNamespace(ns);
}
}
}
private String getNamespace(String uri) throws LdContextEnhanceException {
if (uri == null) return null;
if (uri.endsWith("#") || uri.endsWith("/")) {
return uri;
}
int end = uri.lastIndexOf('#');
if (end < 0) {
end = uri.lastIndexOf('/');
}
if (end<0) {
throw new LdContextEnhanceException("Namespace not found for uri: " + uri);
}
return uri.substring(0, end+1);
}
}
}