~ubuntu-branches/ubuntu/quantal/kde-runtime/quantal

« back to all changes in this revision

Viewing changes to nepomuk/services/storage/ontologymanagermodel.cpp

  • Committer: Package Import Robot
  • Author(s): Philip Muškovac
  • Date: 2012-06-03 21:50:00 UTC
  • mto: This revision was merged to the branch mainline in revision 21.
  • Revision ID: package-import@ubuntu.com-20120603215000-vn7oarsq0ynrydj5
Tags: upstream-4.8.80
Import upstream version 4.8.80

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* This file is part of the KDE Project
2
 
   Copyright (c) 2008 Sebastian Trueg <trueg@kde.org>
3
 
 
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.
7
 
 
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.
12
 
 
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.
17
 
*/
18
 
 
19
 
#include "ontologymanagermodel.h"
20
 
#include "crappyinferencer.h"
21
 
 
22
 
#include <QtCore/QUrl>
23
 
#include <QtCore/QDateTime>
24
 
#include <QtCore/QScopedPointer>
25
 
 
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>
40
 
 
41
 
#include <KDebug>
42
 
 
43
 
 
44
 
using namespace Soprano;
45
 
 
46
 
 
47
 
 
48
 
namespace {
49
 
    /**
50
 
     * Create a uri for an nrl:MetadataGraph.
51
 
     * \param uri The uri of the data graph.
52
 
     */
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( '/' ) )
58
 
            s += '/';
59
 
        s += "metadata";
60
 
        return QUrl( s );
61
 
    }
62
 
 
63
 
    /**
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)
69
 
     *
70
 
     * \return \p true if the ontology was found and both dataGraphUri and metaDataGraphUri are filled
71
 
     * with proper values.
72
 
     */
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 { "
76
 
                                 "?dg <%1> ?ns . "
77
 
                                 "?mdg <%3> ?dg . "
78
 
                                 "FILTER(REGEX(STR(?ns), \"^%2\")) . "
79
 
                                 "}" )
80
 
                        .arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() )
81
 
                        .arg( ns.toString() )
82
 
                        .arg( Soprano::Vocabulary::NRL::coreGraphMetadataFor().toString() );
83
 
        QueryResultIterator it = model->executeQuery( query, Soprano::Query::QueryLanguageSparql );
84
 
        if ( it.next() ) {
85
 
            metaDataGraphUri = it.binding("mdg").uri();
86
 
            dataGraphUri = it.binding("dg").uri();
87
 
            return true;
88
 
        }
89
 
        else {
90
 
            return false;
91
 
        }
92
 
    }
93
 
 
94
 
    /**
95
 
     * Check if the ontology with namespace \p ns has a proper NRL layout in model
96
 
     * \p model.
97
 
     *
98
 
     * An ontology that passes this test can be imported into a production model without
99
 
     * any modifications.
100
 
     *
101
 
     * \return \p true if the necessary NRL graphs are defined, \p false otherwise
102
 
     */
103
 
    bool ensureDataLayout( Soprano::Model* tmpModel, const QUrl& ns )
104
 
    {
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;
110
 
                return false;
111
 
            }
112
 
        }
113
 
 
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.";
118
 
            return false;
119
 
        }
120
 
 
121
 
        return true;
122
 
    }
123
 
 
124
 
 
125
 
    /**
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
128
 
     */
129
 
    QUrl guessOntologyType( Soprano::Model* tmpModel )
130
 
    {
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();
152
 
 
153
 
        // check for classes and properties
154
 
        QStringList classesOrPropertiesSubQueries;
155
 
        foreach( const QUrl& uri, propertyClasses ) {
156
 
            classesOrPropertiesSubQueries << QString( "?type = <%1>" ).arg( uri.toString() );
157
 
        }
158
 
 
159
 
        // we cannot use UNION here because redland does not support it!
160
 
        bool haveClassesOrProperties = tmpModel->executeQuery( QString( "ask where { "
161
 
                                                                        "?r a ?type . "
162
 
                                                                        "FILTER(%1) . }" )
163
 
                                                               .arg( classesOrPropertiesSubQueries.join( " || " ) ),
164
 
                                                               Soprano::Query::QueryLanguageSparql ).boolValue();
165
 
 
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() );
170
 
        }
171
 
        // owl:Ontologys do not have any influce on our descision
