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>
41 using namespace Soprano
;
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( '/' ) )
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
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> . "
75 .arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() )
77 .arg( Soprano::Vocabulary::XMLSchema::string().toString() )
78 .arg( Soprano::Vocabulary::NRL::coreGraphMetadataFor().toString() );
80 QueryResultIterator it
= model
->executeQuery( query
, Soprano::Query::QueryLanguageSparql
);
82 metaDataGraphUri
= it
.binding("mdg").uri();
83 dataGraphUri
= it
.binding("dg").uri();
92 * Check if the ontology with namespace \p ns has a proper NRL layout in model
95 * An ontology that passes this test can be imported into a production model without
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
;
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.";
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 { "
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 { "
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();
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
;
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
229 ObjectGarbageCollector( QObject
* o
)
232 ~ObjectGarbageCollector() {
242 class Nepomuk::OntologyManagerModel::Private
245 Private( OntologyManagerModel
* p
)
250 OntologyManagerModel
* q
;
257 Nepomuk::OntologyManagerModel::OntologyManagerModel( Soprano::Model
* parentModel
, QObject
* parent
)
258 : FilterModel( parentModel
),
259 d( new Private( this ) )
265 Nepomuk::OntologyManagerModel::~OntologyManagerModel()
271 void Nepomuk::OntologyManagerModel::setParentModel( Soprano::Model
* parentModel
)
273 FilterModel::setParentModel( parentModel
);
277 bool Nepomuk::OntologyManagerModel::updateOntology( Soprano::StatementIterator data
, const QUrl
& ns
)
284 // Create temp memory model
285 // ------------------------------------
286 const Soprano::Backend
* backend
= Soprano::PluginManager::instance()->discoverBackendByFeatures( Soprano::BackendFeatureStorageMemory
);
288 kDebug() << "No Soprano backend found that can handle memory models!";
289 setError( "No Soprano backend found that can handle memory models." );
293 Soprano::Model
* tmpModel
= backend
->createModel( BackendSettings() << BackendSetting( Soprano::BackendOptionStorageMemory
) );
295 kDebug() << "Failed to create temp memory model!";
296 setError( backend
->lastError() );
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
);
309 if ( ontoUri
.isEmpty() ) {
310 StatementIterator it
= tmpModel
->listStatements();
312 ontoUri
= it
.current().subject().uri();
313 if ( !ontoUri
.fragment().isEmpty() ) {
314 ontoUri
.setFragment( QString() );
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." );
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
);
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
);
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
) ) {
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.
377 kDebug() << "Successfully updated ontology" << ontoUri
<< QString("(%1ms)").arg(timer
.elapsed());
381 kDebug() << "BUG! BUG! BUG! BUG! BUG! BUG! Could not find data and metadata graph URIs! This should not happen!";
387 bool Nepomuk::OntologyManagerModel::removeOntology( const QUrl
& ns
)
391 QUrl dataGraphUri
, metadataGraphUri
;
392 if ( findGraphUris( this, ns
, dataGraphUri
, metadataGraphUri
) ) {
393 // now removing the ontology is simple
394 removeContext( dataGraphUri
);
395 removeContext( metadataGraphUri
);
399 kDebug() << "Could not find data graph URI for" << ns
;
400 setError( "Could not find ontology " + ns
.toString(), Error::ErrorInvalidArgument
);
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
);
419 kDebug() << "Found modification date for" << uri
<< it
.binding( "date" ).literal().toDateTime();
420 return it
.binding( "date" ).literal().toDateTime();
428 QUrl
Nepomuk::OntologyManagerModel::findOntologyContext( const QUrl
& uri
)
430 QUrl dataGraphUri
, metaDataGraphUri
;
431 if ( findGraphUris( parentModel(), uri
, dataGraphUri
, metaDataGraphUri
) ) {
439 #include "ontologymanagermodel.moc"