/*******************************************************************************
* Copyright 2014 Miami-Dade County
*
* 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.sharegov.cirm.rdb;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hypergraphdb.app.owl.util.StopWatch;
import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLDataPropertyAssertionAxiom;
import org.semanticweb.owlapi.model.OWLIndividual;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLMutableOntology;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLNamedObject;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectPropertyAssertionAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.OWLOntologyID;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLPropertyAssertionAxiom;
import org.semanticweb.owlapi.model.OWLPropertyExpression;
import org.semanticweb.owlapi.model.RemoveAxiom;
import org.sharegov.cirm.OWL;
import org.sharegov.cirm.owl.Model;
import org.sharegov.cirm.utils.DBGUtils;
import org.sharegov.cirm.utils.ThreadLocalStopwatch;
import uk.ac.manchester.cs.owl.owlapi.OWLOntologyImpl;
/**
* Transforms an ontology for generic storage optimization purposes.
*
* Currently only hasServiceAnswer AI1 -> hasServiceField, hasAnswerValue | hasAnswerObject is supported.
*
* @author Thomas Hilpold, Abbas Syed
*/
public class OntologyTransformer
{
/**
* Triggers output of axiom numbers before and after transform/inverse transform.
*/
public static boolean DBG = false;
/**
* Triggers full functional printout of original and transformed (save)/inverse transformed (load) ontologies
*/
public static boolean DBGX = false;
/**
* This will print a trace whenever a class assertion is removed during transform (e.g. for a temp0..N individual.)
*/
public static boolean DBGX_CLASS_ASSERTION_REMOVE = true;
public static OWLObjectProperty objectPropertyToCollapse = OWL.objectProperty(Model.legacy("hasServiceAnswer"));
public static OWLObjectProperty objectSubPropertyForOptimizedPredicate = OWL.objectProperty(Model.legacy("hasServiceField"));
public static OWLObjectProperty objectSubPropertyForOptimizedObject = OWL.objectProperty(Model.legacy("hasAnswerObject"));
public static OWLDataProperty dataSubPropertyForOptimizedValue = OWL.dataProperty(Model.legacy("hasAnswerValue"));
public static OWLClass optimizedPredicateClassification = OWL.owlClass(Model.legacy("ServiceField"));
private volatile Set<OWLNamedIndividual> optimizedPredicates;
public OntologyTransformer()
{
initOptimizedPredicates();
}
private synchronized void initOptimizedPredicates()
{
StopWatch s = new StopWatch();
s.start();
System.out.print("Start caching all " + optimizedPredicateClassification);
optimizedPredicates = OWL.reasoner().getInstances(optimizedPredicateClassification, false).getFlattened();
s.stop("Done in ");
System.out.println("Storage optimized predicates: " + optimizedPredicates.size());
}
/**
* Clears the meta dependent predicate cache.
*/
public synchronized void clearPredicateCache()
{
optimizedPredicates = null;
}
/**
* Refreshes the meta dependent predicate cache.
*/
protected synchronized void refreshPredicateCache()
{
if (optimizedPredicates != null)
{
clearPredicateCache();
}
initOptimizedPredicates();
}
public synchronized boolean isOptimizedPredicate(OWLNamedIndividual predicateInd)
{
return optimizedPredicates.contains(predicateInd);
}
/**
* Transforms 3 axioms into 1 for objectPropertyToCollapse.
* indX ax1 indY, indY ax2 indZ1, indY dataPropertySubAx3 litZ2 -> X dataPropertyAxIRIZ1 litZ2
* indX ax1 indY, indY ax2 indZ1, indY objectPropertySubAx3 indZ3 -> X objectPropertyAxIRIZ1 indZ3
*
* This will modify the given ontology directly, without going through the OwlOntologyManager.
* @return an optimized copy of o for storage purposes only.
*/
public OWLOntology transform(OWLOntology o)
{
if (optimizedPredicates == null) refreshPredicateCache();
int oldAxiomNr = o.getAxiomCount();
OWLDataFactory factory = o.getOWLOntologyManager().getOWLDataFactory();
OWLMutableOntology newMo = copyOntology(o);
if (o.getAxiomCount() != oldAxiomNr) throw new IllegalArgumentException("Wrong copy");
// e.g. BO hasServiceAnswer temp0,
Set<OWLObjectPropertyAssertionAxiom> collapsibleOPAxioms = getCollapsibleObjectPropertiesAxioms(o);
for (OWLObjectPropertyAssertionAxiom oax : collapsibleOPAxioms)
{ //TODO hilpold: do NOT use OWL datafactory below, use the one the onto provides.
List<OWLOntologyChange> transformationChanges = new ArrayList<OWLOntologyChange>();
OWLIndividual newSubject = oax.getSubject();
//e.g. serviceAnswer1 hasServiceField serviceQuestion1 -> (serviceAnswer1 hasAnswerObject ind1, serviceAnswer1 hasAnswerObject ind2, ...)
Map<OWLObjectPropertyAssertionAxiom, Set<OWLObjectPropertyAssertionAxiom>> axForNewPredicateToSetofSubObjectAx = getObjectSubPropertyAxiomForOptimizedPredicate(o, oax.getObject());
OWLObjectPropertyAssertionAxiom axForNewPredicate = axForNewPredicateToSetofSubObjectAx.keySet().iterator().next();
IRI newPredicateIRI = axForNewPredicate.getObject().asOWLNamedIndividual().getIRI();
OWLObjectProperty newPredicateObjectProperty = factory.getOWLObjectProperty(newPredicateIRI);
//Create one collapsed Object Property assertion ax per OWLObjectPropertyAssertionAxiom.
Set<OWLObjectPropertyAssertionAxiom> objectSubPropertyAxSet = axForNewPredicateToSetofSubObjectAx.values().iterator().next();
for (OWLObjectPropertyAssertionAxiom oldOPAx: objectSubPropertyAxSet)
{
transformationChanges.add(new AddAxiom(newMo, factory.getOWLObjectPropertyAssertionAxiom(newPredicateObjectProperty, newSubject, oldOPAx.getObject())));
transformationChanges.add(new RemoveAxiom(newMo, oldOPAx));
}
//Create one collapsed Data Property assertion ax per Literal.
Set<OWLDataPropertyAssertionAxiom> dataSubPropertyAxSet = getDataSubPropertyAxiomForOptimizedPredicate(o, oax.getObject());
OWLDataProperty newPredicateDataProperty = factory.getOWLDataProperty(newPredicateIRI);
for (OWLDataPropertyAssertionAxiom oldDPAx: dataSubPropertyAxSet)
{
transformationChanges.add(new AddAxiom(newMo, factory.getOWLDataPropertyAssertionAxiom(newPredicateDataProperty, newSubject, oldDPAx.getObject())));
transformationChanges.add(new RemoveAxiom(newMo, oldDPAx));
}
if (dataSubPropertyAxSet.isEmpty() && objectSubPropertyAxSet.isEmpty())
{
throw new IllegalArgumentException("Cannot collapse " + objectPropertyToCollapse + " for " + axForNewPredicate + " because no subaxioms were found.");
}
transformationChanges.add(new RemoveAxiom(newMo, axForNewPredicate)); //e.g.
transformationChanges.add(new RemoveAxiom(newMo, oax));
//hilpold 2014.04.08 do not let store save unnecessary class assertions for individuals we won't save (temp 0..n)
//Remove class assertions for oax objects (temp0, ..., tempN)
OWLIndividual removableObject = oax.getObject();
for (OWLAxiom removableClassAssertion : newMo.getClassAssertionAxioms(removableObject))
{
transformationChanges.add(new RemoveAxiom(newMo, removableClassAssertion)); //e.g. temp0 isClass ServiceAnswer
if(DBGX_CLASS_ASSERTION_REMOVE)
{
String ontoId = (!o.isAnonymous() && o.getOntologyID() != null)? "" + o.getOntologyID().getOntologyIRI() : "anonymous";
ThreadLocalStopwatch.getWatch().time("OntologyTransformer removed a class assertion: " + removableClassAssertion.toString() + " from " + ontoId);
}
}
//we do that here to make the ontology smaller while we transform.
newMo.applyChanges(transformationChanges);
}
if (DBG)
{
int newAxiomNr = newMo.getAxiomCount();
ThreadLocalStopwatch.getWatch().time("Transforming ontology from " + oldAxiomNr + " to " + newAxiomNr + " (" + (oldAxiomNr - newAxiomNr) + " removed Ax)");
}
if (DBGX)
{
DBGUtils.printOntologyFunctional(o);
DBGUtils.printOntologyFunctional(newMo);
}
if(oldAxiomNr != o.getAxiomCount())
throw new IllegalStateException("Modified original ontology.");
return newMo;
}
/**
* Created a new Ontology with all axioms of the given o and an equal ID.
* The new Axioms will be added without using an owlontologymanager. (Reasoner not notified).
* @param o
* @return an ontology with o's manager.
*/
protected OWLMutableOntology copyOntology(OWLOntology o)
{
String copiedIri = o.isAnonymous() ?
o.getOntologyID().toString() : o.getOntologyID().getOntologyIRI().toString();
copiedIri += "-TRANSFORMED";
IRI copiedOntoIRI = IRI.create(copiedIri);
OWLOntologyID copyOntoID = new OWLOntologyID(copiedOntoIRI);
OWLMutableOntology newMo = new OWLOntologyImpl(o.getOWLOntologyManager(), copyOntoID);
Set<OWLAxiom> oAxioms = o.getAxioms();
List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>();
//Copy all axioms to new mo
for (OWLAxiom ax : oAxioms)
{
changes.add(new AddAxiom(newMo, ax));
}
newMo.applyChanges(changes);
return newMo;
}
/**
* returns all referencing axioms of the objectPropertyToCollapse (e.g. hasServiceAnswer)
* e.g. BO hasServiceAnswer temp0, BO hasServiceAnswer temp1, et.c.
* @param o
* @return
*/
protected Set<OWLObjectPropertyAssertionAxiom> getCollapsibleObjectPropertiesAxioms(OWLOntology o)
{
Set<OWLAxiom> original = o.getReferencingAxioms(objectPropertyToCollapse);
Set<OWLObjectPropertyAssertionAxiom> filtered = new HashSet<OWLObjectPropertyAssertionAxiom>();
for (OWLAxiom ax : original)
{
if (ax instanceof OWLObjectPropertyAssertionAxiom)
{
filtered.add((OWLObjectPropertyAssertionAxiom)ax);
}
}
return filtered;
}
protected Map<OWLObjectPropertyAssertionAxiom, Set<OWLObjectPropertyAssertionAxiom>> getObjectSubPropertyAxiomForOptimizedPredicate(OWLOntology o, OWLIndividual subject)
{
//e.g. 1 serviceAnswerX hasServiceField serviceQuestion AND 0..* serviceAnswerX hasAnswerObject choiceValueYN
OWLObjectPropertyAssertionAxiom objectSubPropertyForOptimizedPredicateAxiom = null;
Set<OWLObjectPropertyAssertionAxiom> subjectAxioms = o.getObjectPropertyAssertionAxioms(subject);
Iterator<OWLObjectPropertyAssertionAxiom> axIt = subjectAxioms.iterator();
//modify subjectAxioms set to only contain objectSubPropertyForOptimizedObject axioms (e.g. hasAnswerObject), if any.
while (axIt.hasNext())
{
OWLObjectPropertyAssertionAxiom curAx = axIt.next();
if (curAx.getProperty().equals(objectSubPropertyForOptimizedPredicate))
{
objectSubPropertyForOptimizedPredicateAxiom = curAx;
axIt.remove();
} else if (!curAx.getProperty().equals(objectSubPropertyForOptimizedObject))
{
axIt.remove();
}
}
if (objectSubPropertyForOptimizedPredicateAxiom == null)
{
throw new IllegalStateException("objectSubPropertyForOptimizedPredicateAxiom not found for subject " + subject);
}
return Collections.singletonMap(objectSubPropertyForOptimizedPredicateAxiom, subjectAxioms);
}
protected Set<OWLDataPropertyAssertionAxiom> getDataSubPropertyAxiomForOptimizedPredicate(OWLOntology o, OWLIndividual subject)
{
//e.g. serviceAnswerX hasAnswerValue literalYN
Set<OWLDataPropertyAssertionAxiom> subjectAxioms = o.getDataPropertyAssertionAxioms(subject);
Iterator<OWLDataPropertyAssertionAxiom> axIt = subjectAxioms.iterator();
//modify subjectAxioms set to only contain objectSubPropertyForOptimizedObject axioms (e.g. hasAnswerObject), if any.
while (axIt.hasNext())
{
OWLDataPropertyAssertionAxiom curAx = axIt.next();
if (!curAx.getProperty().equals(dataSubPropertyForOptimizedValue))
{
axIt.remove();
}
}
return subjectAxioms;
}
// protected OWLNamedIndividual getObjectSubPropertyForOptimizedPredicate(OWLOntology o, OWLIndividual subject)
// {
// Set<OWLIndividual> predicateSet = subject.getObjectPropertyValues(objectSubPropertyForOptimizedPredicate, o);
// if (predicateSet.size() != 1) {
// throw new IllegalStateException("The ontology " + o + "is expected to have exactly one "
// + objectSubPropertyForOptimizedPredicate + " asserted for " + subject + ", was: " + predicateSet.size());
// }
// return (OWLNamedIndividual) predicateSet.iterator().next();
// }
// /**
// * If ObjectProperty (CharMult, CharOpt questions)
// * @param o
// * @param subject
// * @return
// */
// protected Set<OWLNamedIndividual> getAnswerObjectIndividuals(OWLOntology o, OWLIndividual subject)
// {
// Set<OWLIndividual> objectInds = subject.getObjectPropertyValues(objectSubPropertyForOptimizedObject, o);
// Set<OWLNamedIndividual> objectNamedInds = new HashSet<OWLNamedIndividual>(objectInds.size() * 2);
// for (OWLIndividual objectInd : objectInds) {
// if (!(objectInd instanceof OWLNamedIndividual)) {
// throw new IllegalStateException("Found an unexpected anonymous individual. " + objectInd + " for " + objectSubPropertyForOptimizedObject + " and " + subject);
// } else {
// objectNamedInds.add((OWLNamedIndividual) objectInd);
// }
// }
// return objectNamedInds;
// }
/**
* Reversing an optimized ontology. Changes applied throug manager to notify reasoner.
*
* Transforms 1 axiom into 3 for objectPropertyToCollapse.
* X dataPropertyAxZ1 Z2 -> indX ax1 indY ax2 indZ1, indY dataPropertySubAx3 indZ2
* X objectPropertyAxZ1 Z2 -> indX ax1 indY ax2 indZ1, indY objectPropertySubAx3 litZ2
*/
public synchronized void reverseTransform(OWLOntology o)
{
if (DBGX) DBGUtils.printOntologyFunctional(o);
if (optimizedPredicates == null) refreshPredicateCache();
int newAxiomNr = o.getAxiomCount();
int tempIndividualCounter = 0;
List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>();
OWLOntologyManager man = o.getOWLOntologyManager();
OWLDataFactory factory = man.getOWLDataFactory();
OWLObjectProperty objectPropertyToCollapseMem = objectPropertyToCollapse;
OWLObjectProperty objectSubPropertyForOptimizedPredicateMem = objectSubPropertyForOptimizedPredicate;
OWLDataProperty dataSubPropertyForOptimizedValueMem = dataSubPropertyForOptimizedValue;
OWLObjectProperty objectSubPropertyForOptimizedObjectMem = objectSubPropertyForOptimizedObject;
List<OWLPropertyAssertionAxiom<? extends OWLPropertyExpression<?,?>, ?>> propertyAssertionAxioms;
Map<OWLNamedIndividual, OWLNamedIndividual> predicateToAnonymousIndividual = new HashMap<OWLNamedIndividual, OWLNamedIndividual>(newAxiomNr * 2);
Set<OWLDataPropertyAssertionAxiom> dpaSet = o.getAxioms(AxiomType.DATA_PROPERTY_ASSERTION);
Set<OWLObjectPropertyAssertionAxiom> opaSet = o.getAxioms(AxiomType.OBJECT_PROPERTY_ASSERTION);
propertyAssertionAxioms = new ArrayList<OWLPropertyAssertionAxiom<? extends OWLPropertyExpression<?,?>,?>>(dpaSet.size() + opaSet.size());
propertyAssertionAxioms.addAll(dpaSet);
propertyAssertionAxioms.addAll(opaSet);
for(OWLPropertyAssertionAxiom<? extends OWLPropertyExpression<?,?>, ?> pa: propertyAssertionAxioms)
{
//Example: predicateToCompare will be a ServiceQuestion individual used as a OWLDataProperty when collapsed.
OWLNamedIndividual predicateToCompare = factory.getOWLNamedIndividual(((OWLNamedObject)pa.getProperty()).getIRI());
if(optimizedPredicates.contains(predicateToCompare))
{
//TODO one Anonymous for all choicevalues!! or no?
OWLIndividual sr = pa.getSubject();
OWLNamedIndividual serviceAnswer = predicateToAnonymousIndividual.get(predicateToCompare);
if (serviceAnswer == null)
{
serviceAnswer = factory.getOWLNamedIndividual(OWL.fullIri("temp" + tempIndividualCounter++));
//axiom #1
OWLObjectPropertyAssertionAxiom hasServiceAnswer = factory.getOWLObjectPropertyAssertionAxiom(objectPropertyToCollapseMem, sr, serviceAnswer);
//axiom #2
OWLObjectPropertyAssertionAxiom hasServiceField = factory.getOWLObjectPropertyAssertionAxiom(objectSubPropertyForOptimizedPredicateMem, serviceAnswer, predicateToCompare);
changes.add(new AddAxiom(o, hasServiceAnswer));
changes.add(new AddAxiom(o, hasServiceField));
predicateToAnonymousIndividual.put(predicateToCompare, serviceAnswer);
}
OWLPropertyAssertionAxiom<? extends OWLPropertyExpression<?,?>, ?> hasAnswer;
if (pa instanceof OWLDataPropertyAssertionAxiom)
{
//axiom #3 for Data Property Assertion
OWLLiteral literal = (OWLLiteral)pa.getObject();
hasAnswer = factory.getOWLDataPropertyAssertionAxiom(dataSubPropertyForOptimizedValueMem, serviceAnswer, literal);
}
else
{
//axiom #3 for Object Property Assertion
OWLNamedIndividual individual = (OWLNamedIndividual)pa.getObject();
hasAnswer = factory.getOWLObjectPropertyAssertionAxiom(objectSubPropertyForOptimizedObjectMem, serviceAnswer, individual);
}
changes.add(new AddAxiom(o, hasAnswer));
changes.add(new RemoveAxiom(o, pa));
}
}
man.applyChanges(changes);
if (DBG)
{
int reversedAxiomNr = o.getAxiomCount();
System.out.println("Reverse Transforming ontology from " + newAxiomNr + " to " + reversedAxiomNr + " (" + (reversedAxiomNr - newAxiomNr) + " added Ax)");
}
if (DBGX) DBGUtils.printOntologyFunctional(o);
}
public boolean isTransformProperty(OWLObjectProperty property)
{
return objectPropertyToCollapse.equals(property);
}
}