/*******************************************************************************
* 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.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import mjson.Json;
import org.hypergraphdb.util.Pair;
import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLDataPropertyExpression;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLIndividual;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLObjectPropertyExpression;
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.sharegov.cirm.CirmTransaction;
import org.sharegov.cirm.OWL;
import org.sharegov.cirm.Refs;
import org.sharegov.cirm.utils.DBGUtils;
import org.sharegov.cirm.utils.ThreadLocalStopwatch;
public class RelationalOWLPersister
{
public static boolean DBG = true;
public static boolean DBG_AXIOMS = false;
private IRI connectionInfo;
private volatile RelationalStoreExt store = null;
private static volatile RelationalOWLPersister instance = null;
private volatile OntologyTransformer transformer = null;
private final ConcurrentLinkedQueue<RDBListener> rdbListeners = new ConcurrentLinkedQueue<RDBListener>();
private DataSourceRef dataSource = null;
private void createStore()
{
if (connectionInfo == null)
throw new RuntimeException(
"No connection info meta data specified.");
// OWLNamedIndividual info = OWL.individual(connectionInfo);
// OWLNamedIndividual dbType = OWL.objectProperty(info, "hasDatabaseType");
// String driverClassName = OWL.dataProperty(dbType, "hasDriver")
// .getLiteral();
// String url = OWL.dataProperty(info, "hasUrl").getLiteral();
// String username = OWL.dataProperty(info, "hasUsername").getLiteral();
// String password = OWL.dataProperty(info, "hasPassword").getLiteral();
this.dataSource = new DataSourceRef(connectionInfo);
store = new RelationalStoreExt(dataSource); // url, driverClassName, username, password);
}
public RelationalOWLPersister()
{
}
/**
* ConnectionInfo is ignored after the instance is created.
*
* @param connectionInfo
* @return
*/
public static RelationalOWLPersister getInstance(IRI connectionInfo)
{
if (instance == null)
{
synchronized (RelationalOWLPersister.class)
{
if (instance == null)
instance = new RelationalOWLPersister(connectionInfo);
}
}
return instance;
}
public DataSourceRef getDataSourceRef()
{
return dataSource;
}
/**
* This publicly makes the store available. Maybe we should avoid this by
* extending the persister interface. The method is heavily used in this
* class also, I suspect, because of lazy initialization pattern
* (constructor does not create store) or because it was not clear when to
* create it. In support of this argument is the fact that only two store
* methods are extensively used publicly: selectIDsByIRI and
* selectEntitiesByIDs {@see LegacyEmulator, ActivityManager, DBIDFactory
* and some test classes: QueryTranslatorTest, OntologyChanger.} hilpoldQ
*
* @return
*/
public RelationalStore getStore()
{
return getStoreExt();
}
public RelationalStoreExt getStoreExt()
{
if (store == null)
synchronized (this) {
if (store == null)
createStore();
}
return store;
}
public RelationalOWLPersister(IRI connectionInfo)
{
this.connectionInfo = connectionInfo;
createStore();
transformer = Refs.ontologyTransformer.resolve();
}
public IRI getConnectionInfo()
{
return connectionInfo;
}
public void setConnectionInfo(IRI connectionInfo)
{
this.connectionInfo = connectionInfo;
}
// -------------------------------------------------------------------------
// INDIVIDUAL METHODS
//
/**
* Reads an individual and all dependent individuals into the given ontology
* within a transaction.
*
* @param on
* an (empty) OWLntology with a manager set.
* @param ind
* the named indiviual to read.
*/
public void readIndividualData(final OWLOntology on, final OWLNamedIndividual ind)
{
ThreadLocalStopwatch stopwatch = ThreadLocalStopwatch.getWatch();
if (DBG)
stopwatch.time("START readIndividualData(" + ind + ")");
store.txn(new CirmTransaction<Object>()
{
@Override
public Object call() throws Exception
{
store.readIndividualDataRecursive(on, ind);
return null;
}
});
transformer.reverseTransform(on);
if (DBG)
stopwatch.time("END readIndividualData(" + ind + ")");
if (DBG_AXIOMS)
{
System.out.println(on);
DBGUtils.printOntologyFunctional(on);
}
}
// /**
// * Searches for all entities by the IRI of the given individual and
// * adds OWLObjectPropertyAssertionAxioms, OWLDataPropertyAssertionAxioms,
// OWLClassAssertionAxiom the ontology with
// * @param on an ontology to add axioms too.
// * @param ind
// * @return a set of dependent referenced individuals, which should be
// loaded next or the empty set.
// */
// public Set<OWLNamedIndividual> readIndividualDataNotMapped(OWLOntology
// on, OWLNamedIndividual ind)
// {
// OWLOntologyManager manager = MetaService.get().getManager();
// ArrayList<OWLOntologyChange> changes = new
// ArrayList<OWLOntologyChange>();
// Set<OWLNamedIndividual> referencedIndividuals = Collections.emptySet();
// //TRANSACTION START
// ThreadLocalConnection conn = store.getConnection();
// Set<OWLNamedIndividual> entities = Collections.singleton(ind);
// try
// {
// Map<OWLEntity, Long> identifiers =
// store.selectIDsAndEntitiesByIRIs(entities);
//
// Map<OWLObjectPropertyExpression, Set<OWLIndividual>> objProps =
// store.selectObjectProperties(ind, identifiers);
// for (Map.Entry<OWLObjectPropertyExpression, Set<OWLIndividual>> e :
// objProps.entrySet())
// {
// for (OWLIndividual propValue : e.getValue())
// {
// changes.add(new AddAxiom(on,
// manager.getOWLDataFactory().getOWLObjectPropertyAssertionAxiom(e.getKey(),
// ind, propValue)));
// if (referencedIndividuals.isEmpty()) {
// referencedIndividuals = new HashSet<OWLNamedIndividual>();
// }
// referencedIndividuals.add((OWLNamedIndividual)propValue);
// }
// }
//
// Map<OWLDataPropertyExpression, Set<OWLLiteral>> dataProps =
// getStore().selectDataProperties(ind, identifiers);
// for (Map.Entry<OWLDataPropertyExpression, Set<OWLLiteral>> e :
// dataProps.entrySet())
// {
// for (OWLLiteral propValue : e.getValue())
// changes.add(new AddAxiom(on,
// manager.getOWLDataFactory().getOWLDataPropertyAssertionAxiom(e.getKey(),
// ind, propValue)));
// }
//
// Set<OWLClass> classes = getStore().selectClass(ind, identifiers);
// for (OWLClass c : classes)
// {
// changes.add(new AddAxiom(on,
// manager.getOWLDataFactory().getOWLClassAssertionAxiom(c, ind)));
// }
// manager.applyChanges(changes);
// return referencedIndividuals;
// }
// catch (RuntimeException e)
// {
// store.rollback(conn);
// throw e;
// }
// finally
// {
// store.close(conn);
// }
// }
// /**
// *
// * @param on
// * @param ind
// */
// public void readIndividualDataMapped(OWLOntology onto, OWLNamedIndividual
// ind) {
// store.readIndividualDataMapped(onto, ind);
// }
/**
* Searches for all entities by the IRI of the given individual and adds
* OWLObjectPropertyAssertionAxioms, OWLDataPropertyAssertionAxioms,
* OWLClassAssertionAxiom the ontology that are valid at a given point in
* time.
*
* @param on
* the ontology to add the individual data to.
* @param individual
* the named individual
* @param version
*/
public void readIndividualData(OWLOntology on,
OWLNamedIndividual individual, Date version)
{
// TODO hilpold use THREAD SAFE MANAGER AND DATA FACTORY
// Populates a given Ontology with the given version from database.
OWLOntologyManager manager = on.getOWLOntologyManager(); // OWL.manager();
OWLDataFactory df = manager.getOWLDataFactory();
ArrayList<OWLOntologyChange> L = new ArrayList<OWLOntologyChange>();
Set<OWLNamedIndividual> entities = new HashSet<OWLNamedIndividual>();
entities.add(individual.asOWLNamedIndividual());
ThreadLocalConnection conn = store.getConnection();
try
{
Map<OWLEntity, DbId> identifiers = getStoreExt().selectIDsAndEntitiesByIRIs(entities);
Map<OWLObjectPropertyExpression, Set<OWLIndividual>> objProps = getStoreExt()
.selectObjectProperties(individual, version, identifiers, df);
for (Map.Entry<OWLObjectPropertyExpression, Set<OWLIndividual>> e : objProps
.entrySet())
{
for (OWLIndividual propValue : e.getValue())
{
L.add(new AddAxiom(on, manager.getOWLDataFactory()
.getOWLObjectPropertyAssertionAxiom(e.getKey(),
individual, propValue)));
// 2012.04.12 hilpold was this a BUG? old was:
// readIndividualData(on, propValue);
readIndividualData(on, (OWLNamedIndividual) propValue,
version);
}
}
Map<OWLDataPropertyExpression, Set<OWLLiteral>> dataProps = getStoreExt()
.selectDataProperties(individual, version, identifiers, df);
for (Map.Entry<OWLDataPropertyExpression, Set<OWLLiteral>> e : dataProps
.entrySet())
{
for (OWLLiteral propValue : e.getValue())
L.add(new AddAxiom(on, manager.getOWLDataFactory()
.getOWLDataPropertyAssertionAxiom(e.getKey(),
individual, propValue)));
}
Set<OWLClass> classes = getStoreExt().selectClass(individual, version,
identifiers, df);
for (OWLClass c : classes)
{
L.add(new AddAxiom(on, manager.getOWLDataFactory()
.getOWLClassAssertionAxiom(c, individual)));
}
manager.applyChanges(L);
conn.commit();
}
catch (Exception e)
{
store.rollback(conn);
throw new RuntimeException(e);
} finally
{
store.close(conn);
}
transformer.reverseTransform(on);
}
/**
*
* @param ind
* @return
*/
public List<Date> readIndividualHistory(OWLIndividual ind)
{
Set<OWLEntity> entities = new HashSet<OWLEntity>();
entities.add(ind.asOWLNamedIndividual());
ThreadLocalConnection conn = store.getConnection();
try
{
Map<OWLEntity, DbId> identifiers = getStoreExt().selectInsertIDsAndEntitiesByIRIs(entities, false);
long ind_iri = identifiers.get(ind).getFirst();
List<Date> dates = getStoreExt().selectIndividualHistory(ind_iri);
conn.commit();
return dates;
} catch (Exception e)
{
store.rollback(conn);
throw new RuntimeException(e);
} finally
{
store.close(conn);
}
}
// -------------------------------------------------------------------------
// BUSINESS OBJECT ONTOLOGY METHODS
//
public void saveBusinessObjectOntology(final OWLOntology ontology)
{
ThreadLocalStopwatch stopwatch = ThreadLocalStopwatch.getWatch();
if (DBG)
{
stopwatch.time("START saveBusinessObjectOntology("
+ ontology.getOntologyID() + ") ");
}
final OWLOntology optimizedOntology = transformer.transform(ontology);
store.txn(new CirmTransaction<Object>()
{
@Override
public Object call() throws Exception
{
ThreadLocalConnection conn = store.getConnection();
DbId boObj;
OWLOntologyID id = ontology.getOntologyID();
if (!id.isAnonymous())
{
boObj = ensureBusinessOntologyIndividual(ontology.getOntologyID(), conn);
}
else
boObj = null;
fireRDBSave(ontology);
//TODO revmoc
getStoreExt().merge(optimizedOntology, boObj);
// Commit, rollback, close txn implicit
return null;
}
});
if (DBG)
{
stopwatch.time("END saveBusinessObjectOntology ");
}
}
private DbId ensureBusinessOntologyIndividual(OWLOntologyID ontologyId, Connection conn) throws SQLException
{
OWLDataFactory df = Refs.tempOntoManager.resolve().getOWLDataFactory();
IRI boIRI = ontologyId.getOntologyIRI().resolve("#bo");
long id = OWL.parseIDFromBusinessOntologyIRI(boIRI);
if (id < 0)
throw new IllegalArgumentException(" parsed < 0 id from bo iri"
+ ontologyId.getOntologyIRI());
OWLEntity e = getStoreExt().selectEntityByID(id, df);
if (e == null)
{
getStoreExt().insertIri(id, boIRI.toString(), "NamedIndividual", conn);
return new DbId(id, df.getOWLNamedIndividual(boIRI), false);
}
else
{
// 2013.02.25 hilpold supporting type change of a BO
if (!e.getIRI().equals(boIRI))
getStoreExt().updateIri(id, boIRI.toString(), conn);
return new DbId(id, df.getOWLNamedIndividual(boIRI), true);
}
}
public OWLOntology getBusinessObjectOntology(final Long boID)
{
return store.txn(new CirmTransaction<OWLOntology>() {
public OWLOntology call()
{
OWLDataFactory df = Refs.tempOntoManager.resolve().getOWLDataFactory();
Connection conn = getStoreExt().getConnection();
OWLOntology result;
try
{
OWLEntity entity = getStoreExt().selectEntityByID(boID, df);
if (entity != null)
{
result = getBusinessObjectOntology(entity.getIRI());
} else
{
result = null;
}
conn.commit();
return result;
}
catch (Exception e)
{
store.rollback(conn);
throw new RuntimeException(e);
}
finally
{
getStoreExt().close(conn);
}
}
});
}
/**
*
* @param boIRI
* an IRI ending #bo.
* @return
* @throws OWLOntologyCreationException
*/
public OWLOntology getBusinessObjectOntology(IRI boIRI)
{
try
{
OWLOntologyManager manager = Refs.tempOntoManager.resolve();
OWLOntology on;
synchronized (manager)
{
IRI ontologyIRI = IRI.create(boIRI.getStart().substring(0,
boIRI.getStart().length() - 1));
on = manager.getOntology(ontologyIRI);
if (on != null)
manager.removeOntology(on);
on = manager.createOntology(ontologyIRI);
}
readIndividualData(on, OWL.individual(boIRI));
return on;
} catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
// Mark the business object is invalid as of now (today's date)
public void shelveBusinessObjectOntology(OWLOntology ontology)
{
OWLOntology optimizedOntology = transformer.transform(ontology);
Map<OWLEntity, DbId> identifiers; // = new HashMap<OWLEntity, Long>();
Set<OWLEntity> entities = new HashSet<OWLEntity>();
entities.addAll(optimizedOntology.getClassesInSignature());
entities.addAll(optimizedOntology.getIndividualsInSignature());
entities.addAll(optimizedOntology.getObjectPropertiesInSignature());
entities.addAll(optimizedOntology.getDataPropertiesInSignature());
entities.addAll(optimizedOntology.getDatatypesInSignature(true));
ThreadLocalConnection conn = getStoreExt().getConnection();
try
{
identifiers = store.selectIDsAndEntitiesByIRIs(entities);
Set<OWLNamedIndividual> individuals = optimizedOntology.getIndividualsInSignature();
getStoreExt().deleteClassification(
optimizedOntology.getAxioms(AxiomType.CLASS_ASSERTION),
individuals, identifiers);
getStoreExt().deleteDataProperties(
optimizedOntology.getAxioms(AxiomType.DATA_PROPERTY_ASSERTION),
individuals, identifiers);
getStoreExt().deleteObjectProperties(
optimizedOntology.getAxioms(AxiomType.OBJECT_PROPERTY_ASSERTION),
individuals, identifiers);
conn.commit();
} catch (SQLException e)
{
store.rollback(conn);
throw new RuntimeException(e);
} finally
{
store.close(conn);
}
}
// Delete the BO ontology from the database completely.
/**
* Deletes all stored axioms of the given ontology including their history.
* Currently, this only deletes from the vertical schema and NOT(!) from the
* mapped schema. (Because of foreign key constraint exceptions)
*
* @param ontology
* @deprecated
*/
public void deleteBusinessObjectOntologyWithHistory(OWLOntology ontology)
{
OWLOntology optimizedOntology = transformer.transform(ontology);
ThreadLocalConnection conn = store.getConnection();
try
{
Map<OWLEntity, Long> identifiers = null; //store.selectIDsAndEntitiesByIRIs(optimizedOntology, false);
// Set<OWLNamedIndividual> individuals =
// ontology.getIndividualsInSignature();
getStoreExt().deleteClassificationWithHistory(
optimizedOntology.getAxioms(AxiomType.CLASS_ASSERTION),
identifiers);
getStoreExt()
.deleteDataPropertiesWithHistory(
optimizedOntology
.getAxioms(AxiomType.DATA_PROPERTY_ASSERTION),
identifiers);
getStoreExt()
.deleteObjectPropertiesWithHistory(
optimizedOntology
.getAxioms(AxiomType.OBJECT_PROPERTY_ASSERTION),
identifiers);
System.err
.println("Current implementation does not delete from mapped schema!");
// getStoreImpl().deleteMappedOntologyIndividuals(ontology,
// identifiers);
conn.commit();
} catch (Exception e)
{
store.rollback(conn);
throw new RuntimeException(e);
} finally
{
store.close(conn);
}
}
// -------------------------------------------------------------------------
// QUERY METHODS
//
public List<OWLEntity> query(final Json pattern)
{
return store.txn(new CirmTransaction<List<OWLEntity>>()
{
@Override
public List<OWLEntity> call() throws Exception
{
return queryTxn(pattern);
}
});
}
private List<OWLEntity> queryTxn(Json pattern)
{
// TODO: this obviously really temporary. I actually have the feeling
// that this class
// should try to avoid accessing the SQL API and just implement higher
// level operations.
// For example, here, it should basically translate the query pattern
// into something more
// primitive that the RelationalStore will work with. But for now I just
// want a list of
// business objects so I can work on the UI side with templating and
// stuff....
OWLDataFactory df = Refs.tempOntoManager.resolve().getOWLDataFactory();
ThreadLocalStopwatch stopwatch = ThreadLocalStopwatch.getWatch();
QueryTranslator translator = new QueryTranslator();
ThreadLocalConnection conn = store.getConnection();
if (DBG)
{
stopwatch.time("START Query ");
}
try
{
Query query = translator.translate(pattern, getStoreExt());
ArrayList<OWLEntity> L = new ArrayList<OWLEntity>();
L.addAll(getStoreExt().queryGetEntities(query, df).values());
conn.commit();
return L;
} catch (Exception e)
{
store.rollback(conn);
throw new RuntimeException(e);
} finally
{
store.close(conn);
stopwatch.time("END Query ");
}
}
public void addRDBListener(RDBListener l)
{
rdbListeners.add(l);
}
public void removeRDBListener(RDBListener l)
{
rdbListeners.remove(l);
}
public int getNrOfRDBListeners()
{
return rdbListeners.size();
}
private void fireRDBSave(OWLOntology ontology)
{
RDBEvent e = new RDBEvent(ontology.getOntologyID());
for (RDBListener l : rdbListeners)
l.saveExecuting(e);
}
}