172
 
        classesOrPropertiesSubQueries << QString( "?type != <%1>" ).arg( Soprano::Vocabulary::OWL::Ontology().toString() );
173
 
 
174
 
        bool haveInstances = tmpModel->executeQuery( QString( "ask where { "
175
 
                                                              "?r a ?type . "
176
 
                                                              "FILTER(%1) . }" )
177
 
                                                     .arg( classesOrPropertiesSubQueries.join( " && " ) ),
178
 
                                                     Soprano::Query::QueryLanguageSparql ).boolValue();
179
 
 
180
 
        if ( haveClassesOrProperties && !haveInstances )
181
 
            return Soprano::Vocabulary::NRL::Ontology();
182
 
        else if ( !haveClassesOrProperties && haveInstances )
183
 
            return Soprano::Vocabulary::NRL::InstanceBase();
184
 
        else
185
 
            return Soprano::Vocabulary::NRL::KnowledgeBase();
186
 
    }
187
 
 
188
 
 
189
 
    /**
190
 
     * Create the necessary NRL graphs and metadata for an ontology to pass ensureDataLayout.
191
 
     *
192
 
     * \param tmpModel The model to store everything in
193
 
     * \param ns The namespace of the ontology to modify in \p tmpModel
194
 
     */
195
 
    void createMetadata( Soprano::Model* tmpModel, const QUrl& ns )
196
 
    {
197
 
        Q_ASSERT( ns.isValid() );
198
 
        QUrl dataGraphUri( ns );
199
 
        dataGraphUri.setFragment( QString() );
200
 
        QUrl metaDataGraphUri = createMetadataGraphUri( dataGraphUri );
201
 
 
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 );
208
 
        }
209
 
 
210
 
        QUrl graphType = guessOntologyType( tmpModel );
211
 
 
212
 
        kDebug() << "guessed onto type:" << graphType;
213
 
 
214
 
        // add the metadata
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 ) );
222
 
        }
223
 
#if SOPRANO_IS_VERSION( 2, 2, 67 )
224
 
        tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::NAO::hasDefaultNamespace(), LiteralValue::createPlainLiteral( ns.toString() ), metaDataGraphUri ) );
225
 
#else
226
 
        tmpModel->addStatement( Soprano::Statement( dataGraphUri, Soprano::Vocabulary::NAO::hasDefaultNamespace(), LiteralValue( ns.toString() ), metaDataGraphUri ) );
227
 
#endif
228
 
    }
229
 
}
230
 
 
231
 
 
232
 
class Nepomuk::OntologyManagerModel::Private
233
 
{
234
 
public:
235
 
    Private( OntologyManagerModel* p )
236
 
        : q( p ) {
237
 
    }
238
 
 
239
 
    CrappyInferencer m_inferenceModel;
240
 
 
241
 
private:
242
 
    OntologyManagerModel* q;
243
 
};
244
 
 
245
 
 
246
 
 
247
 
 
248
 
 
249
 
Nepomuk::OntologyManagerModel::OntologyManagerModel( Soprano::Model* parentModel, QObject* parent )
250
 
    : FilterModel(),
251
 
      d( new Private( this ) )
252
 
{
253
 
    setParent( parent );
254
 
    FilterModel::setParentModel( &d->m_inferenceModel );
255
 
    setParentModel( parentModel );
256
 
}
257
 
 
258
 
 
259
 
Nepomuk::OntologyManagerModel::~OntologyManagerModel()
260
 
{
261
 
    delete d;
262
 
}
263
 
 
264
 
 
265
 
void Nepomuk::OntologyManagerModel::setParentModel( Soprano::Model* parentModel )
266
 
{
267
 
    d->m_inferenceModel.setParentModel( parentModel );
268
 
}
269
 
 
270
 
 
271
 
bool Nepomuk::OntologyManagerModel::updateOntology( Soprano::StatementIterator data, const QUrl& ns )
272
 
