blob: be4e5af3bde2e09102cc4628e4392c7f298910e3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.stanbol.ontologymanager.multiplexer.clerezza.impl;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.clerezza.commons.rdf.ImmutableGraph;
import org.apache.clerezza.commons.rdf.Graph;
import org.apache.clerezza.commons.rdf.BlankNodeOrIRI;
import org.apache.clerezza.commons.rdf.RDFTerm;
import org.apache.clerezza.commons.rdf.Triple;
import org.apache.clerezza.commons.rdf.Graph;
import org.apache.clerezza.commons.rdf.IRI;
import org.apache.clerezza.commons.rdf.impl.utils.simple.SimpleGraph;
import org.apache.clerezza.commons.rdf.impl.utils.TripleImpl;
import org.apache.clerezza.rdf.ontologies.OWL;
import org.apache.clerezza.rdf.ontologies.RDF;
import org.apache.stanbol.ontologymanager.servicesapi.collector.OntologyCollector;
import org.apache.stanbol.ontologymanager.servicesapi.collector.OntologyCollectorListener;
import org.apache.stanbol.ontologymanager.servicesapi.collector.UnmodifiableOntologyCollectorException;
import org.apache.stanbol.ontologymanager.servicesapi.io.OntologyInputSource;
import org.apache.stanbol.ontologymanager.servicesapi.scope.OntologySpace;
import org.apache.stanbol.ontologymanager.servicesapi.scope.OntologySpace.SpaceType;
import org.apache.stanbol.ontologymanager.servicesapi.scope.OntologySpaceFactory;
import org.apache.stanbol.ontologymanager.servicesapi.scope.Scope;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.AddImport;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyID;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLOntologySetProvider;
import org.semanticweb.owlapi.util.OWLOntologyMerger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The default implementation of an ontology scope.
*
* @author alexdma
*
*/
public class ScopeImpl implements Scope, OntologyCollectorListener {
/**
* The core ontology space for this scope, always set as default.
*/
protected OntologySpace coreSpace;
/**
* The custom ontology space for this scope. This is optional, but cannot be set after the scope has been
* setup.
*/
protected OntologySpace customSpace;
/**
* The unique identifier for this scope.
*/
protected String id = null;
private Set<OntologyCollectorListener> listeners = new HashSet<OntologyCollectorListener>();
/**
* An ontology scope knows whether it's write-locked or not. Initially it is not.
*/
protected volatile boolean locked = false;
private Logger log = LoggerFactory.getLogger(getClass());
protected org.semanticweb.owlapi.model.IRI namespace = null;
public ScopeImpl(String id,
org.semanticweb.owlapi.model.IRI namespace,
OntologySpaceFactory factory,
OntologyInputSource<?>... coreOntologies) {
setID(id);
setDefaultNamespace(namespace);
configureCoreSpace(factory);
for (OntologyInputSource<?> src : coreOntologies)
this.coreSpace.addOntology(src);
// let's just lock it. Once the core space is done it's done.
this.coreSpace.setUp();
configureCustomSpace(factory);
}
@Override
public void addOntologyCollectorListener(OntologyCollectorListener listener) {
listeners.add(listener);
}
@Override
public void clearOntologyCollectorListeners() {
listeners.clear();
}
private void configureCoreSpace(OntologySpaceFactory factory) {
this.coreSpace = factory.createCoreOntologySpace(id/* , coreOntologies */);
this.coreSpace.addOntologyCollectorListener(this); // Set listener before adding core ontologies
}
private void configureCustomSpace(OntologySpaceFactory factory) {
try {
setCustomSpace(factory.createCustomOntologySpace(id));
} catch (UnmodifiableOntologyCollectorException e) {
// Cannot happen unless the factory or space implementations are really naughty.
log.warn(
"Ontology scope "
+ id
+ " was denied creation of its own custom space upon initialization! This should not happen.",
e);
}
this.customSpace.addOntologyCollectorListener(this);
}
@Override
public boolean equals(Object arg0) {
if (arg0 == null) return false;
if (!(arg0 instanceof Scope)) return false;
Scope sc = (Scope) arg0;
// return this.getID().equals(sc.getID()) && this.getDefaultNamespace().equals(sc.getDefaultNamespace())
// && this.getCoreSpace().equals(sc.getCoreSpace())
// && this.getCustomSpace().equals(sc.getCustomSpace());
if (!this.getID().equals(sc.getID())) return false;
if (!this.getDefaultNamespace().equals(sc.getDefaultNamespace())) return false;
if (!this.getCoreSpace().equals(sc.getCoreSpace())) return false;
if (!this.getCustomSpace().equals(sc.getCustomSpace())) return false;
return true;
}
@Override
public <O> O export(Class<O> returnType, boolean merge) {
return export(returnType, merge, getDefaultNamespace());
}
@SuppressWarnings("unchecked")
@Override
public <O> O export(Class<O> returnType, boolean merge, org.semanticweb.owlapi.model.IRI universalPrefix) {
if (OWLOntology.class.isAssignableFrom(returnType)) {
return (O) exportToOWLOntology(merge, universalPrefix);
}
if (Graph.class.isAssignableFrom(returnType)) {
Graph root = exportToGraph(merge, universalPrefix);
// A Clerezza graph has to be cast properly.
if (returnType == ImmutableGraph.class) root = ((Graph) root).getImmutableGraph();
else if (returnType == Graph.class) {}
return (O) root;
}
throw new UnsupportedOperationException("Cannot export scope " + getID() + " to a " + returnType);
}
/**
* Get a Clerezza {@link Graph} representation of the scope.
*
* @param merge
* if true the core and custom spaces will be recursively merged with the scope graph,
* otherwise owl:imports statements will be added.
* @return the RDF representation of the scope as a modifiable graph.
*/
protected Graph exportToGraph(boolean merge, org.semanticweb.owlapi.model.IRI universalPrefix) {
// No need to store, give it a name, or anything.
Graph root = new SimpleGraph();
IRI iri = new IRI(universalPrefix + getID());
if (root != null) {
// Set the ontology ID
root.add(new TripleImpl(iri, RDF.type, OWL.Ontology));
if (merge) {
ImmutableGraph custom, core;
// Get the subjects of "bad" triples (those with subjects of type owl:Ontology).
Iterator<Triple> it;
Set<BlankNodeOrIRI> ontologies = new HashSet<BlankNodeOrIRI>();
Set<RDFTerm> importTargets = new HashSet<RDFTerm>();
custom = this.getCustomSpace().export(ImmutableGraph.class, merge);
// root.addAll(space);
it = custom.filter(null, RDF.type, OWL.Ontology);
while (it.hasNext())
ontologies.add(it.next().getSubject());
it = custom.filter(null, OWL.imports, null);
while (it.hasNext())
importTargets.add(it.next().getObject());
core = this.getCoreSpace().export(ImmutableGraph.class, merge);
// root.addAll(space);
it = core.filter(null, RDF.type, OWL.Ontology);
while (it.hasNext())
ontologies.add(it.next().getSubject());
it = core.filter(null, OWL.imports, null);
while (it.hasNext())
importTargets.add(it.next().getObject());
// Make sure the scope itself is not in the "bad" subjects.
ontologies.remove(iri);
for (BlankNodeOrIRI nl : ontologies)
log.debug("{} -related triples will not be added to {}", nl, iri);
// Merge the two spaces, skipping the "bad" triples.
log.debug("Merging custom space of {}.", getID());
for (Triple t : custom)
if (!ontologies.contains(t.getSubject())) root.add(t);
log.debug("Merging core space of {}.", getID());
for (Triple t : core)
if (!ontologies.contains(t.getSubject())) root.add(t);
/*
* Reinstate import statements, though. If imported ontologies were not merged earlier, we are
* not doing it now anyway.
*/
for (RDFTerm target : importTargets)
root.add(new TripleImpl(iri, OWL.imports, target));
} else {
IRI physIRI = new IRI(universalPrefix.toString() + this.getID() + "/"
+ SpaceType.CUSTOM.getIRISuffix());
root.add(new TripleImpl(iri, OWL.imports, physIRI));
physIRI = new IRI(universalPrefix.toString() + this.getID() + "/"
+ SpaceType.CORE.getIRISuffix());
root.add(new TripleImpl(iri, OWL.imports, physIRI));
}
}
return root;
}
/**
* Get an OWL API {@link OWLOntology} representation of the scope.
*
* @param merge
* if true the core and custom spaces will be recursively merged with the scope ontology,
* otherwise owl:imports statements will be added.
* @return the OWL representation of the scope.
*/
protected OWLOntology exportToOWLOntology(boolean merge, org.semanticweb.owlapi.model.IRI universalPrefix) {
// if (merge) throw new UnsupportedOperationException(
// "Ontology merging only implemented for managed ontologies, not for collectors. "
// + "Please set merge parameter to false.");
// Create an ontology manager on the fly. We don't really need a permanent one.
OWLOntologyManager mgr = OWLManager.createOWLOntologyManager();
OWLDataFactory df = mgr.getOWLDataFactory();
OWLOntology ont = null;
try {
if (merge) {
final Set<OWLOntology> set = new HashSet<OWLOntology>();
log.debug("Merging custom space of {}.", getID());
set.add(this.getCustomSpace().export(OWLOntology.class, merge));
log.debug("Merging core space of {}.", getID());
set.add(this.getCoreSpace().export(OWLOntology.class, merge));
OWLOntologySetProvider provider = new OWLOntologySetProvider() {
@Override
public Set<OWLOntology> getOntologies() {
return set;
}
};
OWLOntologyMerger merger = new OWLOntologyMerger(provider);
try {
ont = merger.createMergedOntology(OWLManager.createOWLOntologyManager(),
org.semanticweb.owlapi.model.IRI.create(getDefaultNamespace() + getID()));
} catch (OWLOntologyCreationException e) {
log.error("Failed to merge imports for ontology.", e);
ont = null;
}
} else {
// The root ontology ID is in the form [namespace][scopeId]
ont = mgr.createOntology(org.semanticweb.owlapi.model.IRI.create(universalPrefix + getID()));
List<OWLOntologyChange> additions = new LinkedList<OWLOntologyChange>();
// Add the import statement for the custom space, if existing and not empty
OntologySpace spc = getCustomSpace();
if (spc != null && spc.listManagedOntologies().size() > 0) {
org.semanticweb.owlapi.model.IRI spaceIri = org.semanticweb.owlapi.model.IRI.create(universalPrefix + spc.getID());
additions.add(new AddImport(ont, df.getOWLImportsDeclaration(spaceIri)));
}
// Add the import statement for the core space, if existing and not empty
spc = getCoreSpace();
if (spc != null && spc.listManagedOntologies().size() > 0) {
org.semanticweb.owlapi.model.IRI spaceIri = org.semanticweb.owlapi.model.IRI.create(universalPrefix + spc.getID());
additions.add(new AddImport(ont, df.getOWLImportsDeclaration(spaceIri)));
}
mgr.applyChanges(additions);
}
} catch (OWLOntologyCreationException e) {
log.error("Failed to generate an OWL form of scope " + getID(), e);
ont = null;
}
return ont;
}
protected void fireOntologyAdded(OntologySpace space, OWLOntologyID addedOntology) {
for (OntologyCollectorListener listener : listeners)
listener.onOntologyAdded(space, addedOntology);
}
protected void fireOntologyRemoved(OntologySpace space, OWLOntologyID removedOntology) {
for (OntologyCollectorListener listener : listeners)
listener.onOntologyRemoved(space, removedOntology);
}
@Override
public ConnectivityPolicy getConnectivityPolicy() {
return ConnectivityPolicy.LOOSE;
}
@Override
public OntologySpace getCoreSpace() {
return coreSpace;
}
@Override
public OntologySpace getCustomSpace() {
return customSpace;
}
@Override
public org.semanticweb.owlapi.model.IRI getDefaultNamespace() {
return this.namespace;
}
@Override
public String getID() {
return id;
}
@Override
public org.semanticweb.owlapi.model.IRI getNamespace() {
return getDefaultNamespace();
}
@Override
public Collection<OntologyCollectorListener> getOntologyCollectorListeners() {
return listeners;
}
@Override
public boolean isLocked() {
return locked;
}
@Override
public void onOntologyAdded(OntologyCollector collector, OWLOntologyID addedOntology) {
// Propagate events to scope listeners
if (collector instanceof OntologySpace) fireOntologyAdded((OntologySpace) collector, addedOntology);
}
@Override
public void onOntologyRemoved(OntologyCollector collector, OWLOntologyID removedOntology) {
// Propagate events to scope listeners
if (collector instanceof OntologySpace) fireOntologyRemoved((OntologySpace) collector,
removedOntology);
}
@Override
public void removeOntologyCollectorListener(OntologyCollectorListener listener) {
listeners.remove(listener);
}
@Override
public void setConnectivityPolicy(ConnectivityPolicy policy) {
throw new UnsupportedOperationException(
"Cannot set connectivity policy on scopes. Ontology scopes only allow LOOSE connectivity policy (set by default).");
}
@Override
public synchronized void setCustomSpace(OntologySpace customSpace) throws UnmodifiableOntologyCollectorException {
if (this.customSpace != null && this.customSpace.isLocked()) throw new UnmodifiableOntologyCollectorException(
getCustomSpace());
this.customSpace = customSpace;
this.customSpace.addOntologyCollectorListener(this);
}
/**
* @param namespace
* The OntoNet namespace that will prefix the scope ID in Web references. This implementation
* only allows non-null and non-empty IRIs, with no query or fragment. Hash URIs are not
* allowed, slash URIs are preferred. If neither, a slash will be concatenated and a warning
* will be logged.
*/
@Override
public void setDefaultNamespace(org.semanticweb.owlapi.model.IRI namespace) {
if (namespace == null) throw new IllegalArgumentException("Namespace cannot be null.");
if (namespace.toURI().getQuery() != null) throw new IllegalArgumentException(
"URI Query is not allowed in OntoNet namespaces.");
if (namespace.toURI().getFragment() != null) throw new IllegalArgumentException(
"URI Fragment is not allowed in OntoNet namespaces.");
if (namespace.toString().endsWith("#")) throw new IllegalArgumentException(
"OntoNet namespaces must not end with a hash ('#') character.");
if (!namespace.toString().endsWith("/")) {
log.warn("Namespace {} does not end with slash character ('/'). It will be added automatically.",
namespace);
namespace = org.semanticweb.owlapi.model.IRI.create(namespace + "/");
}
this.namespace = namespace;
}
protected void setID(String id) {
if (id == null) throw new IllegalArgumentException("Scope ID cannot be null.");
id = id.trim();
if (id.isEmpty()) throw new IllegalArgumentException("Scope ID cannot be empty.");
if (!id.matches("[\\w-\\.]+")) throw new IllegalArgumentException(
"Illegal scope ID " + id
+ " - Must be an alphanumeric sequence, with optional underscores, dots or dashes.");
this.id = id;
}
@Override
public void setNamespace(org.semanticweb.owlapi.model.IRI namespace) {
setDefaultNamespace(namespace);
}
@Override
public synchronized void setUp() {
if (locked || (customSpace != null && !customSpace.isLocked())) return;
this.coreSpace.addOntologyCollectorListener(this);
this.coreSpace.setUp();
if (this.customSpace != null) {
this.customSpace.addOntologyCollectorListener(this);
this.customSpace.setUp();
}
locked = true;
}
@Override
public synchronized void tearDown() {
// this.coreSpace.addOntologySpaceListener(this);
this.coreSpace.tearDown();
if (this.customSpace != null) {
// this.customSpace.addOntologySpaceListener(this);
this.customSpace.tearDown();
}
locked = false;
}
@Override
public String toString() {
return getDefaultNamespace() + getID();
}
}