1
/* This file is part of the KDE Project
2
Copyright (c) 2008 Sebastian Trueg <trueg@kde.org>
4
This library is free software; you can redistribute it and/or
5
modify it under the terms of the GNU Library General Public
6
License version 2 as published by the Free Software Foundation.
8
This library is distributed in the hope that it will be useful,
9
but WITHOUT ANY WARRANTY; without even the implied warranty of
10
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
Library General Public License for more details.
13
You should have received a copy of the GNU Library General Public License
14
along with this library; see the file COPYING.LIB. If not, write to
15
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16
Boston, MA 02110-1301, USA.
19
#include "ontologymanagermodel.h"
20
#include "crappyinferencer.h"
22
#include <QtCore/QUrl>
23
#include <QtCore/QDateTime>
24
#include <QtCore/QScopedPointer>
26
#include <Soprano/Backend>
27
#include <Soprano/Version>
28
#include <Soprano/StorageModel>
29
#include <Soprano/PluginManager>
30
#include <Soprano/Global>
31
#include <Soprano/NodeIterator>
32
#include <Soprano/StatementIterator>
33
#include <Soprano/QueryResultIterator>
34
#include <Soprano/Vocabulary/RDF>
35
#include <Soprano/Vocabulary/RDFS>
36
#include <Soprano/Vocabulary/NRL>
37
#include <Soprano/Vocabulary/NAO>
38
#include <Soprano/Vocabulary/XMLSchema>
39
#include <Soprano/Vocabulary/OWL>
44
using namespace Soprano;
50
* Create a uri for an nrl:MetadataGraph.
51
* \param uri The uri of the data graph.
53
QUrl createMetadataGraphUri( const QUrl& uri ) {
54
QString s( uri.toString() );
55
if ( s.endsWith( '#' ) )
56
s[s.length()-1] = '/';
57
else if ( !s.endsWith( '/' ) )
64
* Find the graphs an ontology is stored in.
65
* \param model The model to search in.
66
* \param ns The namespace of the ontology in question.
67
* \param dataGraphUri The graph which stores the ontology data (output variable)
68
* \param metaDataGraphUri The graph which stores the ontology metadata (output variable)
70
* \return \p true if the ontology was found and both dataGraphUri and metaDataGraphUri are filled
73
bool findGraphUris( Soprano::Model* model, const QUrl& ns, QUrl& dataGraphUri, QUrl& metaDataGraphUri ) {
74
// We use a FILTER(STR(?ns)...) to support both Soprano 2.3 (with plain literals) and earlier (with only typed ones)
75
QString query = QString( "select ?dg ?mdg where { "
78
"FILTER(REGEX(STR(?ns), \"^%2\")) . "
80
.arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() )
82
.arg( Soprano::Vocabulary::NRL::coreGraphMetadataFor().toString() );
83
QueryResultIterator it = model->executeQuery( query, Soprano::Query::QueryLanguageSparql );
85
metaDataGraphUri = it.binding("mdg").uri();
86
dataGraphUri = it.binding("dg").uri();
95
* Check if the ontology with namespace \p ns has a proper NRL layout in model
98
* An ontology that passes this test can be imported into a production model without
101
* \return \p true if the necessary NRL graphs are defined, \p false otherwise
103
bool ensureDataLayout( Soprano::Model* tmpModel, const QUrl& ns )
105
// 1. all statements need to have a proper context set
106
StatementIterator it = tmpModel->listStatements();
107
while ( it.next() ) {
108
if ( !it.current().context().isValid() ) {
109
kDebug() << "Invalid data in ontology" << ns << *it;
114
// 2. make sure we have a proper relation between the data and metadata graphs
115
QUrl dataGraphUri, metaDataGraphUri;
116
if ( !findGraphUris( tmpModel, ns, dataGraphUri, metaDataGraphUri ) ) {
117
kDebug() << "Invalid data in ontology" << ns << "Could not find datagraph and metadatagraph relation.";
126
* Try to guess the ontoloy type from the contents of the model:
127
* a nrl:Ontology or a nrl:KnowledgeBase or a pure nrl:InstanceBase
129
QUrl guessOntologyType( Soprano::Model* tmpModel )
131
static QList<QUrl> propertyClasses;
132
if ( propertyClasses.isEmpty() )
133
propertyClasses << Soprano::Vocabulary::RDFS::Class()
134
<< Soprano::Vocabulary::OWL::Class()
135
<< Soprano::Vocabulary::RDF::Property()
136
<< Soprano::Vocabulary::RDFS::ContainerMembershipProperty()
137
<< Soprano::Vocabulary::OWL::ObjectProperty()
138
<< Soprano::Vocabulary::OWL::DatatypeProperty()
139
<< Soprano::Vocabulary::OWL::AnnotationProperty()
140
<< Soprano::Vocabulary::OWL::FunctionalProperty()
141
<< Soprano::Vocabulary::OWL::DeprecatedProperty()
142
<< Soprano::Vocabulary::OWL::OntologyProperty()
143
<< Soprano::Vocabulary::OWL::TransitiveProperty()
144
<< Soprano::Vocabulary::OWL::SymmetricProperty()
145
<< Soprano::Vocabulary::OWL::InverseFunctionalProperty()
146
<< Soprano::Vocabulary::NRL::TransitiveProperty()
147
<< Soprano::Vocabulary::NRL::SymmetricProperty()
148
<< Soprano::Vocabulary::NRL::AsymmetricProperty()
149
<< Soprano::Vocabulary::NRL::InverseFunctionalProperty()
150
<< Soprano::Vocabulary::NRL::FunctionalProperty()
151
<< Soprano::Vocabulary::NRL::ReflexiveProperty();
153
// check for classes and properties
154
QStringList classesOrPropertiesSubQueries;
155
foreach( const QUrl& uri, propertyClasses ) {
156
classesOrPropertiesSubQueries << QString( "?type = <%1>" ).arg( uri.toString() );
159
// we cannot use UNION here because redland does not support it!
160
bool haveClassesOrProperties = tmpModel->executeQuery( QString( "ask where { "
163
.arg( classesOrPropertiesSubQueries.join( " || " ) ),
164
Soprano::Query::QueryLanguageSparql ).boolValue();
166
// check for anything that is not a class or property
167
classesOrPropertiesSubQueries.clear();
168
foreach( const QUrl& uri, propertyClasses ) {
169
classesOrPropertiesSubQueries << QString( "?type != <%1>" ).arg( uri.toString() );
171
// owl:Ontologys do not have any influce on our descision
172
classesOrPropertiesSubQueries << QString( "?type != <%1>" ).arg( Soprano::Vocabulary::OWL::Ontology().toString() );
174
bool haveInstances = tmpModel->executeQuery( QString( "ask where { "
177
.arg( classesOrPropertiesSubQueries.join( " && " ) ),
178
Soprano::Query::QueryLanguageSparql ).boolValue();
180
if ( haveClassesOrProperties && !haveInstances )
181
return Soprano::Vocabulary::NRL::Ontology();
182
else if ( !haveClassesOrProperties && haveInstances )
183
return Soprano::Vocabulary::NRL::InstanceBase();
185
return Soprano::Vocabulary::NRL::KnowledgeBase();
190
* Create the necessary NRL graphs and metadata for an ontology to pass ensureDataLayout.
192
* \param tmpModel The model to store everything in
193
* \param ns The namespace of the ontology to modify in \p tmpModel
195
void createMetadata( Soprano::Model* tmpModel, const QUrl& ns )
197
Q_ASSERT( ns.isValid() );
198
QUrl dataGraphUri( ns );
199
dataGraphUri.setFragment( QString() );
200
QUrl metaDataGraphUri = createMetadataGraphUri( dataGraphUri );
202
// set proper context on all data statements (This is a bit ugly but we cannot iterate and modify at the same time!)
203
QList<Statement> allStatements = tmpModel->listStatements().allStatements();
204
tmpModel->removeAllStatements();
205
foreach( Statement s, allStatements ) { // krazy:exclude=foreach
206
s.setContext( dataGraphUri );
207
tmpModel->addStatement( s );
210
QUrl graphType = guessOntologyType( tmpModel );
212
kDebug() << "guessed onto type:" << graphType;
215
tmpModel->addStatement( Soprano::Statement( metaDataGraphUri, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::GraphMetadata(), metaDataGraphUri ) );
216
tmpModel->addStatement( Soprano::Statement( metaDataGraphUri, Soprano::Vocabulary::NRL::coreGraphMetadataFor(), dataGraphUri, metaDataGraphUri ) );
217
tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::RDF::type(), graphType, metaDataGraphUri ) );
218
if ( graphType == Soprano::Vocabulary::NRL::KnowledgeBase() ) {
219
// we do not have inference in Nepomuk yet and this way libnepomuk does not get confused when reading types
220
tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::Ontology(), metaDataGraphUri ) );
221
tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::InstanceBase(), metaDataGraphUri ) );
223
#if SOPRANO_IS_VERSION( 2, 2, 67 )
224
tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::NAO::hasDefaultNamespace(), LiteralValue::createPlainLiteral( ns.toString() ), metaDataGraphUri ) );
226
tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::NAO::hasDefaultNamespace(), LiteralValue( ns.toString() ), metaDataGraphUri ) );
232
class Nepomuk::OntologyManagerModel::Private
235
Private( OntologyManagerModel* p )
239
CrappyInferencer m_inferenceModel;
242
OntologyManagerModel* q;
249
Nepomuk::OntologyManagerModel::OntologyManagerModel( Soprano::Model* parentModel, QObject* parent )
251
d( new Private( this ) )
254
FilterModel::setParentModel( &d->m_inferenceModel );
255
setParentModel( parentModel );
259
Nepomuk::OntologyManagerModel::~OntologyManagerModel()
265
void Nepomuk::OntologyManagerModel::setParentModel( Soprano::Model* parentModel )
267
d->m_inferenceModel.setParentModel( parentModel );
271
bool Nepomuk::OntologyManagerModel::updateOntology( Soprano::StatementIterator data, const QUrl& ns )
278
// Create temp memory model
279
// ------------------------------------
280
const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByFeatures( Soprano::BackendFeatureStorageMemory );
282
kDebug() << "No Soprano backend found that can handle memory models!";
283
setError( "No Soprano backend found that can handle memory models." );
287
Soprano::Model* tmpModel = backend->createModel( BackendSettings() << BackendSetting( Soprano::BackendOptionStorageMemory ) );
289
kDebug() << "Failed to create temp memory model!";
290
setError( backend->lastError() );
294
// so we do not have to care about deleting out tmpModel anymore.
295
QScopedPointer<Soprano::Model> modelGarbageCollector( tmpModel );
297
// import the data into our tmp model
298
while ( data.next() ) {
299
tmpModel->addStatement( *data );
303
if ( ontoUri.isEmpty() ) {
304
StatementIterator it = tmpModel->listStatements();
306
ontoUri = it.current().subject().uri();
307
if ( !ontoUri.fragment().isEmpty() ) {
308
ontoUri.setFragment( QString() );
311
ontoUri = ontoUri.toString().left( ontoUri.toString().lastIndexOf( '/' )+1 );
315
if ( ontoUri.isEmpty() ) {
316
kDebug() << "Failed to determine ontology URI.";
317
setError( "Failed to determine ontology URI from data." );
321
// all the data has been read into the temp model
322
// now we make sure it has a proper layout (one main and one metadata graph)
323
// ------------------------------------
324
QList<Node> graphs = tmpModel->listContexts().allNodes();
325
if ( graphs.count() == 0 ) {
326
// simple: we have to create all data manually
327
createMetadata( tmpModel, ontoUri );
329
else if ( graphs.count() == 2 ) {
330
// proper number of graphs. Make sure we have all the necessary information
331
if ( !ensureDataLayout( tmpModel, ontoUri ) ) {
332
setError( "The ontology data contains invalid statements.", Soprano::Error::ErrorInvalidArgument );
337
kDebug() << "Invalid data in ontology" << ontoUri << "We need one data and one metadata graph.";
338
setError( "The ontology data contains invalid statements.", Soprano::Error::ErrorInvalidArgument );
343
// store the modification date of the ontology file in the metadata graph and reuse it to know if we have to update
344
// ------------------------------------
345
QUrl dataGraphUri, metadataGraphUri;
346
if ( findGraphUris( tmpModel, ontoUri, dataGraphUri, metadataGraphUri ) ) {
347
// remove any modification date data there is
348
tmpModel->removeAllStatements( dataGraphUri, Soprano::Vocabulary::NAO::lastModified(), Node() );
350
// set the new modification date
351
tmpModel->addStatement( dataGraphUri, Soprano::Vocabulary::NAO::lastModified(), LiteralValue( QDateTime::currentDateTime() ), metadataGraphUri );
353
// now it is time to merge the new data in
354
// ------------------------------------
355
if ( ontoModificationDate( ontoUri ).isValid() ) {
356
if ( !removeOntology( ontoUri ) ) {
361
StatementIterator it = tmpModel->listStatements();
362
while ( it.next() ) {
363
if ( addStatement( *it ) != Error::ErrorNone ) {
364
// FIXME: here we should cleanup, but then again, if adding the statement
365
// fails, removing will probably also fail. So the only real solution
366
// would be a transaction.
371
kDebug() << "Successfully updated ontology" << ontoUri << QString("(%1ms)").arg(timer.elapsed());
375
kDebug() << "BUG! BUG! BUG! BUG! BUG! BUG! Could not find data and metadata graph URIs! This should not happen!";
381
bool Nepomuk::OntologyManagerModel::removeOntology( const QUrl& ns )
385
QUrl dataGraphUri, metadataGraphUri;
386
if ( findGraphUris( this, ns, dataGraphUri, metadataGraphUri ) ) {
387
// now removing the ontology is simple
388
removeContext( dataGraphUri );
389
removeContext( metadataGraphUri );
390
// be sure we remove any junk from buggy versions
391
removeAllStatements( dataGraphUri, Soprano::Node(), Soprano::Node() );
395
kDebug() << "Could not find data graph URI for" << ns;
396
setError( "Could not find ontology " + ns.toString(), Error::ErrorInvalidArgument );
402
QDateTime Nepomuk::OntologyManagerModel::ontoModificationDate( const QUrl& uri )
404
// We use a FILTER(STR(?ns)...) to support both Soprano 2.3 (with plain literals) and earlier (with only typed ones)
405
QString query = QString( "select ?date where { "
407
"?onto <%3> ?date . "
408
"FILTER(STR(?ns) = \"%2\") . "
409
"FILTER(DATATYPE(?date) = <%4>) . } LIMIT 1" )
410
.arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() )
411
.arg( uri.toString() )
412
.arg( Soprano::Vocabulary::NAO::lastModified().toString() )
413
.arg( Soprano::Vocabulary::XMLSchema::dateTime().toString() );
414
QueryResultIterator it = executeQuery( query, Soprano::Query::QueryLanguageSparql );
416
//kDebug() << "Found modification date for" << uri << it.binding( "date" ).literal().toDateTime();
417
return it.binding( "date" ).literal().toDateTime();
425
QUrl Nepomuk::OntologyManagerModel::findOntologyContext( const QUrl& uri )
427
QUrl dataGraphUri, metaDataGraphUri;
428
if ( findGraphUris( parentModel(), uri, dataGraphUri, metaDataGraphUri ) ) {
436
#include "ontologymanagermodel.moc"