{
273
 
    clearError();
274
 
 
275
 
    QTime timer;
276
 
    timer.start();
277
 
 
278
 
    // Create temp memory model
279
 
    // ------------------------------------
280
 
    const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByFeatures( Soprano::BackendFeatureStorageMemory );
281
 
    if ( !backend ) {
282
 
        kDebug() << "No Soprano backend found that can handle memory models!";
283
 
        setError( "No Soprano backend found that can handle memory models." );
284
 
        return false;
285
 
    }
286
 
 
287
 
    Soprano::Model* tmpModel = backend->createModel( BackendSettings() << BackendSetting( Soprano::BackendOptionStorageMemory ) );
288
 
    if ( !tmpModel ) {
289
 
        kDebug() << "Failed to create temp memory model!";
290
 
        setError( backend->lastError() );
291
 
        return false;
292
 
    }
293
 
 
294
 
    // so we do not have to care about deleting out tmpModel anymore.
295
 
    QScopedPointer<Soprano::Model> modelGarbageCollector( tmpModel );
296
 
 
297
 
    // import the data into our tmp model
298
 
    while ( data.next() ) {
299
 
        tmpModel->addStatement( *data );
300
 
    }
301
 
 
302
 
    QUrl ontoUri = ns;
303
 
    if ( ontoUri.isEmpty() ) {
304
 
        StatementIterator it = tmpModel->listStatements();
305
 
        if ( it.next() ) {
306
 
            ontoUri = it.current().subject().uri();
307
 
            if ( !ontoUri.fragment().isEmpty() ) {
308
 
                ontoUri.setFragment( QString() );
309
 
            }
310
 
            else {
311
 
                ontoUri = ontoUri.toString().left( ontoUri.toString().lastIndexOf( '/' )+1 );
312
 
            }
313
 
        }
314
 
    }
315
 
    if ( ontoUri.isEmpty() ) {
316
 
        kDebug() << "Failed to determine ontology URI.";
317
 
        setError( "Failed to determine ontology URI from data." );
318
 
        return false;
319
 
    }
320
 
 
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 );
328
 
    }
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 );
333
 
            return false;
334
 
        }
335
 
    }
336
 
    else {
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 );
339
 
        return false;
340
 
    }
341
 
 
342
 
 
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() );
349
 
 
350
 
        // set the new modification date
351
 
        tmpModel->addStatement( dataGraphUri, Soprano::Vocabulary::NAO::lastModified(), LiteralValue( QDateTime::currentDateTime() ), metadataGraphUri );
352
 
 
353
 
        // now it is time to merge the new data in
354
 
        // ------------------------------------
355
 
        if ( ontoModificationDate( ontoUri ).isValid() ) {
356
 
            if ( !removeOntology( ontoUri ) ) {
357
 
                return false;
358
 
            }
359
 
        }
360
 
 
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.
367
 
                return false;
368
 
            }
369
 
        }
370
 
 
371
 
        kDebug() << "Successfully updated ontology" << ontoUri << QString("(%1ms)").arg(timer.elapsed());
372
 
        return true;
373
 
    }
374
 
    else {
375
 
        kDebug() << "BUG! BUG! BUG! BUG! BUG! BUG! Could not find data and metadata graph URIs! This should not happen!";
376
 
        return false;
377
 
    }
378
 
}
379
 
 
380
 
 
381
 
bool Nepomuk::OntologyManagerModel::removeOntology( const QUrl& ns )
382
 
{
383
 
    clearError();
384
 
 
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() );
392
 
        return true;
393
 
    }
394
 
    else {
395
 
        kDebug() << "Could not find data graph URI for" << ns;
396
 
        setError( "Could not find ontology " + ns.toString(), Error::ErrorInvalidArgument );
397
 
        return false;
398
 
    }
399
 
}
400
 
 
401
 
 
402
 
QDateTime Nepomuk::OntologyManagerModel::ontoModificationDate( const QUrl& uri )
403
 
{
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 { "
406
 
                             "?onto <%1> ?ns . "
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 );
415
 
    if ( it.next() ) {
416
 
        //kDebug() << "Found modification date for" << uri << it.binding( "date" ).literal().toDateTime();
417
 
        return it.binding( "date" ).literal().toDateTime();
418
 
    }
419
 
    else {
420
 
        return QDateTime();
421
 
    }
422
 
}
423
 
 
424
 
 
425
 
QUrl Nepomuk::OntologyManagerModel::findOntologyContext( const QUrl& uri )
426
 
{
427
 
    QUrl dataGraphUri, metaDataGraphUri;
428
 
    if ( findGraphUris( parentModel(), uri, dataGraphUri, metaDataGraphUri ) ) {
429
 
        return dataGraphUri;
430
 
    }
431
 
    else {
432
 
        return QUrl();
433
 
    }
434
 
}
435
 
 
436
 
#include "ontologymanagermodel.moc"