add more spacing
[personal-kdebase.git] / runtime / nepomuk / services / ontologyloader / ontologymanagermodel.cpp
blob83ed41e796c535815298c3bdb70feab5ffe21021
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"
21 #include <QtCore/QUrl>
22 #include <QtCore/QDateTime>
24 #include <Soprano/Backend>
25 #include <Soprano/StorageModel>
26 #include <Soprano/PluginManager>
27 #include <Soprano/Global>
28 #include <Soprano/NodeIterator>
29 #include <Soprano/StatementIterator>
30 #include <Soprano/QueryResultIterator>
31 #include <Soprano/Vocabulary/RDF>
32 #include <Soprano/Vocabulary/RDFS>
33 #include <Soprano/Vocabulary/NRL>
34 #include <Soprano/Vocabulary/NAO>
35 #include <Soprano/Vocabulary/XMLSchema>
36 #include <Soprano/Vocabulary/OWL>
38 #include <KDebug>
41 using namespace Soprano;
45 namespace {
46 /**
47 * Create a uri for an nrl:MetadataGraph.
48 * \param uri The uri of the data graph.
50 QUrl createMetadataGraphUri( const QUrl& uri ) {
51 QString s( uri.toString() );
52 if ( s.endsWith( '#' ) )
53 s[s.length()-1] = '/';
54 else if ( !s.endsWith( '/' ) )
55 s += '/';
56 s += "metadata";
57 return QUrl( s );
60 /**
61 * Find the graphs an ontology is stored in.
62 * \param model The model to search in.
63 * \param ns The namespace of the ontology in question.
64 * \param dataGraphUri The graph which stores the ontology data (output variable)
65 * \param metaDataGraphUri The graph which stores the ontology metadata (output variable)
67 * \return \p true if the ontology was found and both dataGraphUri and metaDataGraphUri are filled
68 * with proper values.
70 bool findGraphUris( Soprano::Model* model, const QUrl& ns, QUrl& dataGraphUri, QUrl& metaDataGraphUri ) {
71 QString query = QString( "select ?dg ?mdg where { "
72 "?dg <%1> \"%2\"^^<%3> . "
73 "?mdg <%4> ?dg . "
74 "}" )
75 .arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() )
76 .arg( ns.toString() )
77 .arg( Soprano::Vocabulary::XMLSchema::string().toString() )
78 .arg( Soprano::Vocabulary::NRL::coreGraphMetadataFor().toString() );
80 QueryResultIterator it = model->executeQuery( query, Soprano::Query::QueryLanguageSparql );
81 if ( it.next() ) {
82 metaDataGraphUri = it.binding("mdg").uri();
83 dataGraphUri = it.binding("dg").uri();
84 return true;
86 else {
87 return false;
91 /**
92 * Check if the ontology with namespace \p ns has a proper NRL layout in model
93 * \p model.
95 * An ontology that passes this test can be imported into a production model without
96 * any modifications.
98 * \return \p true if the necessary NRL graphs are defined, \p false otherwise
100 bool ensureDataLayout( Soprano::Model* tmpModel, const QUrl& ns )
102 // 1. all statements need to have a proper context set
103 StatementIterator it = tmpModel->listStatements();
104 while ( it.next() ) {
105 if ( !it.current().context().isValid() ) {
106 kDebug() << "Invalid data in ontology" << ns << *it;
107 return false;
111 // 2. make sure we have a proper relation between the data and metadata graphs
112 QUrl dataGraphUri, metaDataGraphUri;
113 if ( !findGraphUris( tmpModel, ns, dataGraphUri, metaDataGraphUri ) ) {
114 kDebug() << "Invalid data in ontology" << ns << "Could not find datagraph and metadatagraph relation.";
115 return false;
118 return true;
123 * Try to guess the ontoloy type from the contents of the model:
124 * a nrl:Ontology or a nrl:KnowledgeBase or a pure nrl:InstanceBase
126 QUrl guessOntologyType( Soprano::Model* tmpModel )
128 static QList<QUrl> propertyClasses;
129 if ( propertyClasses.isEmpty() )
130 propertyClasses << Soprano::Vocabulary::RDFS::Class()
131 << Soprano::Vocabulary::OWL::Class()
132 << Soprano::Vocabulary::RDF::Property()
133 << Soprano::Vocabulary::RDFS::ContainerMembershipProperty()
134 << Soprano::Vocabulary::OWL::ObjectProperty()
135 << Soprano::Vocabulary::OWL::DatatypeProperty()
136 << Soprano::Vocabulary::OWL::AnnotationProperty()
137 << Soprano::Vocabulary::OWL::FunctionalProperty()
138 << Soprano::Vocabulary::OWL::DeprecatedProperty()
139 << Soprano::Vocabulary::OWL::OntologyProperty()
140 << Soprano::Vocabulary::OWL::TransitiveProperty()
141 << Soprano::Vocabulary::OWL::SymmetricProperty()
142 << Soprano::Vocabulary::OWL::InverseFunctionalProperty()
143 << Soprano::Vocabulary::NRL::TransitiveProperty()
144 << Soprano::Vocabulary::NRL::SymmetricProperty()
145 << Soprano::Vocabulary::NRL::AsymmetricProperty()
146 << Soprano::Vocabulary::NRL::InverseFunctionalProperty()
147 << Soprano::Vocabulary::NRL::FunctionalProperty()
148 << Soprano::Vocabulary::NRL::ReflexiveProperty();
150 // check for classes and properties
151 QStringList classesOrPropertiesSubQueries;
152 foreach( const QUrl& uri, propertyClasses ) {
153 classesOrPropertiesSubQueries << QString( "?type = <%1>" ).arg( uri.toString() );
156 // we cannot use UNION here because redland does not support it!
157 bool haveClassesOrProperties = tmpModel->executeQuery( QString( "ask where { "
158 "?r a ?type . "
159 "FILTER(%1) . }" )
160 .arg( classesOrPropertiesSubQueries.join( " || " ) ),
161 Soprano::Query::QueryLanguageSparql ).boolValue();
163 // check for anything that is not a class or property
164 classesOrPropertiesSubQueries.clear();
165 foreach( const QUrl& uri, propertyClasses ) {
166 classesOrPropertiesSubQueries << QString( "?type != <%1>" ).arg( uri.toString() );
168 // owl:Ontologys do not have any influce on our descision
169 classesOrPropertiesSubQueries << QString( "?type != <%1>" ).arg( Soprano::Vocabulary::OWL::Ontology().toString() );
171 bool haveInstances = tmpModel->executeQuery( QString( "ask where { "
172 "?r a ?type . "
173 "FILTER(%1) . }" )
174 .arg( classesOrPropertiesSubQueries.join( " && " ) ),
175 Soprano::Query::QueryLanguageSparql ).boolValue();
177 if ( haveClassesOrProperties && !haveInstances )
178 return Soprano::Vocabulary::NRL::Ontology();
179 else if ( !haveClassesOrProperties && haveInstances )
180 return Soprano::Vocabulary::NRL::InstanceBase();
181 else
182 return Soprano::Vocabulary::NRL::KnowledgeBase();
187 * Create the necessary NRL graphs and metadata for an ontology to pass ensureDataLayout.
189 * \param tmpModel The model to store everything in
190 * \param ns The namespace of the ontology to modify in \p tmpModel
192 void createMetadata( Soprano::Model* tmpModel, const QUrl& ns )
194 Q_ASSERT( ns.isValid() );
195 QUrl dataGraphUri( ns );
196 dataGraphUri.setFragment( QString() );
197 QUrl metaDataGraphUri = createMetadataGraphUri( dataGraphUri );
199 // set proper context on all data statements (This is a bit ugly but we cannot iterate and modify at the same time!)
200 QList<Statement> allStatements = tmpModel->listStatements().allStatements();
201 tmpModel->removeAllStatements();
202 foreach( Statement s, allStatements ) {
203 s.setContext( dataGraphUri );
204 tmpModel->addStatement( s );
207 QUrl graphType = guessOntologyType( tmpModel );
209 kDebug() << "guessed onto type:" << graphType;
211 // add the metadata
212 tmpModel->addStatement( Soprano::Statement( metaDataGraphUri, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::GraphMetadata(), metaDataGraphUri ) );
213 tmpModel->addStatement( Soprano::Statement( metaDataGraphUri, Soprano::Vocabulary::NRL::coreGraphMetadataFor(), dataGraphUri, metaDataGraphUri ) );
214 tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::RDF::type(), graphType, metaDataGraphUri ) );
215 if ( graphType == Soprano::Vocabulary::NRL::KnowledgeBase() ) {
216 // we do not have inference in Nepomuk yet and this way libnepomuk does not get confused when reading types
217 tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::Ontology(), metaDataGraphUri ) );
218 tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::InstanceBase(), metaDataGraphUri ) );
220 tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::NAO::hasDefaultNamespace(), LiteralValue( ns.toString() ), metaDataGraphUri ) );
224 * Simple garbage collection class.
226 class ObjectGarbageCollector
228 public:
229 ObjectGarbageCollector( QObject* o )
230 : m_object( o ) {
232 ~ObjectGarbageCollector() {
233 delete m_object;
236 private:
237 QObject* m_object;
242 class Nepomuk::OntologyManagerModel::Private
244 public:
245 Private( OntologyManagerModel* p )
246 : q( p ) {
249 private:
250 OntologyManagerModel* q;
257 Nepomuk::OntologyManagerModel::OntologyManagerModel( Soprano::Model* parentModel, QObject* parent )
258 : FilterModel( parentModel ),
259 d( new Private( this ) )
261 setParent( parent );
265 Nepomuk::OntologyManagerModel::~OntologyManagerModel()
267 delete d;
271 void Nepomuk::OntologyManagerModel::setParentModel( Soprano::Model* parentModel )
273 FilterModel::setParentModel( parentModel );
277 bool Nepomuk::OntologyManagerModel::updateOntology( Soprano::StatementIterator data, const QUrl& ns )
279 clearError();
281 QTime timer;
282 timer.start();
284 // Create temp memory model
285 // ------------------------------------
286 const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByFeatures( Soprano::BackendFeatureStorageMemory );
287 if ( !backend ) {
288 kDebug() << "No Soprano backend found that can handle memory models!";
289 setError( "No Soprano backend found that can handle memory models." );
290 return false;
293 Soprano::Model* tmpModel = backend->createModel( BackendSettings() << BackendSetting( Soprano::BackendOptionStorageMemory ) );
294 if ( !tmpModel ) {
295 kDebug() << "Failed to create temp memory model!";
296 setError( backend->lastError() );
297 return false;
300 // so we do not have to care about deleting out tmpModel anymore.
301 ObjectGarbageCollector modelGarbageCollector( tmpModel );
303 // import the data into our tmp model
304 while ( data.next() ) {
305 tmpModel->addStatement( *data );
308 QUrl ontoUri = ns;
309 if ( ontoUri.isEmpty() ) {
310 StatementIterator it = tmpModel->listStatements();
311 if ( it.next() ) {
312 ontoUri = it.current().subject().uri();
313 if ( !ontoUri.fragment().isEmpty() ) {
314 ontoUri.setFragment( QString() );
316 else {
317 ontoUri = ontoUri.toString().left( ontoUri.toString().lastIndexOf( '/' )+1 );
321 if ( ontoUri.isEmpty() ) {
322 kDebug() << "Failed to determine ontology URI.";
323 setError( "Failed to determine ontology URI from data." );
324 return false;
327 // all the data has been read into the temp model
328 // now we make sure it has a proper layout (one main and one metadata graph)
329 // ------------------------------------
330 QList<Node> graphs = tmpModel->listContexts().allNodes();
331 if ( graphs.count() == 0 ) {
332 // simple: we have to create all data manually
333 createMetadata( tmpModel, ontoUri );
335 else if ( graphs.count() == 2 ) {
336 // proper number of graphs. Make sure we have all the necessary information
337 if ( !ensureDataLayout( tmpModel, ontoUri ) ) {
338 setError( "The ontology data contains invalid statements.", Soprano::Error::ErrorInvalidArgument );
339 return false;
342 else {
343 kDebug() << "Invalid data in ontology" << ontoUri << "We need one data and one metadata graph.";
344 setError( "The ontology data contains invalid statements.", Soprano::Error::ErrorInvalidArgument );
345 return false;
349 // store the modification date of the ontology file in the metadata graph and reuse it to know if we have to update
350 // ------------------------------------
351 QUrl dataGraphUri, metadataGraphUri;
352 if ( findGraphUris( tmpModel, ontoUri, dataGraphUri, metadataGraphUri ) ) {
353 // remove any modification date data there is
354 tmpModel->removeAllStatements( dataGraphUri, Soprano::Vocabulary::NAO::lastModified(), Node() );
356 // set the new modification date
357 tmpModel->addStatement( dataGraphUri, Soprano::Vocabulary::NAO::lastModified(), LiteralValue( QDateTime::currentDateTime() ), metadataGraphUri );
359 // now it is time to merge the new data in
360 // ------------------------------------
361 if ( ontoModificationDate( ontoUri ).isValid() ) {
362 if ( !removeOntology( ontoUri ) ) {
363 return false;
367 StatementIterator it = tmpModel->listStatements();
368 while ( it.next() ) {
369 if ( addStatement( *it ) != Error::ErrorNone ) {
370 // FIXME: here we should cleanup, but then again, if adding the statement
371 // fails, removing will probably also fail. So the only real solution
372 // would be a transaction.
373 return false;
377 kDebug() << "Successfully updated ontology" << ontoUri << QString("(%1ms)").arg(timer.elapsed());
378 return true;
380 else {
381 kDebug() << "BUG! BUG! BUG! BUG! BUG! BUG! Could not find data and metadata graph URIs! This should not happen!";
382 return false;
387 bool Nepomuk::OntologyManagerModel::removeOntology( const QUrl& ns )
389 clearError();
391 QUrl dataGraphUri, metadataGraphUri;
392 if ( findGraphUris( this, ns, dataGraphUri, metadataGraphUri ) ) {
393 // now removing the ontology is simple
394 removeContext( dataGraphUri );
395 removeContext( metadataGraphUri );
396 return true;
398 else {
399 kDebug() << "Could not find data graph URI for" << ns;
400 setError( "Could not find ontology " + ns.toString(), Error::ErrorInvalidArgument );
401 return false;
406 QDateTime Nepomuk::OntologyManagerModel::ontoModificationDate( const QUrl& uri )
408 QueryResultIterator it = executeQuery( QString( "select ?date where { "
409 "?onto <%1> \"%2\"^^<%3> . "
410 "?onto <%4> ?date . "
411 "FILTER(DATATYPE(?date) = <%5>) . }" )
412 .arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() )
413 .arg( uri.toString() )
414 .arg( Soprano::Vocabulary::XMLSchema::string().toString() )
415 .arg( Soprano::Vocabulary::NAO::lastModified().toString() )
416 .arg( Soprano::Vocabulary::XMLSchema::dateTime().toString() ),
417 Soprano::Query::QueryLanguageSparql );
418 if ( it.next() ) {
419 kDebug() << "Found modification date for" << uri << it.binding( "date" ).literal().toDateTime();
420 return it.binding( "date" ).literal().toDateTime();
422 else {
423 return QDateTime();
428 QUrl Nepomuk::OntologyManagerModel::findOntologyContext( const QUrl& uri )
430 QUrl dataGraphUri, metaDataGraphUri;
431 if ( findGraphUris( parentModel(), uri, dataGraphUri, metaDataGraphUri ) ) {
432 return dataGraphUri;
434 else {
435 return QUrl();
439 #include "ontologymanagermodel.moc"